diff options
194 files changed, 4015 insertions, 2555 deletions
diff --git a/Android.bp b/Android.bp index 6e3799b74cd6..5070b5ea2403 100644 --- a/Android.bp +++ b/Android.bp @@ -405,6 +405,8 @@ filegroup { filegroup { name: "statsd_aidl", srcs: [ + "core/java/android/os/IPullAtomCallback.aidl", + "core/java/android/os/IPullAtomResultReceiver.aidl", "core/java/android/os/IStatsCompanionService.aidl", "core/java/android/os/IStatsManager.aidl", "core/java/android/os/IStatsPullerCallback.aidl", @@ -425,6 +427,7 @@ java_library { name: "framework-minus-apex", defaults: ["framework-defaults"], srcs: [":framework-non-updatable-sources"], + libs: ["app-compat-annotations"], installable: true, javac_shard_size: 150, required: [ @@ -463,6 +466,15 @@ java_library { defaults: ["framework-defaults"], srcs: [":framework-all-sources"], installable: false, + libs: ["app-compat-annotations"], +} + +java_library { + name: "framework-annotation-proc", + defaults: ["framework-aidl-export-defaults"], + srcs: [":framework-all-sources"], + libs: ["app-compat-annotations"], + installable: false, plugins: [ "unsupportedappusage-annotation-processor", "compat-changeid-annotation-processor", @@ -471,7 +483,7 @@ java_library { platform_compat_config { name: "framework-platform-compat-config", - src: ":framework-all", + src: ":framework-annotation-proc", } // A library including just UnsupportedAppUsage.java classes. @@ -502,6 +514,7 @@ java_library { java_library { name: "framework-atb-backward-compatibility", installable: true, + libs: ["app-compat-annotations"], srcs: [ "core/java/android/content/pm/AndroidTestBaseUpdater.java", ], @@ -1595,7 +1608,7 @@ aidl_mapping { genrule { name: "framework-annotation-proc-index", - srcs: [":framework-all"], + srcs: [":framework-annotation-proc"], cmd: "unzip -qp $(in) unsupportedappusage/unsupportedappusage_index.csv > $(out)", out: ["unsupportedappusage_index.csv"], dist: { 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 593e49490e94..0a4e020e07cd 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -57,6 +57,7 @@ import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -2688,13 +2689,14 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - protected int handleShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out, - @NonNull FileDescriptor err, @NonNull String[] args) { + protected int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { return (new JobSchedulerShellCommand(JobSchedulerService.this)).exec( - this, in, out, err, args); + this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); } - /** * <b>For internal system user only!</b> * Returns a list of all currently-executing jobs. diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index d879273df3bc..5cb9834de130 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -76,6 +76,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IPullAtomCallback; import android.os.IStatsCompanionService; import android.os.IStatsManager; import android.os.IStoraged; @@ -165,6 +166,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -272,6 +274,72 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private final BroadcastReceiver mAppUpdateReceiver; private final BroadcastReceiver mUserUpdateReceiver; private final ShutdownEventReceiver mShutdownEventReceiver; + + private static final class PullerKey { + private final int mUid; + private final int mAtomTag; + + PullerKey(int uid, int atom) { + mUid = uid; + mAtomTag = atom; + } + + public int getUid() { + return mUid; + } + + public int getAtom() { + return mAtomTag; + } + + @Override + public int hashCode() { + return Objects.hash(mUid, mAtomTag); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PullerKey) { + PullerKey other = (PullerKey) obj; + return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); + } + return false; + } + } + + private static final class PullerValue { + private final long mCoolDownNs; + private final long mTimeoutNs; + private int[] mAdditiveFields; + private IPullAtomCallback mCallback; + + PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, + IPullAtomCallback callback) { + mCoolDownNs = coolDownNs; + mTimeoutNs = timeoutNs; + mAdditiveFields = additiveFields; + mCallback = callback; + } + + public long getCoolDownNs() { + return mCoolDownNs; + } + + public long getTimeoutNs() { + return mTimeoutNs; + } + + public int[] getAdditiveFields() { + return mAdditiveFields; + } + + public IPullAtomCallback getCallback() { + return mCallback; + } + } + + private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>(); + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); private IWifiManager mWifiManager = null; @@ -323,7 +391,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onReceive(Context context, Intent intent) { synchronized (sStatsdLock) { - sStatsd = fetchStatsdService(); if (sStatsd == null) { Slog.w(TAG, "Could not access statsd for UserUpdateReceiver"); return; @@ -1735,7 +1802,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeString(Build.BRAND); e.writeString(Build.PRODUCT); e.writeString(Build.DEVICE); - e.writeString(Build.VERSION.RELEASE); + e.writeString(Build.VERSION.RELEASE_OR_CODENAME); e.writeString(Build.ID); e.writeString(Build.VERSION.INCREMENTAL); e.writeString(Build.TYPE); @@ -2553,10 +2620,40 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); } + @Override + public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + int[] additiveFields, IPullAtomCallback pullerCallback) { + synchronized (sStatsdLock) { + // Always cache the puller in SCS. + // If statsd is down, we will register it when it comes back up. + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + PullerValue val = new PullerValue( + coolDownNs, timeoutNs, additiveFields, pullerCallback); + mPullers.put(key, val); + + if (sStatsd == null) { + Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); + return; + } + try { + sStatsd.registerPullAtomCallback( + callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + // Lifecycle and related code /** - * Fetches the statsd IBinder service + * Fetches the statsd IBinder service. + * Note: This should only be called from sayHiToStatsd. All other clients should use the cached + * sStatsd with a null check. */ private static IStatsManager fetchStatsdService() { return IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); @@ -2654,6 +2751,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Pull the latest state of UID->app name, version mapping when // statsd starts. informAllUidsLocked(mContext); + // Register all pullers. If SCS has just started, this should be empty. + registerAllPullersLocked(); } finally { restoreCallingIdentity(token); } @@ -2665,10 +2764,21 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + @GuardedBy("sStatsdLock") + private void registerAllPullersLocked() throws RemoteException { + // TODO: pass in one call, using a file descriptor (similar to uidmap). + for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) { + PullerKey key = entry.getKey(); + PullerValue val = entry.getValue(); + sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(), + val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback()); + } + } + private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { - Slog.i(TAG, "Statsd is dead - erase all my knowledge."); + Slog.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); synchronized (sStatsdLock) { long now = SystemClock.elapsedRealtime(); for (Long timeMillis : mDeathTimeMillis) { diff --git a/api/current.txt b/api/current.txt index 424f2e2a9c76..1333e4a6c475 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9650,8 +9650,9 @@ package android.content { method public static boolean isSyncPending(android.accounts.Account, String); method @NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, @NonNull android.util.Size, @Nullable android.os.CancellationSignal) throws java.io.IOException; method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver); - method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean); + method @Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean); method public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, int); + method public void notifyChange(@NonNull Iterable<android.net.Uri>, @Nullable android.database.ContentObserver, int); method @Nullable public final android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException; method @Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException; method @Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException; @@ -24153,6 +24154,9 @@ package android.media { field public static final String TAG_MODEL = "Model"; field public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType"; field public static final String TAG_OECF = "OECF"; + field public static final String TAG_OFFSET_TIME = "OffsetTime"; + field public static final String TAG_OFFSET_TIME_DIGITIZED = "OffsetTimeDigitized"; + field public static final String TAG_OFFSET_TIME_ORIGINAL = "OffsetTimeOriginal"; field public static final String TAG_ORF_ASPECT_FRAME = "AspectFrame"; field public static final String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength"; field public static final String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart"; @@ -34389,6 +34393,7 @@ package android.os { field public static final String INCREMENTAL; field public static final int PREVIEW_SDK_INT; field public static final String RELEASE; + field @NonNull public static final String RELEASE_OR_CODENAME; field @Deprecated public static final String SDK; field public static final int SDK_INT; field public static final String SECURITY_PATCH; @@ -35355,6 +35360,8 @@ package android.os { public class RemoteException extends android.util.AndroidException { ctor public RemoteException(); ctor public RemoteException(String); + method @NonNull public RuntimeException rethrowAsRuntimeException(); + method @NonNull public RuntimeException rethrowFromSystemServer(); } public class ResultReceiver implements android.os.Parcelable { @@ -45224,6 +45231,7 @@ package android.telephony { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>); method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context); method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList(); + method public static int getActiveDataSubscriptionId(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfo(int); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getActiveSubscriptionInfoCount(); method public int getActiveSubscriptionInfoCountMax(); diff --git a/api/system-current.txt b/api/system-current.txt index 4df8e8c1da91..9420a286d67a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1414,6 +1414,7 @@ package android.content { method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler); method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); + method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[]); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); field public static final String APP_PREDICTION_SERVICE = "app_prediction"; @@ -6820,7 +6821,7 @@ package android.service.euicc { method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean); method @Deprecated public abstract int onEraseSubscriptions(int); - method public int onEraseSubscriptionsWithOptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int); + method public int onEraseSubscriptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int); method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean); method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean); method public abstract String onGetEid(int); @@ -8951,7 +8952,7 @@ package android.telephony.euicc { public class EuiccManager { method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(android.content.Intent, android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@NonNull android.app.PendingIntent); - method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptionsWithOptions(@android.telephony.euicc.EuiccCardManager.ResetOption int, @NonNull android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@android.telephony.euicc.EuiccCardManager.ResetOption int, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus(); @@ -9227,7 +9228,7 @@ package android.telephony.ims { public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getFeatureState(@NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull java.util.concurrent.Executor) throws android.telephony.ims.ImsException; + 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; method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoWiFiModeSetting(); diff --git a/api/test-current.txt b/api/test-current.txt index b8806720d8aa..44f736ca918a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3359,7 +3359,7 @@ package android.telephony.ims { public class ImsMmTelManager implements android.telephony.ims.RegistrationManager { method @NonNull public static android.telephony.ims.ImsMmTelManager createForSubscriptionId(int); - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getFeatureState(@NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull java.util.concurrent.Executor) throws android.telephony.ims.ImsException; + 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; method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getVoWiFiModeSetting(); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index b665a8b99fbd..f072c9c7b121 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1289,6 +1289,13 @@ Status StatsService::registerPullerCallback(int32_t atomTag, return Status::ok(); } +Status StatsService::registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs, + int64_t timeoutNs, const std::vector<int32_t>& additiveFields, + const sp<android::os::IPullAtomCallback>& pullerCallback) { + VLOG("StatsService::registerPuller called."); + return Status::ok(); +} + Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& packageName) { ENFORCE_DUMP_AND_USAGE_STATS(packageName); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 949094871936..6d40007826e7 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -180,6 +180,13 @@ public: const String16& packageName) override; /** + * Binder call to register a callback function for a pulled atom. + */ + virtual Status registerPullAtomCallback(int32_t uid, int32_t atomTag, int64_t coolDownNs, + int64_t timeoutNs, const std::vector<int32_t>& additiveFields, + const sp<android::os::IPullAtomCallback>& pullerCallback) override; + + /** * Binder call to unregister any existing callback function for a vendor pulled atom. */ virtual Status unregisterPullerCallback(int32_t atomTag, const String16& packageName) override; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index f0b354650cf4..e858e6a976bc 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1215,7 +1215,8 @@ public final class LoadedApk { } // Rewrite the R 'constants' for all library apks. - SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers(); + SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers( + false, false); final int N = packageIdentifiers.size(); for (int i = 0; i < N; i++) { final int id = packageIdentifiers.keyAt(i); diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index e6682d620b99..92bfee24d402 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -24,12 +24,22 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.os.IBinder; +import android.os.IPullAtomCallback; +import android.os.IPullAtomResultReceiver; +import android.os.IStatsCompanionService; import android.os.IStatsManager; import android.os.IStatsPullerCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.util.AndroidException; import android.util.Slog; +import android.util.StatsEvent; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; /** * API for statsd clients to send configurations and retrieve data. @@ -43,8 +53,12 @@ public final class StatsManager { private final Context mContext; + @GuardedBy("this") private IStatsManager mService; + @GuardedBy("this") + private IStatsCompanionService mStatsCompanion; + /** * Long extra of uid that added the relevant stats config. */ @@ -449,7 +463,9 @@ public final class StatsManager { * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service * * @hide + * @deprecated Please use registerPullAtomCallback */ + @Deprecated @RequiresPermission(allOf = { DUMP, PACKAGE_USAGE_STATS }) public void setPullerCallback(int atomTag, IStatsPullerCallback callback) throws StatsUnavailableException { @@ -472,6 +488,75 @@ public final class StatsManager { } } + + /** + * Registers a callback for an atom when that atom is to be pulled. The stats service will + * invoke pullData in the callback when the stats service determines that this atom needs to be + * pulled. + * + * @param atomTag The tag of the atom for this puller callback. + * @param coolDownNs The minimum time between successive pulls. A cache of the previous + * pull will be used if the time between pulls is less than coolDownNs. + * @param timeoutNs The maximum time a pull should take. Statsd will wait timeoutNs for + * the pull to complete before timing out and marking the pull as + * failed. + * @param additiveFields Fields that are added when mapping isolated uids to host uids. + * @param callback The callback to be invoked when the stats service pulls the atom. + * @throws RemoteException if unsuccessful due to failing to connect to system server. + * + * @hide + */ + public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + int[] additiveFields, @NonNull StatsPullAtomCallback callback, + @NonNull Executor executor) throws RemoteException, SecurityException { + synchronized (this) { + IStatsCompanionService service = getIStatsCompanionServiceLocked(); + PullAtomCallbackInternal rec = + new PullAtomCallbackInternal(atomTag, callback, executor); + service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields, rec); + } + } + + private static class PullAtomCallbackInternal extends IPullAtomCallback.Stub { + public final int mAtomId; + public final StatsPullAtomCallback mCallback; + public final Executor mExecutor; + + PullAtomCallbackInternal(int atomId, StatsPullAtomCallback callback, Executor executor) { + mAtomId = atomId; + mCallback = callback; + mExecutor = executor; + } + + @Override + public void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver) { + mExecutor.execute(() -> { + List<StatsEvent> data = new ArrayList<>(); + boolean success = mCallback.onPullAtom(atomTag, data); + StatsEvent[] arr = new StatsEvent[data.size()]; + arr = data.toArray(arr); + try { + resultReceiver.pullFinished(atomTag, success, arr); + } catch (RemoteException e) { + Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); + } + }); + } + } + + /** + * Callback interface for pulling atoms requested by the stats service. + * + * @hide + */ + public interface StatsPullAtomCallback { + /** + * Pull data for the specified atom tag, filling in the provided list of StatsEvent data. + * @return if the pull was successful + */ + boolean onPullAtom(int atomTag, List<StatsEvent> data); + } + private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { @@ -481,6 +566,7 @@ public final class StatsManager { } } + @GuardedBy("this") private IStatsManager getIStatsManagerLocked() throws StatsUnavailableException { if (mService != null) { return mService; @@ -497,6 +583,16 @@ public final class StatsManager { return mService; } + @GuardedBy("this") + private IStatsCompanionService getIStatsCompanionServiceLocked() { + if (mStatsCompanion != null) { + return mStatsCompanion; + } + mStatsCompanion = IStatsCompanionService.Stub.asInterface( + ServiceManager.getService("statscompanion")); + return mStatsCompanion; + } + /** * Exception thrown when communication with the stats service fails (eg if it is not available). * This might be thrown early during boot before the stats service has started or if it crashed. diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 2657cc54b91a..61c8db5db124 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -65,6 +65,7 @@ import android.text.TextUtils; import android.util.EventLog; import android.util.Log; import android.util.Size; +import android.util.SparseArray; import com.android.internal.util.MimeIconUtils; import com.android.internal.util.Preconditions; @@ -2381,15 +2382,15 @@ public abstract class ContentResolver implements ContentInterface { * true. * @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}. * @see #requestSync(android.accounts.Account, String, android.os.Bundle) + * @deprecated callers should consider migrating to + * {@link #notifyChange(Uri, ContentObserver, int)}, as it + * offers support for many more options than just + * {@link #NOTIFY_SYNC_TO_NETWORK}. */ + @Deprecated public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer, boolean syncToNetwork) { - Preconditions.checkNotNull(uri, "uri"); - notifyChange( - ContentProvider.getUriWithoutUserId(uri), - observer, - syncToNetwork, - ContentProvider.getUserIdFromUri(uri, mContext.getUserId())); + notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0); } /** @@ -2398,10 +2399,10 @@ public abstract class ContentResolver implements ContentInterface { * To observe events sent through this call, use * {@link #registerContentObserver(Uri, boolean, ContentObserver)}. * <p> - * If syncToNetwork is true, this will attempt to schedule a local sync - * using the sync adapter that's registered for the authority of the - * provided uri. No account will be passed to the sync adapter, so all - * matching accounts will be synchronized. + * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule + * a local sync using the sync adapter that's registered for the authority + * of the provided uri. No account will be passed to the sync adapter, so + * all matching accounts will be synchronized. * <p> * Starting in {@link android.os.Build.VERSION_CODES#O}, all content * notifications must be backed by a valid {@link ContentProvider}. @@ -2427,21 +2428,71 @@ public abstract class ContentResolver implements ContentInterface { } /** + * Notify registered observers that several rows have been updated. + * <p> + * To observe events sent through this call, use + * {@link #registerContentObserver(Uri, boolean, ContentObserver)}. + * <p> + * If {@link #NOTIFY_SYNC_TO_NETWORK} is set, this will attempt to schedule + * a local sync using the sync adapter that's registered for the authority + * of the provided uri. No account will be passed to the sync adapter, so + * all matching accounts will be synchronized. + * <p> + * Starting in {@link android.os.Build.VERSION_CODES#O}, all content + * notifications must be backed by a valid {@link ContentProvider}. + * + * @param uris The uris of the content that was changed. + * @param observer The observer that originated the change, may be + * <code>null</null>. The observer that originated the change + * will only receive the notification if it has requested to + * receive self-change notifications by implementing + * {@link ContentObserver#deliverSelfNotifications()} to return + * true. + * @param flags Flags such as {@link #NOTIFY_SYNC_TO_NETWORK} or + * {@link #NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS}. + */ + public void notifyChange(@NonNull Iterable<Uri> uris, @Nullable ContentObserver observer, + @NotifyFlags int flags) { + Preconditions.checkNotNull(uris, "uris"); + + // Cluster based on user ID + final SparseArray<ArrayList<Uri>> clusteredByUser = new SparseArray<>(); + for (Uri uri : uris) { + final int userId = ContentProvider.getUserIdFromUri(uri, mContext.getUserId()); + ArrayList<Uri> list = clusteredByUser.get(userId); + if (list == null) { + list = new ArrayList<>(); + clusteredByUser.put(userId, list); + } + list.add(ContentProvider.getUriWithoutUserId(uri)); + } + + for (int i = 0; i < clusteredByUser.size(); i++) { + final int userId = clusteredByUser.keyAt(i); + final ArrayList<Uri> list = clusteredByUser.valueAt(i); + notifyChange(list.toArray(new Uri[list.size()]), observer, flags, userId); + } + } + + /** * Notify registered observers within the designated user(s) that a row was updated. * + * @deprecated callers should consider migrating to + * {@link #notifyChange(Uri, ContentObserver, int)}, as it + * offers support for many more options than just + * {@link #NOTIFY_SYNC_TO_NETWORK}. * @hide */ + @Deprecated public void notifyChange(@NonNull Uri uri, ContentObserver observer, boolean syncToNetwork, @UserIdInt int userHandle) { - try { - getContentService().notifyChange( - uri, observer == null ? null : observer.getContentObserver(), - observer != null && observer.deliverSelfNotifications(), - syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, - userHandle, mTargetSdkVersion, mContext.getPackageName()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + notifyChange(uri, observer, syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0, userHandle); + } + + /** {@hide} */ + public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags, + @UserIdInt int userHandle) { + notifyChange(new Uri[] { uri }, observer, flags, userHandle); } /** @@ -2449,11 +2500,11 @@ public abstract class ContentResolver implements ContentInterface { * * @hide */ - public void notifyChange(@NonNull Uri uri, ContentObserver observer, @NotifyFlags int flags, + public void notifyChange(@NonNull Uri[] uris, ContentObserver observer, @NotifyFlags int flags, @UserIdInt int userHandle) { try { getContentService().notifyChange( - uri, observer == null ? null : observer.getContentObserver(), + uris, observer == null ? null : observer.getContentObserver(), observer != null && observer.deliverSelfNotifications(), flags, userHandle, mTargetSdkVersion, mContext.getPackageName()); } catch (RemoteException e) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 41b773e81c46..03b49136bde4 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2072,7 +2072,7 @@ public abstract class Context { * Intent will receive the broadcast. * @param receiverPermissions Array of names of permissions that a receiver must hold * in order to receive your broadcast. - * If null or empty, no permissions are required. + * If empty, no permissions are required. * * @see android.content.BroadcastReceiver * @see #registerReceiver @@ -2081,8 +2081,11 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) * @hide */ - public abstract void sendBroadcastMultiplePermissions(Intent intent, - String[] receiverPermissions); + @SystemApi + public void sendBroadcastMultiplePermissions(@NonNull Intent intent, + @NonNull String[] receiverPermissions) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } /** * Broadcast the given intent to all interested BroadcastReceivers, allowing diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 5bdea52d63e1..b04f7810ed45 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -464,7 +464,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) { + public void sendBroadcastMultiplePermissions(@NonNull Intent intent, + @NonNull String[] receiverPermissions) { mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions); } diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index a34a9951671c..03c99e1a2344 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -51,7 +51,7 @@ interface IContentService { * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL * USER_CURRENT are properly interpreted. */ - void notifyChange(in Uri uri, IContentObserver observer, + void notifyChange(in Uri[] uris, IContentObserver observer, boolean observerWantsSelfNotifications, int flags, int userHandle, int targetSdkVersion, String callingPackage); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 23e772075ad6..070e282a0eb2 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1491,9 +1491,17 @@ public final class AssetManager implements AutoCloseable { */ @UnsupportedAppUsage public SparseArray<String> getAssignedPackageIdentifiers() { + return getAssignedPackageIdentifiers(true, true); + } + + /** + * @hide + */ + public SparseArray<String> getAssignedPackageIdentifiers(boolean includeOverlays, + boolean includeLoaders) { synchronized (this) { ensureValidLocked(); - return nativeGetAssignedPackageIdentifiers(mObject); + return nativeGetAssignedPackageIdentifiers(mObject, includeOverlays, includeLoaders); } } @@ -1557,7 +1565,7 @@ public final class AssetManager implements AutoCloseable { int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion); private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( - long ptr); + long ptr, boolean includeOverlays, boolean includeLoaders); // File native methods. private static native @Nullable String[] nativeList(long ptr, @NonNull String path) diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index a856975e2501..ec3919997603 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -932,8 +932,14 @@ public class Binder implements IBinder { } int result = -1; - try { - result = handleShellCommand(in, out, err, args); + try (ParcelFileDescriptor inPfd = ParcelFileDescriptor.dup(in); + ParcelFileDescriptor outPfd = ParcelFileDescriptor.dup(out); + ParcelFileDescriptor errPfd = ParcelFileDescriptor.dup(err)) { + result = handleShellCommand(inPfd, outPfd, errPfd, args); + } catch (IOException e) { + PrintWriter pw = new FastPrintWriter(new FileOutputStream(err)); + pw.println("dup() failed: " + e.getMessage()); + pw.flush(); } finally { resultReceiver.send(result, null); } @@ -954,9 +960,10 @@ public class Binder implements IBinder { * @hide */ // @SystemApi TODO Make it a system API. - protected int handleShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out, - @NonNull FileDescriptor err, @NonNull String[] args) { - FileOutputStream ferr = new FileOutputStream(err); + protected int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + FileOutputStream ferr = new FileOutputStream(err.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(ferr); pw.println("No shell command implementation."); pw.flush(); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 400d98159a7b..733079691a15 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -240,6 +240,13 @@ public class Build { public static final String RELEASE = getString("ro.build.version.release"); /** + * The version string we show to the user; may be {@link #RELEASE} or + * {@link #CODENAME} if not a final release build. + */ + @NonNull public static final String RELEASE_OR_CODENAME = getString( + "ro.build.version.release_or_codename"); + + /** * The base OS build the product is based on. */ public static final String BASE_OS = SystemProperties.get("ro.build.version.base_os", ""); diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 947b0a1efebd..034e6a7a06c4 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -177,13 +177,6 @@ public class GraphicsEnvironment { } /** - * Check whether application is debuggable - */ - private static boolean isDebuggable(Context context) { - return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) > 0; - } - - /** * Check whether application is has set the manifest metadata for layer injection. */ private static boolean canInjectLayers(ApplicationInfo ai) { @@ -246,7 +239,7 @@ public class GraphicsEnvironment { // 2. ENABLE_GPU_DEBUG_LAYERS is true // 3. Package name is equal to GPU_DEBUG_APP - if (isDebuggable(context) || (getCanLoadSystemLibraries() == 1) || canInjectLayers(ai)) { + if (isDebuggable() || canInjectLayers(ai)) { final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); @@ -441,9 +434,7 @@ public class GraphicsEnvironment { * Check for ANGLE debug package, but only for apps that can load them (dumpable) */ private String getAngleDebugPackage(Context context, Bundle coreSettings) { - final boolean appIsDebuggable = isDebuggable(context); - final boolean deviceIsDebuggable = getCanLoadSystemLibraries() == 1; - if (appIsDebuggable || deviceIsDebuggable) { + if (isDebuggable()) { String debugPackage; if (coreSettings != null) { @@ -478,12 +469,8 @@ public class GraphicsEnvironment { * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for * debugging (PR_SET_DUMPABLE). */ - final boolean appIsDebuggable = isDebuggable(context); - final boolean deviceIsDebuggable = getCanLoadSystemLibraries() == 1; - if (!(appIsDebuggable || deviceIsDebuggable)) { - Log.v(TAG, "Skipping loading temporary rules file: " - + "appIsDebuggable = " + appIsDebuggable + ", " - + "adbRootEnabled = " + deviceIsDebuggable); + if (!isDebuggable()) { + Log.v(TAG, "Skipping loading temporary rules file"); return false; } @@ -742,7 +729,7 @@ public class GraphicsEnvironment { final boolean enablePrereleaseDriver = (ai.metaData != null && ai.metaData.getBoolean(METADATA_DEVELOPER_DRIVER_ENABLE)) - || getCanLoadSystemLibraries() == 1; + || isDebuggable(); // Priority for Game Driver settings global on confliction (Higher priority comes first): // 1. GAME_DRIVER_ALL_APPS @@ -918,7 +905,7 @@ public class GraphicsEnvironment { return ""; } - private static native int getCanLoadSystemLibraries(); + private static native boolean isDebuggable(); private static native void setLayerPaths(ClassLoader classLoader, String layerPaths); private static native void setDebugLayers(String layers); private static native void setDebugLayersGLES(String layers); diff --git a/core/java/android/os/IPullAtomCallback.aidl b/core/java/android/os/IPullAtomCallback.aidl new file mode 100644 index 000000000000..88d3c3e46ff5 --- /dev/null +++ b/core/java/android/os/IPullAtomCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.os.IPullAtomResultReceiver; + +/** + * Binder interface to pull atoms for the stats service. + * {@hide} + */ +interface IPullAtomCallback { + /** + * Initiate a request for a pull for an atom. + */ + void onPullAtom(int atomTag, IPullAtomResultReceiver resultReceiver); + +} diff --git a/core/java/android/os/IPullAtomResultReceiver.aidl b/core/java/android/os/IPullAtomResultReceiver.aidl new file mode 100644 index 000000000000..bfb35ff0c9d1 --- /dev/null +++ b/core/java/android/os/IPullAtomResultReceiver.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.util.StatsEvent; + +/** + * Binder interface to pull atoms for the stats service. + * {@hide} + */ +interface IPullAtomResultReceiver { + + /** + * Indicate that a pull request for an atom is complete. + */ + oneway void pullFinished(int atomTag, boolean success, in StatsEvent[] output); + +} diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl index 0751b964f85e..22a25374e064 100644 --- a/core/java/android/os/IStatsCompanionService.aidl +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -16,6 +16,7 @@ package android.os; +import android.os.IPullAtomCallback; import android.os.StatsDimensionsValue; import android.os.StatsLogEventWrapper; @@ -85,4 +86,8 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); + + /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */ + oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + in int[] additiveFields, IPullAtomCallback pullerCallback); } diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index e3f9326048d1..29871b6cf017 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -17,6 +17,7 @@ package android.os; import android.os.IStatsPullerCallback; +import android.os.IPullAtomCallback; import android.os.ParcelFileDescriptor; /** @@ -188,11 +189,19 @@ interface IStatsManager { * for the specified vendor atom tag. * * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS + * @deprecated please use registerPullAtomCallback. */ oneway void registerPullerCallback(int atomTag, IStatsPullerCallback pullerCallback, String packageName); /** + * Registers a puller callback function that, when invoked, pulls the data + * for the specified atom tag. + */ + oneway void registerPullAtomCallback(int uid, int atomTag, long coolDownNs, long timeoutNs, + in int[] additiveFields, IPullAtomCallback pullerCallback); + + /** * Unregisters a puller callback function for the given vendor atom. * * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS diff --git a/core/java/android/os/IStatsPullerCallback.aidl b/core/java/android/os/IStatsPullerCallback.aidl index 1684aeb0d666..c3e1e55dde06 100644 --- a/core/java/android/os/IStatsPullerCallback.aidl +++ b/core/java/android/os/IStatsPullerCallback.aidl @@ -19,6 +19,7 @@ package android.os; import android.os.StatsLogEventWrapper; /** + * DEPRECATED * Binder interface to pull atoms for the stats service. * {@hide} */ diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java index 2e673a857a93..10ef27952df4 100644 --- a/core/java/android/os/RemoteException.java +++ b/core/java/android/os/RemoteException.java @@ -16,7 +16,7 @@ package android.os; -import android.annotation.UnsupportedAppUsage; +import android.annotation.NonNull; import android.util.AndroidException; /** @@ -37,7 +37,15 @@ public class RemoteException extends AndroidException { super(message, cause, enableSuppression, writableStackTrace); } - /** {@hide} */ + /** + * Rethrow this as an unchecked runtime exception. + * <p> + * Apps making calls into other processes may end up persisting internal + * state or making security decisions based on the perceived success or + * failure of a call, or any default values returned. For this reason, we + * want to strongly throw when there was trouble with the transaction. + */ + @NonNull public RuntimeException rethrowAsRuntimeException() { throw new RuntimeException(this); } @@ -52,10 +60,8 @@ public class RemoteException extends AndroidException { * state or making security decisions based on the perceived success or * failure of a call, or any default values returned. For this reason, we * want to strongly throw when there was trouble with the transaction. - * - * @hide */ - @UnsupportedAppUsage + @NonNull public RuntimeException rethrowFromSystemServer() { if (this instanceof DeadObjectException) { throw new RuntimeException(new DeadSystemException()); diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index aa67d9779da4..01f9c7300fa1 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -104,7 +104,14 @@ public final class MediaStore { /** The authority for the media provider */ public static final String AUTHORITY = "media"; /** A content:// style uri to the authority for the media provider */ - public static final @NonNull Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + public static final @NonNull Uri AUTHORITY_URI = + Uri.parse("content://" + AUTHORITY); + + /** @hide */ + public static final String AUTHORITY_LEGACY = "media_legacy"; + /** @hide */ + public static final @NonNull Uri AUTHORITY_LEGACY_URI = + Uri.parse("content://" + AUTHORITY_LEGACY); /** * Synthetic volume name that provides a view of all content across the @@ -878,6 +885,16 @@ public final class MediaStore { } /** + * Rewrite the given {@link Uri} to point at + * {@link MediaStore#AUTHORITY_LEGACY}. + * + * @hide + */ + public static @NonNull Uri rewriteToLegacy(@NonNull Uri uri) { + return uri.buildUpon().authority(MediaStore.AUTHORITY_LEGACY).build(); + } + + /** * Common media metadata columns. */ public interface MediaColumns extends BaseColumns { @@ -3477,11 +3494,15 @@ public final class MediaStore { */ public static @NonNull String getVolumeName(@NonNull Uri uri) { final List<String> segments = uri.getPathSegments(); - if (uri.getAuthority().equals(AUTHORITY) && segments != null && segments.size() > 0) { - return segments.get(0); - } else { - throw new IllegalArgumentException("Missing volume name: " + uri); + switch (uri.getAuthority()) { + case AUTHORITY: + case AUTHORITY_LEGACY: { + if (segments != null && segments.size() > 0) { + return segments.get(0); + } + } } + throw new IllegalArgumentException("Missing volume name: " + uri); } /** {@hide} */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 4773d989ec77..ac53f1b27680 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8375,7 +8375,6 @@ public final class Settings { INSTANT_APP_SETTINGS.add(ANDROID_ID); - INSTANT_APP_SETTINGS.add(PACKAGE_VERIFIER_USER_CONSENT); INSTANT_APP_SETTINGS.add(ALLOW_MOCK_LOCATION); } diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java index 2d40ec480379..bc6a9e848e2a 100644 --- a/core/java/android/service/euicc/EuiccService.java +++ b/core/java/android/service/euicc/EuiccService.java @@ -546,7 +546,7 @@ public abstract class EuiccService extends Service { * @see android.telephony.euicc.EuiccManager#eraseSubscriptions * * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase - * and use {@link #onEraseSubscriptionsWithOptions(int, int)} instead + * and use {@link #onEraseSubscriptions(int, int)} instead */ @Deprecated public abstract int onEraseSubscriptions(int slotId); @@ -563,7 +563,7 @@ public abstract class EuiccService extends Service { * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#eraseSubscriptionsWithOptions */ - public int onEraseSubscriptionsWithOptions(int slotIndex, @ResetOption int options) { + public int onEraseSubscriptions(int slotIndex, @ResetOption int options) { throw new UnsupportedOperationException( "This method must be overridden to enable the ResetOption parameter"); } @@ -809,8 +809,7 @@ public abstract class EuiccService extends Service { mExecutor.execute(new Runnable() { @Override public void run() { - int result = EuiccService.this.onEraseSubscriptionsWithOptions( - slotIndex, options); + int result = EuiccService.this.onEraseSubscriptions(slotIndex, options); try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/core/java/android/util/StatsEvent.aidl b/core/java/android/util/StatsEvent.aidl new file mode 100644 index 000000000000..deac873b0a04 --- /dev/null +++ b/core/java/android/util/StatsEvent.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +parcelable StatsEvent cpp_header "android/util/StatsEvent.h"; diff --git a/core/java/android/util/StatsEvent.java b/core/java/android/util/StatsEvent.java index d6ffd38e98e8..10c9d87dfbe8 100644 --- a/core/java/android/util/StatsEvent.java +++ b/core/java/android/util/StatsEvent.java @@ -20,6 +20,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; @@ -42,7 +44,7 @@ import com.android.internal.annotations.VisibleForTesting; * </pre> * @hide **/ -public final class StatsEvent { +public final class StatsEvent implements Parcelable { private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag. @@ -631,4 +633,39 @@ public final class StatsEvent { return 0; } } + + /** + * Boilerplate for Parcel. + * + * @hide + */ + public static final @NonNull Parcelable.Creator<StatsEvent> CREATOR = + new Parcelable.Creator<StatsEvent>() { + public StatsEvent createFromParcel(Parcel in) { + // Purposefully leaving this method not implemented. + throw new RuntimeException("Not implemented"); + } + + public StatsEvent[] newArray(int size) { + // Purposefully leaving this method not implemented. + throw new RuntimeException("Not implemented"); + } + }; + + /** + * @hide + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mAtomId); + out.writeInt(getNumBytes()); + out.writeByteArray(getBytes()); + } + + /** + * Boilerplate for Parcel. + */ + public int describeContents() { + return 0; + } + } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b815c641ff25..3251127397b6 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -90,7 +90,8 @@ public final class SurfaceControl implements Parcelable { Rect sourceCrop, int width, int height, boolean useIdentityTransform, int rotation, boolean captureSecureLayers); private static native ScreenshotGraphicBuffer nativeCaptureLayers(IBinder displayToken, - long layerObject, Rect sourceCrop, float frameScale, long[] excludeLayerObjects); + long layerObject, Rect sourceCrop, float frameScale, long[] excludeLayerObjects, + int format); private static native long nativeMirrorSurface(long mirrorOfObject); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); @@ -1869,8 +1870,27 @@ public final class SurfaceControl implements Parcelable { */ public static ScreenshotGraphicBuffer captureLayers(SurfaceControl layer, Rect sourceCrop, float frameScale) { + return captureLayers(layer, sourceCrop, frameScale, PixelFormat.RGBA_8888); + } + + /** + * Captures a layer and its children and returns a {@link GraphicBuffer} with the content. + * + * @param layer The root layer to capture. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * @param format The desired pixel format of the returned buffer. + * + * @return Returns a GraphicBuffer that contains the layer capture. + * @hide + */ + public static ScreenshotGraphicBuffer captureLayers(SurfaceControl layer, Rect sourceCrop, + float frameScale, int format) { final IBinder displayToken = SurfaceControl.getInternalDisplayToken(); - return nativeCaptureLayers(displayToken, layer.mNativeObject, sourceCrop, frameScale, null); + return nativeCaptureLayers(displayToken, layer.mNativeObject, sourceCrop, frameScale, null, + format); } /** @@ -1885,7 +1905,7 @@ public final class SurfaceControl implements Parcelable { nativeExcludeObjects[i] = exclude[i].mNativeObject; } return nativeCaptureLayers(displayToken, layer.mNativeObject, sourceCrop, frameScale, - nativeExcludeObjects); + nativeExcludeObjects, PixelFormat.RGBA_8888); } /** diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index fd3cd42b07a1..da43eddeb0bf 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -277,7 +277,7 @@ public class RuntimeInit { result.append(System.getProperty("java.vm.version")); // such as 1.1.0 result.append(" (Linux; U; Android "); - String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" + String version = Build.VERSION.RELEASE_OR_CODENAME; // "1.0" or "3.4b5" result.append(version.length() > 0 ? version : "1.0"); // add the model for the release build diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index 7582cae1a761..4f3f283859b4 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -23,8 +23,8 @@ namespace { -int getCanLoadSystemLibraries_native() { - return android::GraphicsEnv::getInstance().getCanLoadSystemLibraries(); +bool isDebuggable_native() { + return android::GraphicsEnv::getInstance().isDebuggable(); } void setDriverPathAndSphalLibraries_native(JNIEnv* env, jobject clazz, jstring path, @@ -94,7 +94,7 @@ void hintActivityLaunch_native(JNIEnv* env, jobject clazz) { } const JNINativeMethod g_methods[] = { - { "getCanLoadSystemLibraries", "()I", reinterpret_cast<void*>(getCanLoadSystemLibraries_native) }, + { "isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native) }, { "setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native) }, { "setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V", reinterpret_cast<void*>(setGpuStats_native) }, { "setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native) }, diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 3c0971b9ec1a..5c4dc2335822 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -556,7 +556,9 @@ static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin assetmanager->SetConfiguration(configuration); } -static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) { +static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr, + jboolean includeOverlays, + jboolean includeLoaders) { ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr)); jobject sparse_array = @@ -567,6 +569,10 @@ static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/ return nullptr; } + // Optionally exclude overlays and loaders. + uint64_t exclusion_flags = ((includeOverlays) ? 0U : PROPERTY_OVERLAY) + | ((includeLoaders) ? 0U : PROPERTY_LOADER); + assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) -> bool { jstring jpackage_name = env->NewStringUTF(package_name.c_str()); if (jpackage_name == nullptr) { @@ -577,7 +583,8 @@ static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/ env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id), jpackage_name); return true; - }); + }, exclusion_flags); + return sparse_array; } @@ -1591,7 +1598,7 @@ static const JNINativeMethod gAssetManagerMethods[] = { {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V", (void*)NativeSetConfiguration}, - {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;", + {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;", (void*)NativeGetAssignedPackageIdentifiers}, // AssetManager file methods. diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index c807e90d5ad4..f8a2744fdcb0 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -274,7 +274,7 @@ static jobject nativeScreenshot(JNIEnv* env, jclass clazz, static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject displayTokenObj, jlong layerObject, jobject sourceCropObj, jfloat frameScale, - jlongArray excludeObjectArray) { + jlongArray excludeObjectArray, jint format) { auto layer = reinterpret_cast<SurfaceControl *>(layerObject); if (layer == NULL) { @@ -311,8 +311,9 @@ static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject displayTok dataspace = pickDataspaceFromColorMode(colorMode); } status_t res = ScreenshotClient::captureChildLayers(layer->getHandle(), dataspace, - ui::PixelFormat::RGBA_8888, sourceCrop, - excludeHandles, frameScale, &buffer); + static_cast<ui::PixelFormat>(format), + sourceCrop, excludeHandles, frameScale, + &buffer); if (res != NO_ERROR) { return NULL; } @@ -1386,7 +1387,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeScreenshot }, {"nativeCaptureLayers", "(Landroid/os/IBinder;JLandroid/graphics/Rect;" - "F[J)" + "F[JI)" "Landroid/view/SurfaceControl$ScreenshotGraphicBuffer;", (void*)nativeCaptureLayers }, {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V", diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ce2717bb2779..8f084abe71a7 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -126,12 +126,11 @@ message ActivityRecordProto { optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1 [deprecated=true]; optional .com.android.server.wm.IdentifierProto identifier = 2; optional string state = 3; - optional bool visible_requested = 4; + optional bool visible = 4; optional bool front_of_task = 5; optional int32 proc_id = 6; optional bool translucent = 7; optional .com.android.server.wm.AppWindowTokenProto app_window_token = 8; - optional bool visible = 9; } message KeyguardControllerProto { diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 23566a6df847..653d381e2960 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -235,8 +235,8 @@ message AppWindowTokenProto { optional WindowContainerThumbnailProto thumbnail = 6; optional bool fills_parent = 7; optional bool app_stopped = 8; - optional bool visible_requested = 9; - optional bool client_visible = 10; + optional bool hidden_requested = 9; + optional bool client_hidden = 10; optional bool defer_hiding_client = 11; optional bool reported_drawn = 12; optional bool reported_visible = 13; @@ -248,9 +248,8 @@ message AppWindowTokenProto { optional IdentifierProto starting_window = 19; optional bool starting_displayed = 20; optional bool starting_moved = 21; - optional bool visible_set_from_transferred_starting_window = 22; + optional bool hidden_set_from_transferred_starting_window = 22; repeated .android.graphics.RectProto frozen_bounds = 23; - optional bool visible = 24; } /* represents WindowToken */ @@ -260,6 +259,7 @@ message WindowTokenProto { optional WindowContainerProto window_container = 1; optional int32 hash_code = 2; repeated WindowStateProto windows = 3; + optional bool hidden = 4; optional bool waiting_to_show = 5; optional bool paused = 6; } diff --git a/core/proto/android/stats/textclassifier/Android.bp b/core/proto/android/stats/textclassifier/Android.bp new file mode 100644 index 000000000000..bf9022711206 --- /dev/null +++ b/core/proto/android/stats/textclassifier/Android.bp @@ -0,0 +1,23 @@ +// Copyright (C) 2019 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. + +java_library_static { + name: "textclassifierprotoslite", + proto: { + type: "lite", + }, + srcs: [ + "*.proto", + ], +}
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index efa42e5735e1..cb0b5993c420 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2652,6 +2652,9 @@ <!-- The amount to scale fullscreen snapshots for Overview and snapshot starting windows. --> <item name="config_fullTaskSnapshotScale" format="float" type="dimen">1.0</item> + <!-- Feature flag to store TaskSnapshot in 16 bit pixel format to save memory. --> + <bool name="config_use16BitTaskSnapshotPixelFormat">false</bool> + <!-- Determines whether recent tasks are provided to the user. Default device has recents property. If this is false, then the following recents config flags are ignored. --> <bool name="config_hasRecents">true</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 91a8ba4ca3d2..28809daeaad8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -356,6 +356,7 @@ <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/> <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/> <java-symbol type="dimen" name="config_fullTaskSnapshotScale" /> + <java-symbol type="bool" name="config_use16BitTaskSnapshotPixelFormat" /> <java-symbol type="bool" name="config_lowRamTaskSnapshotsAndRecents" /> <java-symbol type="bool" name="config_hasRecents" /> <java-symbol type="string" name="config_recentsComponentName" /> diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java index decc76869a53..2295eb989108 100644 --- a/core/tests/coretests/src/android/os/BuildTest.java +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -60,7 +60,7 @@ public class BuildTest extends TestCase { assertNotEmpty("BRAND", Build.BRAND); assertNotEmpty("MODEL", Build.MODEL); assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL); - assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE); + assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE_OR_CODENAME); assertNotEmpty("TYPE", Build.TYPE); Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty. assertNotEmpty("FINGERPRINT", Build.FINGERPRINT); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 77b0dba67be5..d2ce4e0a03b9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -95,6 +95,9 @@ applications that come with the platform <privapp-permissions package="com.android.mtp"> <permission name="android.permission.ACCESS_MTP"/> <permission name="android.permission.MANAGE_USB"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.WRITE_MEDIA_STORAGE"/> </privapp-permissions> <privapp-permissions package="com.android.musicfx"> @@ -207,6 +210,7 @@ applications that come with the platform <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> <permission name="android.permission.WATCH_APPOPS"/> + <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 3c89bfd43f17..753f8a05cbe3 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -55,12 +55,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "-2006946193": { - "message": "setClientVisible: %s clientVisible=%b Callers=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-2002500255": { "message": "Defer removing snapshot surface in %dms", "level": "VERBOSE", @@ -79,18 +73,18 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1976550065": { - "message": "commitVisibility: %s: visible=%b visibleRequested=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-1963461591": { "message": "Removing %s from %s", "level": "VERBOSE", "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1958209312": { + "message": "Clear freezing of %s: hidden=%b freezing=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-1953668890": { "message": "Can't start recents animation, nextAppTransition=%s", "level": "DEBUG", @@ -313,6 +307,12 @@ "group": "WM_DEBUG_ADD_REMOVE", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-1456549051": { + "message": "setClientHidden: %s clientHidden=%b Callers=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-1455600136": { "message": "Attempted to add Dream window with unknown token %s. Aborting.", "level": "WARN", @@ -547,6 +547,12 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-931184679": { + "message": "Changing app %s hidden=%b performLayout=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "-928291778": { "message": "applyAnimation: anim=%s nextAppTransition=%d transit=%s Callers=%s", "level": "VERBOSE", @@ -835,12 +841,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, - "-374767836": { - "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "-371630969": { "message": "New wallpaper target=%s, oldWallpaper=%s, openingApps=%s, closingApps=%s", "level": "VERBOSE", @@ -1207,6 +1207,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "358613119": { + "message": "setAppVisibility(%s, visible=%b): %s hidden=%b hiddenRequested=%b Callers=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "371641947": { "message": "Window Manager Crash %s", "level": "WTF", @@ -1267,12 +1273,6 @@ "group": "WM_DEBUG_SCREEN_ON", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "466506262": { - "message": "Clear freezing of %s: visible=%b freezing=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "474000473": { "message": "No stack above target stack=%s", "level": "DEBUG", @@ -1489,12 +1489,6 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, - "841702299": { - "message": "Changing app %s visible=%b performLayout=%b", - "level": "VERBOSE", - "group": "WM_DEBUG_APP_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "845234215": { "message": "App is requesting an orientation, return %d for display id=%d", "level": "VERBOSE", @@ -1507,6 +1501,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "857751535": { + "message": "commitVisibility: %s: hidden=%b hiddenRequested=%b", + "level": "VERBOSE", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "873914452": { "message": "goodToGo()", "level": "DEBUG", @@ -1903,12 +1903,6 @@ "group": "WM_DEBUG_STARTING_WINDOW", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "1746778201": { - "message": "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s", - "level": "INFO", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/ActivityRecord.java" - }, "1747941491": { "message": "SURFACE controller=%s alpha=%f matrix=[%f*%f,%f*%f][%f*%f,%f*%f]: %s", "level": "INFO", @@ -1999,6 +1993,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "1966564525": { + "message": "Set freezing of %s: hidden=%b freezing=%b hiddenRequested=%b. %s", + "level": "INFO", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/ActivityRecord.java" + }, "1984470582": { "message": "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d", "level": "DEBUG", diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk index 99dfd0a6d455..2392b333c587 100644 --- a/data/sounds/AudioPackage11.mk +++ b/data/sounds/AudioPackage11.mk @@ -32,7 +32,7 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Lock.ogg \ $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Unlock.ogg \ $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Trusted.ogg \ - $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/ChargingStarted.ogg \. + $(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/ChargingStarted.ogg \ $(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/InCallNotification.ogg \ $(LOCAL_PATH)/effects/material/ogg/WirelessChargingStarted_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/WirelessChargingStarted.ogg \ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/notifications/Adara.ogg \ diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 538319c3f1d7..a7e17d13c9e1 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -55,8 +55,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import javax.security.auth.x500.X500Principal; @@ -811,27 +811,22 @@ public final class KeyChain { throw new NullPointerException("context == null"); } ensureNotOnMainThread(context); - final BlockingQueue<IKeyChainService> q = new LinkedBlockingQueue<IKeyChainService>(1); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final AtomicReference<IKeyChainService> keyChainService = new AtomicReference<>(); ServiceConnection keyChainServiceConnection = new ServiceConnection() { volatile boolean mConnectedAtLeastOnce = false; @Override public void onServiceConnected(ComponentName name, IBinder service) { if (!mConnectedAtLeastOnce) { mConnectedAtLeastOnce = true; - try { - q.put(IKeyChainService.Stub.asInterface(Binder.allowBlocking(service))); - } catch (InterruptedException e) { - // will never happen, since the queue starts with one available slot - } + keyChainService.set( + IKeyChainService.Stub.asInterface(Binder.allowBlocking(service))); + countDownLatch.countDown(); } } @Override public void onBindingDied(ComponentName name) { if (!mConnectedAtLeastOnce) { mConnectedAtLeastOnce = true; - try { - q.put(null); - } catch (InterruptedException e) { - // will never happen, since the queue starts with one available slot - } + countDownLatch.countDown(); } } @Override public void onServiceDisconnected(ComponentName name) {} @@ -843,7 +838,8 @@ public final class KeyChain { intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user)) { throw new AssertionError("could not bind to KeyChainService"); } - IKeyChainService service = q.take(); + countDownLatch.await(); + IKeyChainService service = keyChainService.get(); if (service != null) { return new KeyChainConnection(context, keyChainServiceConnection, service); } else { diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 16dbbf61351a..18934fd55bad 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -43,20 +43,22 @@ static const std::string kResourcesArsc("resources.arsc"); ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time, - bool for_loader) + package_property_t property_flags) : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time), - for_loader_(for_loader) { + property_flags_(property_flags) { } std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system, bool for_loader) { - return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/, - for_loader); + package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) | + (for_loader ? PROPERTY_LOADER : 0U); + return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags); } std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path, bool system) { - return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, true /*load_as_shared_library*/); + package_property_t flags = PROPERTY_DYNAMIC | (system ? PROPERTY_SYSTEM : 0U); + return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags); } std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, @@ -74,27 +76,33 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap LOG(ERROR) << "failed to load IDMAP " << idmap_path; return {}; } - return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), std::move(idmap_asset), - std::move(loaded_idmap), system, true /*load_as_shared_library*/); + + return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), + std::move(idmap_asset), + std::move(loaded_idmap), + PROPERTY_OVERLAY | (system ? PROPERTY_SYSTEM : 0U)); } std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, const std::string& friendly_name, bool system, bool force_shared_lib, bool for_loader) { + package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) | + (force_shared_lib ? PROPERTY_DYNAMIC : 0U) | + (for_loader ? PROPERTY_LOADER : 0U); return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, - system, force_shared_lib, for_loader); + flags); } std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path, bool for_loader) { - return LoadArscImpl({} /*fd*/, path, for_loader); + return LoadArscImpl({} /*fd*/, path, for_loader ? PROPERTY_LOADER : 0U); } std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd, const std::string& friendly_name, bool for_loader) { - return LoadArscImpl(std::move(fd), friendly_name, for_loader); + return LoadArscImpl(std::move(fd), friendly_name, for_loader ? PROPERTY_LOADER : 0U); } std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) { @@ -120,8 +128,7 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) { std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library, - bool for_loader) { + std::unique_ptr<const LoadedIdmap> loaded_idmap, package_property_t property_flags) { ::ZipArchiveHandle unmanaged_handle; int32_t result; if (fd >= 0) { @@ -141,7 +148,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( // Wrap the handle in a unique_ptr so it gets automatically closed. std::unique_ptr<ApkAssets> - loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, for_loader)); + loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, property_flags)); // Find the resource table. ::ZipEntry entry; @@ -170,9 +177,8 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( const StringPiece data( reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)), loaded_apk->resources_asset_->getLength()); - loaded_apk->loaded_arsc_ = - LoadedArsc::Load(data, loaded_apk->loaded_idmap_.get(), system, load_as_shared_library, - for_loader); + loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_apk->loaded_idmap_.get(), + property_flags); if (loaded_apk->loaded_arsc_ == nullptr) { LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; @@ -184,7 +190,7 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd, const std::string& path, - bool for_loader) { + package_property_t property_flags) { std::unique_ptr<Asset> resources_asset; if (fd >= 0) { @@ -201,13 +207,14 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd, time_t last_mod_time = getFileModDate(path.c_str()); - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, path, last_mod_time, for_loader)); + std::unique_ptr<ApkAssets> loaded_apk( + new ApkAssets(nullptr, path, last_mod_time, property_flags)); loaded_apk->resources_asset_ = std::move(resources_asset); const StringPiece data( reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)), loaded_apk->resources_asset_->getLength()); - loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, false, false, for_loader); + loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, property_flags); if (loaded_apk->loaded_arsc_ == nullptr) { LOG(ERROR) << "Failed to load '" << kResourcesArsc << path; return {}; @@ -320,8 +327,8 @@ bool ApkAssets::ForEachFile(const std::string& root_path, } bool ApkAssets::IsUpToDate() const { - // Loaders are invalidated by the app, not the system, so assume up to date - if (for_loader_) { + if (IsLoader()) { + // Loaders are invalidated by the app, not the system, so assume up to date. return true; } diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 2b69c923597f..773353d32d51 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -44,8 +44,8 @@ static bool compare_overlay_entries(const Idmap_overlay_entry& e1, const uint32_ } OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap) - : data_header_(loaded_idmap->data_header_), - idmap_string_pool_(loaded_idmap->string_pool_.get()) { }; + : data_header_(loaded_idmap->data_header_), + idmap_string_pool_(loaded_idmap->string_pool_.get()) { }; OverlayStringPool::~OverlayStringPool() { uninit(); @@ -188,11 +188,12 @@ LoadedIdmap::LoadedIdmap(const Idmap_header* header, const Idmap_data_header* data_header, const Idmap_target_entry* target_entries, const Idmap_overlay_entry* overlay_entries, - ResStringPool* string_pool) : header_(header), - data_header_(data_header), - target_entries_(target_entries), - overlay_entries_(overlay_entries), - string_pool_(string_pool) { + ResStringPool* string_pool) + : header_(header), + data_header_(data_header), + target_entries_(target_entries), + overlay_entries_(overlay_entries), + string_pool_(string_pool) { size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path), arraysize(header_->overlay_path)); @@ -264,7 +265,7 @@ std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_da } } - // Can't use make_unique because LoadedImpl constructor is private. + // Can't use make_unique because LoadedIdmap constructor is private. std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>( new LoadedIdmap(header, data_header, target_entries, overlay_entries, idmap_string_pool.release())); diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index c8962416d082..e35c0249fbdf 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -397,9 +397,7 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { } std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, - bool system, - bool load_as_shared_library, - bool for_loader) { + package_property_t property_flags) { ATRACE_NAME("LoadedPackage::Load"); std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage()); @@ -413,17 +411,24 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, return {}; } - loaded_package->system_ = system; + if ((property_flags & PROPERTY_SYSTEM) != 0) { + loaded_package->property_flags_ |= PROPERTY_SYSTEM; + } - loaded_package->package_id_ = dtohl(header->id); - if (loaded_package->package_id_ == 0 || - (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) { - // Package ID of 0 means this is a shared library. - loaded_package->dynamic_ = true; + if ((property_flags & PROPERTY_LOADER) != 0) { + loaded_package->property_flags_ |= PROPERTY_LOADER; } - if (for_loader) { - loaded_package->custom_loader_ = true; + if ((property_flags & PROPERTY_OVERLAY) != 0) { + // Overlay resources must have an exclusive resource id space for referencing internal + // resources. + loaded_package->property_flags_ |= PROPERTY_OVERLAY | PROPERTY_DYNAMIC; + } + + loaded_package->package_id_ = dtohl(header->id); + if (loaded_package->package_id_ == 0 || + (loaded_package->package_id_ == kAppPackageId && (property_flags & PROPERTY_DYNAMIC) != 0)) { + loaded_package->property_flags_ |= PROPERTY_DYNAMIC; } if (header->header.headerSize >= sizeof(ResTable_package)) { @@ -677,7 +682,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, - bool load_as_shared_library, bool for_loader) { + package_property_t property_flags) { const ResTable_header* header = chunk.header<ResTable_header>(); if (header == nullptr) { LOG(ERROR) << "RES_TABLE_TYPE too small."; @@ -720,7 +725,7 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, packages_seen++; std::unique_ptr<const LoadedPackage> loaded_package = - LoadedPackage::Load(child_chunk, system_, load_as_shared_library, for_loader); + LoadedPackage::Load(child_chunk, property_flags); if (!loaded_package) { return false; } @@ -744,24 +749,18 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, const LoadedIdmap* loaded_idmap, - bool system, - bool load_as_shared_library, - bool for_loader) { + package_property_t property_flags) { ATRACE_NAME("LoadedArsc::Load"); // Not using make_unique because the constructor is private. std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc()); - loaded_arsc->system_ = system; ChunkIterator iter(data.data(), data.size()); while (iter.HasNext()) { const Chunk chunk = iter.Next(); switch (chunk.type()) { case RES_TABLE_TYPE: - if (!loaded_arsc->LoadTable(chunk, - loaded_idmap, - load_as_shared_library, - for_loader)) { + if (!loaded_arsc->LoadTable(chunk, loaded_idmap, property_flags)) { return {}; } break; diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 20472872263e..af802b0e50b9 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -76,10 +76,10 @@ class ApkAssets { // Takes ownership of the file descriptor. static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd, const std::string& friendly_name, - bool resource_loader = false); + bool for_loader = false); // Creates a totally empty ApkAssets with no resources table and no file entries. - static std::unique_ptr<const ApkAssets> LoadEmpty(bool resource_loader = false); + static std::unique_ptr<const ApkAssets> LoadEmpty(bool for_loader = false); std::unique_ptr<Asset> Open(const std::string& path, Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; @@ -100,12 +100,12 @@ class ApkAssets { return loaded_idmap_.get(); } - inline bool IsOverlay() const { - return idmap_asset_.get() != nullptr; + inline bool IsLoader() const { + return (property_flags_ & PROPERTY_LOADER) != 0; } - inline bool IsLoader() const { - return for_loader_; + inline bool IsOverlay() const { + return (property_flags_ & PROPERTY_OVERLAY) != 0; } bool IsUpToDate() const; @@ -119,24 +119,23 @@ class ApkAssets { static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> loaded_idmap, - bool system, bool load_as_shared_library, - bool resource_loader = false); + package_property_t property_flags); static std::unique_ptr<const ApkAssets> LoadArscImpl(base::unique_fd fd, const std::string& path, - bool resource_loader = false); + package_property_t property_flags); ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time, - bool for_loader = false); + package_property_t property_flags); using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>; ZipArchivePtr zip_handle_; const std::string path_; time_t last_mod_time_; - bool for_loader_; + package_property_t property_flags_ = 0U; std::unique_ptr<Asset> resources_asset_; std::unique_ptr<Asset> idmap_asset_; std::unique_ptr<const LoadedArsc> loaded_arsc_; diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 20e40234b418..00cbbcad56e6 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -263,10 +263,13 @@ class AssetManager2 { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); - void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func) const { + void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func, + package_property_t excluded_property_flags = 0U) const { for (const PackageGroup& package_group : package_groups_) { - if (!func(package_group.packages_.front().loaded_package_->GetPackageName(), - package_group.dynamic_ref_table->mAssignedPackageId)) { + const auto loaded_package = package_group.packages_.front().loaded_package_; + if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U + && !func(loaded_package->GetPackageName(), + package_group.dynamic_ref_table->mAssignedPackageId)) { return; } } diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index ba1beaa7827c..6cbda07b6950 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -69,6 +69,14 @@ struct TypeSpec { } }; +using package_property_t = uint32_t; +enum : package_property_t { + PROPERTY_DYNAMIC = 1, + PROPERTY_LOADER = 2, + PROPERTY_OVERLAY = 4, + PROPERTY_SYSTEM = 8, +}; + // TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of // ResTable_type pointers. // TypeSpecPtr is a managed pointer that knows how to delete itself. @@ -131,9 +139,8 @@ class LoadedPackage { return iterator(this, resource_ids_.size() + 1, 0); } - static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk, bool system, - bool load_as_shared_library, - bool load_as_custom_loader); + static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk, + package_property_t property_flags); ~LoadedPackage(); @@ -170,17 +177,26 @@ class LoadedPackage { // Returns true if this package is dynamic (shared library) and needs to have an ID assigned. inline bool IsDynamic() const { - return dynamic_; + return (property_flags_ & PROPERTY_DYNAMIC) != 0; + } + + // Returns true if this package is a Runtime Resource Overlay. + inline bool IsOverlay() const { + return (property_flags_ & PROPERTY_OVERLAY) != 0; } // Returns true if this package originates from a system provided resource. inline bool IsSystem() const { - return system_; + return (property_flags_ & PROPERTY_SYSTEM) != 0; } - // Returns true if this package is a custom loader and should behave like an overlay + // Returns true if this package is a custom loader and should behave like an overlay. inline bool IsCustomLoader() const { - return custom_loader_; + return (property_flags_ & PROPERTY_LOADER) != 0; + } + + inline package_property_t GetPropertyFlags() const { + return property_flags_; } // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a @@ -248,12 +264,10 @@ class LoadedPackage { ResStringPool type_string_pool_; ResStringPool key_string_pool_; std::string package_name_; + bool defines_overlayable_ = false; int package_id_ = -1; int type_id_offset_ = 0; - bool dynamic_ = false; - bool system_ = false; - bool custom_loader_ = false; - bool defines_overlayable_ = false; + package_property_t property_flags_ = 0U; ByteBucketArray<TypeSpecPtr> type_specs_; ByteBucketArray<uint32_t> resource_ids_; @@ -274,9 +288,7 @@ class LoadedArsc { // ID. static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data, const LoadedIdmap* loaded_idmap = nullptr, - bool system = false, - bool load_as_shared_library = false, - bool for_loader = false); + package_property_t property_flags = 0U); // Create an empty LoadedArsc. This is used when an APK has no resources.arsc. static std::unique_ptr<const LoadedArsc> CreateEmpty(); @@ -296,28 +308,15 @@ class LoadedArsc { return packages_; } - // Returns true if this is a system provided resource. - inline bool IsSystem() const { - return system_; - } - private: DISALLOW_COPY_AND_ASSIGN(LoadedArsc); LoadedArsc() = default; - bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library, - bool for_loader); - - static std::unique_ptr<const LoadedArsc> LoadData(std::unique_ptr<LoadedArsc>& loaded_arsc, - const char* data, - size_t length, - const LoadedIdmap* loaded_idmap = nullptr, - bool load_as_shared_library = false, - bool for_loader = false); + bool LoadTable( + const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags); std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>(); std::vector<std::unique_ptr<const LoadedPackage>> packages_; - bool system_ = false; }; } // namespace android diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index 82dd33523c75..8615069e98dd 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -144,8 +144,7 @@ TEST(LoadedArscTest, LoadAppAsSharedLibrary) { "resources.arsc", &contents)); std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(StringPiece(contents), nullptr /* loaded_idmap */, false /*system*/, - true /*load_as_shared_library*/); + LoadedArsc::Load(StringPiece(contents), nullptr /* loaded_idmap */, PROPERTY_DYNAMIC); ASSERT_THAT(loaded_arsc, NotNull()); const auto& packages = loaded_arsc->GetPackages(); @@ -227,9 +226,7 @@ TEST(LoadedArscTest, LoadOverlayable) { ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlayable/overlayable.apk", "resources.arsc", &contents)); - std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(StringPiece(contents), nullptr /* loaded_idmap */, false /*system*/, - false /*load_as_shared_library*/); + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); ASSERT_THAT(loaded_arsc, NotNull()); const LoadedPackage* package = loaded_arsc->GetPackageById( @@ -346,7 +343,7 @@ TEST(LoadedArscTest, LoadCustomLoader) { asset->getLength()); std::unique_ptr<const LoadedArsc> loaded_arsc = - LoadedArsc::Load(data, nullptr, false, false, true); + LoadedArsc::Load(data, nullptr, PROPERTY_LOADER); ASSERT_THAT(loaded_arsc, NotNull()); const LoadedPackage* package = diff --git a/libs/services/Android.bp b/libs/services/Android.bp index 1b9939d9a598..b0fad57dfd29 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -22,6 +22,7 @@ cc_library_shared { "src/os/DropBoxManager.cpp", "src/os/StatsDimensionsValue.cpp", "src/os/StatsLogEventWrapper.cpp", + "src/util/StatsEvent.cpp", ], shared_libs: [ diff --git a/libs/services/include/android/util/StatsEvent.h b/libs/services/include/android/util/StatsEvent.h new file mode 100644 index 000000000000..48631174f7dd --- /dev/null +++ b/libs/services/include/android/util/StatsEvent.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 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. + */ +#ifndef STATS_EVENT_H +#define STATS_EVENT_H + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <vector> + +namespace android { +namespace util { +class StatsEvent : public android::Parcelable { + public: + StatsEvent(); + + StatsEvent(StatsEvent&& in) = default; + + android::status_t writeToParcel(android::Parcel* out) const; + + android::status_t readFromParcel(const android::Parcel* in); + + private: + int mAtomTag; + std::vector<uint8_t> mBuffer; +}; +} // Namespace util +} // Namespace android + +#endif // STATS_ EVENT_H
\ No newline at end of file diff --git a/libs/services/src/util/StatsEvent.cpp b/libs/services/src/util/StatsEvent.cpp new file mode 100644 index 000000000000..8b8579167b00 --- /dev/null +++ b/libs/services/src/util/StatsEvent.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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. + */ +#include <android/util/StatsEvent.h> + +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/Status.h> +#include <vector> + +using android::Parcel; +using android::Parcelable; +using android::status_t; +using std::vector; + +namespace android { +namespace util { + +StatsEvent::StatsEvent(){}; + +status_t StatsEvent::writeToParcel(Parcel* out) const { + // Implement me if desired. We don't currently use this. + ALOGE("Cannot do c++ StatsEvent.writeToParcel(); it is not implemented."); + (void)out; // To prevent compile error of unused parameter 'out' + return UNKNOWN_ERROR; +}; + +status_t StatsEvent::readFromParcel(const Parcel* in) { + status_t res = OK; + if (in == NULL) { + ALOGE("statsd received parcel argument was NULL."); + return BAD_VALUE; + } + if ((res = in->readInt32(&mAtomTag)) != OK) { + ALOGE("statsd could not read atom tag from parcel"); + return res; + } + if ((res = in->readByteVector(&mBuffer)) != OK) { + ALOGE("statsd could not read buffer from parcel"); + return res; + } + return NO_ERROR; +}; + +} // Namespace util +} // Namespace android diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 39a7e25cb68b..5030e6625d5d 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2642,10 +2642,47 @@ public class LocationManager { @Override public void onRemoved() { - unregister(); - synchronized (mListeners) { - mListeners.remove(mListener, this); + // TODO: onRemoved is necessary to GC hanging listeners, but introduces some interesting + // broken edge cases. luckily these edge cases are quite unlikely. consider the + // following interleaving for instance: + // 1) client adds single shot location request (A) + // 2) client gets removal callback, and schedules it for execution + // 3) client replaces single shot request with a different location request (B) + // 4) prior removal callback is executed, removing location request (B) incorrectly + // what's needed is a way to identify which listener a callback belongs to. currently + // we reuse the same transport object for the same listeners (so that we don't leak + // transport objects on the server side). there seem to be two solutions: + // 1) when reregistering a request, first unregister the current transport, then + // register with a new transport object (never reuse transport objects) - the + // downside is that this breaks the server's knowledge that the request is the + // same object, and thus breaks optimizations such as reusing the same transport + // state. + // 2) pass some other type of marker in addition to the transport (for example an + // incrementing integer representing the transport "version"), and pass this + // marker back into callbacks so that each callback knows which transport + // "version" it belongs to and can not execute itself if the version does not + // match. + // (1) seems like the preferred solution as it's simpler to implement and the above + // mentioned server optimizations are not terribly important (they can be bypassed by + // clients that use a new listener every time anyways). + + Executor currentExecutor = mExecutor; + if (currentExecutor == null) { + // we've already been unregistered, no work to do anyways + return; } + + // must be executed on the same executor so callback execution cannot be reordered + currentExecutor.execute(() -> { + if (currentExecutor != mExecutor) { + return; + } + + unregister(); + synchronized (mListeners) { + mListeners.remove(mListener, this); + } + }); } private void locationCallbackFinished() { diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index d6a4ea7cb39f..da8e402ffb9f 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -237,11 +237,56 @@ public class ExifInterface { public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType"; /** Type is String. */ public static final String TAG_OECF = "OECF"; - /** Type is String. {@hide} */ + /** + * <p>A tag used to record the offset from UTC (the time difference from Universal Time + * Coordinated including daylight saving time) of the time of DateTime tag. The format when + * recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When + * the offsets are unknown, all the character spaces except colons (":") should be filled + * with blank characters, or else the Interoperability field should be filled with blank + * characters. The character string length is 7 Bytes including NULL for termination. When + * the field is left blank, it is treated as unknown.</p> + * + * <ul> + * <li>Tag = 36880</li> + * <li>Type = String</li> + * <li>Length = 7</li> + * <li>Default = None</li> + * </ul> + */ public static final String TAG_OFFSET_TIME = "OffsetTime"; - /** Type is String. {@hide} */ + /** + * <p>A tag used to record the offset from UTC (the time difference from Universal Time + * Coordinated including daylight saving time) of the time of DateTimeOriginal tag. The format + * when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When + * the offsets are unknown, all the character spaces except colons (":") should be filled + * with blank characters, or else the Interoperability field should be filled with blank + * characters. The character string length is 7 Bytes including NULL for termination. When + * the field is left blank, it is treated as unknown.</p> + * + * <ul> + * <li>Tag = 36881</li> + * <li>Type = String</li> + * <li>Length = 7</li> + * <li>Default = None</li> + * </ul> + */ public static final String TAG_OFFSET_TIME_ORIGINAL = "OffsetTimeOriginal"; - /** Type is String. {@hide} */ + /** + * <p>A tag used to record the offset from UTC (the time difference from Universal Time + * Coordinated including daylight saving time) of the time of DateTimeDigitized tag. The format + * when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When + * the offsets are unknown, all the character spaces except colons (":") should be filled + * with blank characters, or else the Interoperability field should be filled with blank + * characters. The character string length is 7 Bytes including NULL for termination. When + * the field is left blank, it is treated as unknown.</p> + * + * <ul> + * <li>Tag = 36882</li> + * <li>Type = String</li> + * <li>Length = 7</li> + * <li>Default = None</li> + * </ul> + */ public static final String TAG_OFFSET_TIME_DIGITIZED = "OffsetTimeDigitized"; /** Type is int. */ public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension"; diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 7eec8d9f6cc3..8d857243bc05 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -197,7 +197,7 @@ public class MediaScannerConnection implements ServiceConnection { private static Uri scanFileQuietly(ContentProviderClient client, File file) { Uri uri = null; try { - uri = MediaStore.scanFile(client, file); + uri = MediaStore.scanFile(client, file.getCanonicalFile()); Log.d(TAG, "Scanned " + file + " to " + uri); } catch (Exception e) { Log.w(TAG, "Failed to scan " + file + ": " + e); diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 7d68d0279f1f..3d89909b87aa 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -17,6 +17,7 @@ package android.media.tv.tuner; import android.annotation.Nullable; +import android.media.tv.tuner.TunerConstants.DemuxPidType; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -35,6 +36,7 @@ public final class Tuner implements AutoCloseable { private static final int MSG_ON_FRONTEND_EVENT = 1; private static final int MSG_ON_FILTER_EVENT = 2; private static final int MSG_ON_FILTER_STATUS = 3; + private static final int MSG_ON_LNB_EVENT = 4; static { System.loadLibrary("media_tv_tuner"); @@ -45,6 +47,9 @@ public final class Tuner implements AutoCloseable { private Frontend mFrontend; private EventHandler mHandler; + private List<Integer> mLnbIds; + private Lnb mLnb; + public Tuner() { nativeSetup(); } @@ -76,6 +81,11 @@ public final class Tuner implements AutoCloseable { private native Filter nativeOpenFilter(int type, int subType, int bufferSize); + private native List<Integer> nativeGetLnbIds(); + private native Lnb nativeOpenLnbById(int id); + + private native Descrambler nativeOpenDescrambler(); + /** * Frontend Callback. @@ -89,6 +99,16 @@ public final class Tuner implements AutoCloseable { } /** + * LNB Callback. + */ + public interface LnbCallback { + /** + * Invoked when there is a LNB event. + */ + void onEvent(int lnbEventType); + } + + /** * Frontend Callback. */ public interface FilterCallback { @@ -129,6 +149,11 @@ public final class Tuner implements AutoCloseable { } break; } + case MSG_ON_LNB_EVENT: { + if (mLnb != null && mLnb.mCallback != null) { + mLnb.mCallback.onEvent(msg.arg1); + } + } default: // fall through } @@ -193,6 +218,11 @@ public final class Tuner implements AutoCloseable { private long mNativeContext; private FilterCallback mCallback; int mId; + + private native boolean nativeStartFilter(); + private native boolean nativeStopFilter(); + private native boolean nativeFlushFilter(); + private Filter(int id) { mId = id; } @@ -203,6 +233,18 @@ public final class Tuner implements AutoCloseable { mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this)); } } + + public boolean start() { + return nativeStartFilter(); + } + + public boolean stop() { + return nativeStopFilter(); + } + + public boolean flush() { + return nativeFlushFilter(); + } } private Filter openFilter(int type, int subType, int bufferSize, FilterCallback cb) { @@ -215,4 +257,68 @@ public final class Tuner implements AutoCloseable { } return filter; } + + protected class Lnb { + private int mId; + private LnbCallback mCallback; + + private Lnb(int id) { + mId = id; + } + + public void setCallback(@Nullable LnbCallback callback) { + mCallback = callback; + if (mCallback == null) { + return; + } + if (mHandler == null) { + mHandler = createEventHandler(); + } + } + } + + private List<Integer> getLnbIds() { + mLnbIds = nativeGetLnbIds(); + return mLnbIds; + } + + private Lnb openLnbById(int id) { + if (mLnbIds == null) { + mLnbIds = getLnbIds(); + } + if (!mLnbIds.contains(id)) { + return null; + } + mLnb = nativeOpenLnbById(id); + return mLnb; + } + + private void onLnbEvent(int eventType) { + if (mHandler != null) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LNB_EVENT, eventType, 0)); + } + } + + protected class Descrambler { + private long mNativeContext; + + private native boolean nativeAddPid(int pidType, int pid, Filter filter); + private native boolean nativeRemovePid(int pidType, int pid, Filter filter); + + private Descrambler() {} + + private boolean addPid(@DemuxPidType int pidType, int pid, Filter filter) { + return nativeAddPid(pidType, pid, filter); + } + + private boolean removePid(@DemuxPidType int pidType, int pid, Filter filter) { + return nativeRemovePid(pidType, pid, filter); + } + + } + + private Descrambler openDescrambler() { + Descrambler descrambler = nativeOpenDescrambler(); + return descrambler; + } } diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index 411882ed13bd..01f9367dd4f3 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -67,6 +67,14 @@ final class TunerConstants { public static final int DATA_FORMAT_ES = Constants.DataFormat.ES; public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID}) + public @interface DemuxPidType {} + + public static final int DEMUX_T_PID = 1; + public static final int DEMUX_MMPT_PID = 2; + private TunerConstants() { } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 26405720c31f..efdd333f97f2 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -28,22 +28,45 @@ using ::android::hardware::Void; using ::android::hardware::hidl_vec; using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType; +using ::android::hardware::tv::tuner::V1_0::DemuxMmtpPid; +using ::android::hardware::tv::tuner::V1_0::DemuxTpid; using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType; using ::android::hardware::tv::tuner::V1_0::ITuner; using ::android::hardware::tv::tuner::V1_0::Result; struct fields_t { - jfieldID context; + jfieldID tunerContext; jfieldID filterContext; + jfieldID descramblerContext; jmethodID frontendInitID; jmethodID filterInitID; jmethodID onFrontendEventID; jmethodID onFilterStatusID; + jmethodID lnbInitID; + jmethodID onLnbEventID; + jmethodID descramblerInitID; }; static fields_t gFields; namespace android { +/////////////// LnbCallback /////////////////////// +LnbCallback::LnbCallback(jweak tunerObj, LnbId id) : mObject(tunerObj), mId(id) {} + +Return<void> LnbCallback::onEvent(LnbEventType lnbEventType) { + ALOGD("LnbCallback::onEvent, type=%d", lnbEventType); + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod( + mObject, + gFields.onLnbEventID, + (jint)lnbEventType); + return Void(); +} +Return<void> LnbCallback::onDiseqcMessage(const hidl_vec<uint8_t>& /*diseqcMessage*/) { + ALOGD("LnbCallback::onDiseqcMessage"); + return Void(); +} + /////////////// FilterCallback /////////////////////// //TODO: implement filter callback Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& /*filterEvent*/) { @@ -175,6 +198,52 @@ jobject JTuner::openFrontendById(int id) { (jint) jId); } +jobject JTuner::getLnbIds() { + ALOGD("JTuner::getLnbIds()"); + mTuner->getLnbIds([&](Result, const hidl_vec<FrontendId>& lnbIds) { + mLnbIds = lnbIds; + }); + if (mLnbIds.size() == 0) { + ALOGW("Lnb isn't available"); + return NULL; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jclass arrayListClazz = env->FindClass("java/util/ArrayList"); + jmethodID arrayListAdd = env->GetMethodID(arrayListClazz, "add", "(Ljava/lang/Object;)Z"); + jobject obj = env->NewObject(arrayListClazz, env->GetMethodID(arrayListClazz, "<init>", "()V")); + + jclass integerClazz = env->FindClass("java/lang/Integer"); + jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V"); + + for (int i=0; i < mLnbIds.size(); i++) { + jobject idObj = env->NewObject(integerClazz, intInit, mLnbIds[i]); + env->CallBooleanMethod(obj, arrayListAdd, idObj); + } + return obj; +} + +jobject JTuner::openLnbById(int id) { + sp<ILnb> lnbSp; + mTuner->openLnbById(id, [&](Result, const sp<ILnb>& lnb) { + lnbSp = lnb; + }); + if (lnbSp == nullptr) { + ALOGE("Failed to open lnb"); + return NULL; + } + mLnb = lnbSp; + sp<LnbCallback> lnbCb = new LnbCallback(mObject, id); + mLnb->setCallback(lnbCb); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + return env->NewObject( + env->FindClass("android/media/tv/tuner/Tuner$Lnb"), + gFields.lnbInitID, + mObject, + id); +} + bool JTuner::openDemux() { if (mTuner == nullptr) { return false; @@ -193,6 +262,33 @@ bool JTuner::openDemux() { return true; } +jobject JTuner::openDescrambler() { + ALOGD("JTuner::openDescrambler"); + if (mTuner == nullptr) { + return NULL; + } + sp<IDescrambler> descramblerSp; + mTuner->openDescrambler([&](Result, const sp<IDescrambler>& descrambler) { + descramblerSp = descrambler; + }); + + if (descramblerSp == NULL) { + return NULL; + } + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + jobject descramblerObj = + env->NewObject( + env->FindClass("android/media/tv/tuner/Tuner$Descrambler"), + gFields.descramblerInitID, + mObject); + + descramblerSp->incStrong(descramblerObj); + env->SetLongField(descramblerObj, gFields.descramblerContext, (jlong)descramblerSp.get()); + + return descramblerObj; +} + jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) { if (mDemux == NULL) { if (!openDemux()) { @@ -238,7 +334,7 @@ jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) { using namespace android; static sp<JTuner> setTuner(JNIEnv *env, jobject thiz, const sp<JTuner> &tuner) { - sp<JTuner> old = (JTuner *)env->GetLongField(thiz, gFields.context); + sp<JTuner> old = (JTuner *)env->GetLongField(thiz, gFields.tunerContext); if (tuner != NULL) { tuner->incStrong(thiz); @@ -246,13 +342,27 @@ static sp<JTuner> setTuner(JNIEnv *env, jobject thiz, const sp<JTuner> &tuner) { if (old != NULL) { old->decStrong(thiz); } - env->SetLongField(thiz, gFields.context, (jlong)tuner.get()); + env->SetLongField(thiz, gFields.tunerContext, (jlong)tuner.get()); return old; } static sp<JTuner> getTuner(JNIEnv *env, jobject thiz) { - return (JTuner *)env->GetLongField(thiz, gFields.context); + return (JTuner *)env->GetLongField(thiz, gFields.tunerContext); +} + +static sp<IDescrambler> getDescrambler(JNIEnv *env, jobject descrambler) { + return (IDescrambler *)env->GetLongField(descrambler, gFields.descramblerContext); +} + +static DemuxPid getDemuxPid(int pidType, int pid) { + DemuxPid demuxPid; + if ((int)pidType == 1) { + demuxPid.tPid(static_cast<DemuxTpid>(pid)); + } else if ((int)pidType == 2) { + demuxPid.mmtpPid(static_cast<DemuxMmtpPid>(pid)); + } + return demuxPid; } static sp<IFilter> getFilter(JNIEnv *env, jobject filter) { @@ -263,21 +373,32 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { jclass clazz = env->FindClass("android/media/tv/tuner/Tuner"); CHECK(clazz != NULL); - gFields.context = env->GetFieldID(clazz, "mNativeContext", "J"); - CHECK(gFields.context != NULL); + gFields.tunerContext = env->GetFieldID(clazz, "mNativeContext", "J"); + CHECK(gFields.tunerContext != NULL); gFields.onFrontendEventID = env->GetMethodID(clazz, "onFrontendEvent", "(I)V"); + gFields.onLnbEventID = env->GetMethodID(clazz, "onLnbEvent", "(I)V"); + jclass frontendClazz = env->FindClass("android/media/tv/tuner/Tuner$Frontend"); gFields.frontendInitID = env->GetMethodID(frontendClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); + jclass lnbClazz = env->FindClass("android/media/tv/tuner/Tuner$Lnb"); + gFields.lnbInitID = + env->GetMethodID(lnbClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); + jclass filterClazz = env->FindClass("android/media/tv/tuner/Tuner$Filter"); gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J"); gFields.filterInitID = env->GetMethodID(filterClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); gFields.onFilterStatusID = env->GetMethodID(filterClazz, "onFilterStatus", "(I)V"); + + jclass descramblerClazz = env->FindClass("android/media/tv/tuner/Tuner$Descrambler"); + gFields.descramblerContext = env->GetFieldID(descramblerClazz, "mNativeContext", "J"); + gFields.descramblerInitID = + env->GetMethodID(descramblerClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;)V"); } static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) { @@ -295,6 +416,16 @@ static jobject android_media_tv_Tuner_open_frontend_by_id(JNIEnv *env, jobject t return tuner->openFrontendById(id); } +static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->getLnbIds(); +} + +static jobject android_media_tv_Tuner_open_lnb_by_id(JNIEnv *env, jobject thiz, jint id) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->openLnbById(id); +} + static jobject android_media_tv_Tuner_open_filter( JNIEnv *env, jobject thiz, jint type, jint subType, jint bufferSize) { sp<JTuner> tuner = getTuner(env, thiz); @@ -308,7 +439,61 @@ static jobject android_media_tv_Tuner_open_filter( return tuner->openFilter(filterType, bufferSize); } -static const JNINativeMethod gMethods[] = { +static bool android_media_tv_Tuner_start_filter(JNIEnv *env, jobject filter) { + sp<IFilter> filterSp = getFilter(env, filter); + if (filterSp == NULL) { + ALOGD("Failed to start filter: filter not found"); + return false; + } + return filterSp->start() == Result::SUCCESS; +} + +static bool android_media_tv_Tuner_stop_filter(JNIEnv *env, jobject filter) { + sp<IFilter> filterSp = getFilter(env, filter); + if (filterSp == NULL) { + ALOGD("Failed to stop filter: filter not found"); + return false; + } + return filterSp->stop() == Result::SUCCESS; +} + +static bool android_media_tv_Tuner_flush_filter(JNIEnv *env, jobject filter) { + sp<IFilter> filterSp = getFilter(env, filter); + if (filterSp == NULL) { + ALOGD("Failed to flush filter: filter not found"); + return false; + } + return filterSp->flush() == Result::SUCCESS; +} + +static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz) { + sp<JTuner> tuner = getTuner(env, thiz); + return tuner->openDescrambler(); +} + +static bool android_media_tv_Tuner_add_pid( + JNIEnv *env, jobject descrambler, jint pidType, jint pid, jobject filter) { + sp<IDescrambler> descramblerSp = getDescrambler(env, descrambler); + if (descramblerSp == NULL) { + return false; + } + sp<IFilter> filterSp = getFilter(env, filter); + Result result = descramblerSp->addPid(getDemuxPid((int)pidType, (int)pid), filterSp); + return result == Result::SUCCESS; +} + +static bool android_media_tv_Tuner_remove_pid( + JNIEnv *env, jobject descrambler, jint pidType, jint pid, jobject filter) { + sp<IDescrambler> descramblerSp = getDescrambler(env, descrambler); + if (descramblerSp == NULL) { + return false; + } + sp<IFilter> filterSp = getFilter(env, filter); + Result result = descramblerSp->removePid(getDemuxPid((int)pidType, (int)pid), filterSp); + return result == Result::SUCCESS; +} + +static const JNINativeMethod gTunerMethods[] = { { "nativeInit", "()V", (void *)android_media_tv_Tuner_native_init }, { "nativeSetup", "()V", (void *)android_media_tv_Tuner_native_setup }, { "nativeGetFrontendIds", "()Ljava/util/List;", @@ -317,11 +502,48 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_tv_Tuner_open_frontend_by_id }, { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;", (void *)android_media_tv_Tuner_open_filter }, + { "nativeGetLnbIds", "()Ljava/util/List;", + (void *)android_media_tv_Tuner_get_lnb_ids }, + { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;", + (void *)android_media_tv_Tuner_open_lnb_by_id }, + { "nativeOpenDescrambler", "()Landroid/media/tv/tuner/Tuner$Descrambler;", + (void *)android_media_tv_Tuner_open_descrambler }, +}; + +static const JNINativeMethod gFilterMethods[] = { + { "nativeStartFilter", "()Z", (void *)android_media_tv_Tuner_start_filter }, + { "nativeStopFilter", "()Z", (void *)android_media_tv_Tuner_stop_filter }, + { "nativeFlushFilter", "()Z", (void *)android_media_tv_Tuner_flush_filter }, }; -static int register_android_media_tv_Tuner(JNIEnv *env) { - return AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner", gMethods, NELEM(gMethods)); +static const JNINativeMethod gDescramblerMethods[] = { + { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)Z", + (void *)android_media_tv_Tuner_add_pid }, + { "nativeRemovePid", "(IILandroid/media/tv/tuner/Tuner$Filter;)Z", + (void *)android_media_tv_Tuner_remove_pid }, +}; + +static bool register_android_media_tv_Tuner(JNIEnv *env) { + if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/Tuner", gTunerMethods, NELEM(gTunerMethods)) != JNI_OK) { + ALOGE("Failed to register tuner native methods"); + return false; + } + if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/Tuner$Filter", + gFilterMethods, + NELEM(gFilterMethods)) != JNI_OK) { + ALOGE("Failed to register filter native methods"); + return false; + } + if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/Tuner$Descrambler", + gDescramblerMethods, + NELEM(gDescramblerMethods)) != JNI_OK) { + ALOGE("Failed to register descrambler native methods"); + return false; + } + return true; } jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) @@ -335,7 +557,7 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) } assert(env != NULL); - if (register_android_media_tv_Tuner(env) != JNI_OK) { + if (!register_android_media_tv_Tuner(env)) { ALOGE("ERROR: Tuner native registration failed\n"); return result; } diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index ab48761f36be..d3aec64c075e 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -28,19 +28,33 @@ using ::android::hardware::hidl_vec; using ::android::hardware::tv::tuner::V1_0::DemuxFilterEvent; using ::android::hardware::tv::tuner::V1_0::DemuxFilterStatus; using ::android::hardware::tv::tuner::V1_0::DemuxFilterType; +using ::android::hardware::tv::tuner::V1_0::DemuxPid; using ::android::hardware::tv::tuner::V1_0::FrontendEventType; using ::android::hardware::tv::tuner::V1_0::FrontendId; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage; using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType; using ::android::hardware::tv::tuner::V1_0::IDemux; +using ::android::hardware::tv::tuner::V1_0::IDescrambler; using ::android::hardware::tv::tuner::V1_0::IFilter; using ::android::hardware::tv::tuner::V1_0::IFilterCallback; using ::android::hardware::tv::tuner::V1_0::IFrontend; using ::android::hardware::tv::tuner::V1_0::IFrontendCallback; +using ::android::hardware::tv::tuner::V1_0::ILnb; +using ::android::hardware::tv::tuner::V1_0::ILnbCallback; using ::android::hardware::tv::tuner::V1_0::ITuner; +using ::android::hardware::tv::tuner::V1_0::LnbEventType; +using ::android::hardware::tv::tuner::V1_0::LnbId; namespace android { +struct LnbCallback : public ILnbCallback { + LnbCallback(jweak tunerObj, LnbId id); + virtual Return<void> onEvent(LnbEventType lnbEventType); + virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage); + jweak mObject; + LnbId mId; +}; + struct FilterCallback : public IFilterCallback { virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent); virtual Return<void> onFilterStatus(const DemuxFilterStatus status); @@ -67,7 +81,11 @@ struct JTuner : public RefBase { sp<ITuner> getTunerService(); jobject getFrontendIds(); jobject openFrontendById(int id); + jobject getLnbIds(); + jobject openLnbById(int id); jobject openFilter(DemuxFilterType type, int bufferSize); + jobject openDescrambler(); + protected: bool openDemux(); virtual ~JTuner(); @@ -78,6 +96,8 @@ private: static sp<ITuner> mTuner; hidl_vec<FrontendId> mFeIds; sp<IFrontend> mFe; + hidl_vec<LnbId> mLnbIds; + sp<ILnb> mLnb; sp<IDemux> mDemux; int mDemuxId; }; diff --git a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java index 35322ad8ee6b..0bf0f97d2c5e 100644 --- a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java +++ b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java @@ -19,18 +19,18 @@ package com.android.media.tv.remoteprovider; import android.content.Context; import android.media.tv.ITvRemoteProvider; import android.media.tv.ITvRemoteServiceInput; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.util.Log; +import java.util.LinkedList; + /** * Base class for emote providers implemented in unbundled service. * <p/> * This object is not thread safe. It is only intended to be accessed on the * {@link Context#getMainLooper main looper thread} of an application. + * The callback {@link #onInputBridgeConnected()} may be called from a different thread. * </p><p> * IMPORTANT: This class is effectively a system API for unbundled emote service, and * must remain API stable. See README.txt in the root of this package for more information. @@ -50,11 +50,9 @@ public abstract class TvRemoteProvider { private static final String TAG = "TvRemoteProvider"; private static final boolean DEBUG_KEYS = false; - private static final int MSG_SET_SERVICE_INPUT = 1; - private static final int MSG_SEND_INPUTBRIDGE_CONNECTED = 2; private final Context mContext; private final ProviderStub mStub; - private final ProviderHandler mHandler; + private final LinkedList<Runnable> mOpenBridgeRunnables; private ITvRemoteServiceInput mRemoteServiceInput; /** @@ -67,7 +65,7 @@ public abstract class TvRemoteProvider { public TvRemoteProvider(Context context) { mContext = context.getApplicationContext(); mStub = new ProviderStub(); - mHandler = new ProviderHandler(mContext.getMainLooper()); + mOpenBridgeRunnables = new LinkedList<Runnable>(); } /** @@ -77,7 +75,6 @@ public abstract class TvRemoteProvider { return mContext; } - /** * Gets the Binder associated with the provider. * <p> @@ -105,7 +102,11 @@ public abstract class TvRemoteProvider { * @param tvServiceInput sink defined in framework service */ private void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { - mRemoteServiceInput = tvServiceInput; + synchronized (mOpenBridgeRunnables) { + mRemoteServiceInput = tvServiceInput; + } + mOpenBridgeRunnables.forEach(Runnable::run); + mOpenBridgeRunnables.clear(); } /** @@ -125,8 +126,25 @@ public abstract class TvRemoteProvider { */ public void openRemoteInputBridge(IBinder token, String name, int width, int height, int maxPointers) throws RuntimeException { + synchronized (mOpenBridgeRunnables) { + if (mRemoteServiceInput == null) { + Log.d(TAG, "Delaying openRemoteInputBridge() for " + name); + + mOpenBridgeRunnables.add(() -> { + try { + mRemoteServiceInput.openInputBridge( + token, name, width, height, maxPointers); + Log.d(TAG, "Delayed openRemoteInputBridge() for " + name + ": success"); + } catch (RemoteException re) { + Log.e(TAG, "Delayed openRemoteInputBridge() for " + name + ": failure", re); + } + }); + return; + } + } try { mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers); + Log.d(TAG, "openRemoteInputBridge() for " + name + ": success"); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -271,33 +289,12 @@ public abstract class TvRemoteProvider { private final class ProviderStub extends ITvRemoteProvider.Stub { @Override public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { - mHandler.obtainMessage(MSG_SET_SERVICE_INPUT, tvServiceInput).sendToTarget(); + TvRemoteProvider.this.setRemoteServiceInputSink(tvServiceInput); } @Override public void onInputBridgeConnected(IBinder token) { - mHandler.obtainMessage(MSG_SEND_INPUTBRIDGE_CONNECTED, 0, 0, - (IBinder) token).sendToTarget(); - } - } - - private final class ProviderHandler extends Handler { - public ProviderHandler(Looper looper) { - super(looper, null, true); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_SET_SERVICE_INPUT: { - setRemoteServiceInputSink((ITvRemoteServiceInput) msg.obj); - break; - } - case MSG_SEND_INPUTBRIDGE_CONNECTED: { - onInputBridgeConnected((IBinder) msg.obj); - break; - } - } + TvRemoteProvider.this.onInputBridgeConnected(token); } } } diff --git a/media/lib/tvremote/tests/Android.bp b/media/lib/tvremote/tests/Android.bp new file mode 100644 index 000000000000..f00eed070798 --- /dev/null +++ b/media/lib/tvremote/tests/Android.bp @@ -0,0 +1,15 @@ +android_test { + name: "TvRemoteTests", + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + "com.android.media.tv.remoteprovider", + ], + static_libs: [ + "mockito-target-minus-junit4", + ], + platform_apis: true, + certificate: "platform", + privileged: true, +} diff --git a/media/lib/tvremote/tests/AndroidManifest.xml b/media/lib/tvremote/tests/AndroidManifest.xml new file mode 100644 index 000000000000..4f843f701d20 --- /dev/null +++ b/media/lib/tvremote/tests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.tv.remoteprovider"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.media.tv.remoteprovider" + android:label="Tests for TV Remote"/> +</manifest> diff --git a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java new file mode 100644 index 000000000000..c9ce56138217 --- /dev/null +++ b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 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.media.tv.remoteprovider; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.content.Context; +import android.media.tv.ITvRemoteProvider; +import android.media.tv.ITvRemoteServiceInput; +import android.os.Binder; +import android.os.IBinder; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.util.ArrayList; + +public class TvRemoteProviderTest extends AndroidTestCase { + private static final String TAG = TvRemoteProviderTest.class.getSimpleName(); + + @SmallTest + public void testOpenRemoteInputBridge() throws Exception { + Binder tokenA = new Binder(); + Binder tokenB = new Binder(); + Binder tokenC = new Binder(); + + class LocalTvRemoteProvider extends TvRemoteProvider { + private final ArrayList<IBinder> mTokens = new ArrayList<IBinder>(); + + LocalTvRemoteProvider(Context context) { + super(context); + } + + @Override + public void onInputBridgeConnected(IBinder token) { + mTokens.add(token); + } + + public boolean verifyTokens() { + return mTokens.size() == 3 + && mTokens.contains(tokenA) + && mTokens.contains(tokenB) + && mTokens.contains(tokenC); + } + } + + LocalTvRemoteProvider tvProvider = new LocalTvRemoteProvider(getContext()); + ITvRemoteProvider binder = (ITvRemoteProvider) tvProvider.getBinder(); + + ITvRemoteServiceInput tvServiceInput = mock(ITvRemoteServiceInput.class); + doAnswer((i) -> { + binder.onInputBridgeConnected(i.getArgument(0)); + return null; + }).when(tvServiceInput).openInputBridge(any(), any(), anyInt(), anyInt(), anyInt()); + + tvProvider.openRemoteInputBridge(tokenA, "A", 1, 1, 1); + tvProvider.openRemoteInputBridge(tokenB, "B", 1, 1, 1); + binder.setRemoteServiceInputSink(tvServiceInput); + tvProvider.openRemoteInputBridge(tokenC, "C", 1, 1, 1); + + verify(tvServiceInput).openInputBridge(tokenA, "A", 1, 1, 1); + verify(tvServiceInput).openInputBridge(tokenB, "B", 1, 1, 1); + verify(tvServiceInput).openInputBridge(tokenC, "C", 1, 1, 1); + verifyNoMoreInteractions(tvServiceInput); + + assertTrue(tvProvider.verifyTokens()); + } +} diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types index ce022a8b0f19..cb04d921bd67 100644 --- a/mime/java-res/android.mime.types +++ b/mime/java-res/android.mime.types @@ -73,6 +73,7 @@ ?audio/3gpp 3gpp 3ga ?audio/aac-adts aac ?audio/ac3 ac3 a52 +?audio/amr amr ?audio/imelody imy ?audio/midi rtttl xmf ?audio/mobile-xmf mxmf diff --git a/native/android/Android.bp b/native/android/Android.bp index 91297b0de02e..9d93c9b7b605 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -36,7 +36,6 @@ cc_library_shared { srcs: [ "asset_manager.cpp", - "choreographer.cpp", "configuration.cpp", "hardware_buffer_jni.cpp", "input.cpp", @@ -80,7 +79,7 @@ cc_library_shared { "libarect", ], - whole_static_libs: ["libnativewindow"], + whole_static_libs: ["libnativedisplay", "libnativewindow"], export_static_lib_headers: ["libarect"], @@ -142,4 +141,4 @@ filegroup { "aidl/com/android/internal/compat/IPlatformCompatNative.aidl", ], path: "aidl", -}
\ No newline at end of file +} diff --git a/native/android/choreographer.cpp b/native/android/choreographer.cpp deleted file mode 100644 index 63e073405fe0..000000000000 --- a/native/android/choreographer.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -#define LOG_TAG "Choreographer" -//#define LOG_NDEBUG 0 - -#include <cinttypes> -#include <queue> -#include <thread> - -#include <android/choreographer.h> -#include <androidfw/DisplayEventDispatcher.h> -#include <gui/ISurfaceComposer.h> -#include <gui/SurfaceComposerClient.h> -#include <utils/Looper.h> -#include <utils/Mutex.h> -#include <utils/Timers.h> - -namespace android { - -static inline const char* toString(bool value) { - return value ? "true" : "false"; -} - -struct FrameCallback { - AChoreographer_frameCallback callback; - AChoreographer_frameCallback64 callback64; - void* data; - nsecs_t dueTime; - - inline bool operator<(const FrameCallback& rhs) const { - // Note that this is intentionally flipped because we want callbacks due sooner to be at - // the head of the queue - return dueTime > rhs.dueTime; - } -}; - - -class Choreographer : public DisplayEventDispatcher, public MessageHandler { -public: - void postFrameCallbackDelayed(AChoreographer_frameCallback cb, - AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay); - - enum { - MSG_SCHEDULE_CALLBACKS = 0, - MSG_SCHEDULE_VSYNC = 1 - }; - virtual void handleMessage(const Message& message) override; - - static Choreographer* getForThread(); - -protected: - virtual ~Choreographer() = default; - -private: - explicit Choreographer(const sp<Looper>& looper); - Choreographer(const Choreographer&) = delete; - - void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override; - void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; - void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId, - int32_t configId) override; - - void scheduleCallbacks(); - - // Protected by mLock - std::priority_queue<FrameCallback> mCallbacks; - - mutable Mutex mLock; - - const sp<Looper> mLooper; - const std::thread::id mThreadId; -}; - - -static thread_local Choreographer* gChoreographer; -Choreographer* Choreographer::getForThread() { - if (gChoreographer == nullptr) { - sp<Looper> looper = Looper::getForThread(); - if (!looper.get()) { - ALOGW("No looper prepared for thread"); - return nullptr; - } - gChoreographer = new Choreographer(looper); - status_t result = gChoreographer->initialize(); - if (result != OK) { - ALOGW("Failed to initialize"); - return nullptr; - } - } - return gChoreographer; -} - -Choreographer::Choreographer(const sp<Looper>& looper) : - DisplayEventDispatcher(looper), mLooper(looper), mThreadId(std::this_thread::get_id()) { -} - -void Choreographer::postFrameCallbackDelayed( - AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay) { - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - FrameCallback callback{cb, cb64, data, now + delay}; - { - AutoMutex _l{mLock}; - mCallbacks.push(callback); - } - if (callback.dueTime <= now) { - if (std::this_thread::get_id() != mThreadId) { - Message m{MSG_SCHEDULE_VSYNC}; - mLooper->sendMessage(this, m); - } else { - scheduleVsync(); - } - } else { - Message m{MSG_SCHEDULE_CALLBACKS}; - mLooper->sendMessageDelayed(delay, this, m); - } -} - -void Choreographer::scheduleCallbacks() { - AutoMutex _{mLock}; - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - if (mCallbacks.top().dueTime <= now) { - ALOGV("choreographer %p ~ scheduling vsync", this); - scheduleVsync(); - return; - } -} - -// TODO(b/74619554): The PhysicalDisplayId is ignored because SF only emits VSYNC events for the -// internal display and DisplayEventReceiver::requestNextVsync only allows requesting VSYNC for -// the internal display implicitly. -void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t) { - std::vector<FrameCallback> callbacks{}; - { - AutoMutex _l{mLock}; - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - while (!mCallbacks.empty() && mCallbacks.top().dueTime < now) { - callbacks.push_back(mCallbacks.top()); - mCallbacks.pop(); - } - } - for (const auto& cb : callbacks) { - if (cb.callback64 != nullptr) { - cb.callback64(timestamp, cb.data); - } else if (cb.callback != nullptr) { - cb.callback(timestamp, cb.data); - } - } -} - -void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) { - ALOGV("choreographer %p ~ received hotplug event (displayId=%" - ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", connected=%s), ignoring.", - this, displayId, toString(connected)); -} - -void Choreographer::dispatchConfigChanged(nsecs_t, PhysicalDisplayId displayId, - int32_t configId) { - ALOGV("choreographer %p ~ received config changed event (displayId=%" - ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", configId=%s), ignoring.", - this, displayId, toString(configId)); -} - -void Choreographer::handleMessage(const Message& message) { - switch (message.what) { - case MSG_SCHEDULE_CALLBACKS: - scheduleCallbacks(); - break; - case MSG_SCHEDULE_VSYNC: - scheduleVsync(); - break; - } -} - -} - -/* Glue for the NDK interface */ - -using android::Choreographer; - -static inline Choreographer* AChoreographer_to_Choreographer(AChoreographer* choreographer) { - return reinterpret_cast<Choreographer*>(choreographer); -} - -static inline AChoreographer* Choreographer_to_AChoreographer(Choreographer* choreographer) { - return reinterpret_cast<AChoreographer*>(choreographer); -} - -AChoreographer* AChoreographer_getInstance() { - return Choreographer_to_AChoreographer(Choreographer::getForThread()); -} - -void AChoreographer_postFrameCallback(AChoreographer* choreographer, - AChoreographer_frameCallback callback, void* data) { - AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed( - callback, nullptr, data, 0); -} -void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer, - AChoreographer_frameCallback callback, void* data, long delayMillis) { - AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed( - callback, nullptr, data, ms2ns(delayMillis)); -} -void AChoreographer_postFrameCallback64(AChoreographer* choreographer, - AChoreographer_frameCallback64 callback, void* data) { - AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed( - nullptr, callback, data, 0); -} -void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer, - AChoreographer_frameCallback64 callback, void* data, uint32_t delayMillis) { - AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed( - nullptr, callback, data, ms2ns(delayMillis)); -} diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java index c35303e1e6c9..2bd5fe228f41 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java @@ -19,36 +19,16 @@ package com.android.systemui; import android.content.Context; import com.android.systemui.dagger.SystemUIRootComponent; -import com.android.systemui.navigationbar.car.CarFacetButtonController; - -import javax.inject.Singleton; - -import dagger.Component; /** * Class factory to provide car specific SystemUI components. */ public class CarSystemUIFactory extends SystemUIFactory { - private CarDependencyComponent mCarDependencyComponent; - @Override protected SystemUIRootComponent buildSystemUIRootComponent(Context context) { - mCarDependencyComponent = DaggerCarSystemUIFactory_CarDependencyComponent.builder() - .contextHolder(new ContextHolder(context)) - .build(); return DaggerCarSystemUIRootComponent.builder() .contextHolder(new ContextHolder(context)) .build(); } - - public CarDependencyComponent getCarDependencyComponent() { - return mCarDependencyComponent; - } - - @Singleton - @Component(modules = ContextHolder.class) - public interface CarDependencyComponent { - CarFacetButtonController getCarFacetButtonController(); - } } diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 91d00262dc18..818fdeaef8db 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -38,7 +38,6 @@ import com.android.systemui.statusbar.car.CarStatusBar; import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -101,7 +100,7 @@ abstract class CarSystemUIModule { abstract DockManager bindDockManager(DockManagerImpl dockManager); @Binds - abstract NotificationData.KeyguardEnvironment bindKeyguardEnvironment( + abstract NotificationEntryManager.KeyguardEnvironment bindKeyguardEnvironment( KeyguardEnvironmentImpl keyguardEnvironment); @Binds diff --git a/packages/CarSystemUI/src/com/android/systemui/TEST_MAPPING b/packages/CarSystemUI/src/com/android/systemui/TEST_MAPPING new file mode 100644 index 000000000000..f90947cbeb15 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "auto-postsubmit": [ + { + "name": "AndroidAutoUiTests", + "options" : [ + { + "include-filter": "android.test.functional.auto.apps.HomeHelperTest" + } + ] + } + ] +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index 53a88a9a54e9..ed945e7d4e72 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -19,8 +19,9 @@ import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.logging.NotifLog; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -35,8 +36,12 @@ import javax.inject.Singleton; public class CarNotificationEntryManager extends NotificationEntryManager { @Inject - public CarNotificationEntryManager(NotificationData notificationData, NotifLog notifLog) { - super(notificationData, notifLog); + public CarNotificationEntryManager( + NotifLog notifLog, + NotificationGroupManager groupManager, + NotificationRankingManager rankingManager, + KeyguardEnvironment keyguardEnvironment) { + super(notifLog, groupManager, rankingManager, keyguardEnvironment); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java index f8bfeec6df2d..80ee37127965 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarServiceProvider.java @@ -19,6 +19,8 @@ package com.android.systemui.car; import android.car.Car; import android.content.Context; +import androidx.annotation.VisibleForTesting; + import java.util.ArrayList; import java.util.List; @@ -50,6 +52,12 @@ public class CarServiceProvider { }); } + @VisibleForTesting + public CarServiceProvider(Context context, Car car) { + mContext = context; + mCar = car; + } + /** * Let's other components hook into the connection to the car service. If we're already * connected to the car service, the callback is immediately triggered. diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java index c46e6e7433a3..0b8999263c73 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java @@ -29,9 +29,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.keyguard.AlphaOptimizedImageButton; -import com.android.systemui.CarSystemUIFactory; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; /** * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined @@ -82,10 +80,6 @@ public class CarFacetButton extends LinearLayout { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton); setupIntents(typedArray); setupIcons(typedArray); - CarSystemUIFactory factory = SystemUIFactory.getInstance(); - CarFacetButtonController carFacetButtonController = factory.getCarDependencyComponent() - .getCarFacetButtonController(); - carFacetButtonController.addFacetButton(this); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java index 30f63f052b9f..f66e8280197e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java @@ -43,6 +43,8 @@ import javax.inject.Singleton; @Singleton public class CarFacetButtonController { + private final Set<CarFacetButton> mRegisteredViews = new HashSet<>(); + protected ButtonMap mButtonsByCategory = new ButtonMap(); protected ButtonMap mButtonsByPackage = new ButtonMap(); protected ButtonMap mButtonsByComponentName = new ButtonMap(); @@ -60,7 +62,11 @@ public class CarFacetButtonController { * to get a reference to this controller via {@link com.android.systemui.Dependency} * and self add. */ - public void addFacetButton(CarFacetButton facetButton) { + private void addFacetButton(CarFacetButton facetButton) { + if (mRegisteredViews.contains(facetButton)) { + return; + } + String[] categories = facetButton.getCategories(); for (int i = 0; i < categories.length; i++) { mButtonsByCategory.add(categories[i], facetButton); @@ -74,6 +80,8 @@ public class CarFacetButtonController { for (int i = 0; i < componentNames.length; i++) { mButtonsByComponentName.add(componentNames[i], facetButton); } + + mRegisteredViews.add(facetButton); } /** Removes all buttons from the button maps. */ @@ -82,6 +90,7 @@ public class CarFacetButtonController { mButtonsByPackage.clear(); mButtonsByComponentName.clear(); mSelectedFacetButtons.clear(); + mRegisteredViews.clear(); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java index af2cb0ab5950..fd9c488278ba 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java @@ -32,10 +32,12 @@ import com.android.systemui.car.CarServiceProvider; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -49,6 +51,7 @@ public class HvacController { public static final String TAG = "HvacController"; private final CarServiceProvider mCarServiceProvider; + private final Set<TemperatureView> mRegisteredViews = new HashSet<>(); private CarHvacManager mHvacManager; private HashMap<HvacKey, List<TemperatureView>> mTempComponents = new HashMap<>(); @@ -112,7 +115,10 @@ public class HvacController { /** * Add component to list and initialize it if the connection is up. */ - public void addHvacTextView(TemperatureView temperatureView) { + private void addHvacTextView(TemperatureView temperatureView) { + if (mRegisteredViews.contains(temperatureView)) { + return; + } HvacKey hvacKey = new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()); if (!mTempComponents.containsKey(hvacKey)) { @@ -120,6 +126,8 @@ public class HvacController { } mTempComponents.get(hvacKey).add(temperatureView); initComponent(temperatureView); + + mRegisteredViews.add(temperatureView); } private void initComponents() { @@ -165,6 +173,7 @@ public class HvacController { */ public void removeAllComponents() { mTempComponents.clear(); + mRegisteredViews.clear(); } /** diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java new file mode 100644 index 000000000000..a71d1db3ee70 --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2019 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.navigationbar.car.hvac; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.car.Car; +import android.car.hardware.hvac.CarHvacManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.car.CarServiceProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class HvacControllerTest extends SysuiTestCase { + + private static final int PROPERTY_ID = 1; + private static final int AREA_ID = 1; + private static final float VALUE = 72.0f; + + private HvacController mHvacController; + private CarServiceProvider mCarServiceProvider; + + @Mock + private Car mCar; + @Mock + private CarHvacManager mCarHvacManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mCar.isConnected()).thenReturn(true); + when(mCar.getCarManager(Car.HVAC_SERVICE)).thenReturn(mCarHvacManager); + + mCarServiceProvider = new CarServiceProvider(mContext, mCar); + mHvacController = new HvacController(mCarServiceProvider); + mHvacController.connectToCarService(); + } + + @Test + public void connectToCarService_registersCallback() { + verify(mCarHvacManager).registerCallback(any()); + } + + @Test + public void addTemperatureViewToController_usingTemperatureView_registersView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + + verify(v).setTemp(VALUE); + } + + @Test + public void addTemperatureViewToController_usingSameTemperatureView_registersFirstView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + resetTemperatureView(v, PROPERTY_ID, AREA_ID); + + mHvacController.addTemperatureViewToController(v); + verify(v, never()).setTemp(VALUE); + } + + @Test + public void addTemperatureViewToController_usingDifferentTemperatureView_registersBothViews() { + TemperatureTextView v1 = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v1); + verify(v1).setTemp(VALUE); + + TemperatureTextView v2 = setupMockTemperatureTextView( + PROPERTY_ID + 1, + AREA_ID + 1, + VALUE + 1); + mHvacController.addTemperatureViewToController(v2); + verify(v2).setTemp(VALUE + 1); + } + + @Test + public void removeAllComponents_ableToRegisterSameView() { + TemperatureTextView v = setupMockTemperatureTextView(PROPERTY_ID, AREA_ID, VALUE); + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + + mHvacController.removeAllComponents(); + resetTemperatureView(v, PROPERTY_ID, AREA_ID); + + mHvacController.addTemperatureViewToController(v); + verify(v).setTemp(VALUE); + } + + private TemperatureTextView setupMockTemperatureTextView(int propertyId, int areaId, + float value) { + TemperatureTextView v = mock(TemperatureTextView.class); + resetTemperatureView(v, propertyId, areaId); + when(mCarHvacManager.isPropertyAvailable(propertyId, areaId)).thenReturn(true); + when(mCarHvacManager.getFloatProperty(propertyId, areaId)).thenReturn(value); + return v; + } + + private void resetTemperatureView(TemperatureTextView view, int propertyId, int areaId) { + reset(view); + when(view.getPropertyId()).thenReturn(propertyId); + when(view.getAreaId()).thenReturn(areaId); + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 776593590a67..f5d1ccfe378b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -154,7 +154,6 @@ public class SettingsProvider extends ContentProvider { private static final String TABLE_SYSTEM = "system"; private static final String TABLE_SECURE = "secure"; private static final String TABLE_GLOBAL = "global"; - private static final String TABLE_CONFIG = "config"; // Old tables no longer exist. private static final String TABLE_FAVORITES = "favorites"; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index b9fe9334d14c..91b22d178b9f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -46,6 +46,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; @@ -617,6 +618,15 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); mLockPatternUtils.reportSuccessfulPasswordAttempt(userId); + // Force a garbage collection in an attempt to erase any lockscreen password left in + // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard + // dismiss animation janky. + ThreadUtils.postOnBackgroundThread(() -> { + try { + Thread.sleep(5000); + } catch (InterruptedException ignored) { } + Runtime.getRuntime().gc(); + }); } else { StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index dca5c8a5a36f..1a0690c1a273 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -75,10 +75,10 @@ import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController; import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager; diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java index 385de4acfea8..15a5c2773f0b 100644 --- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java +++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java @@ -139,8 +139,7 @@ public class ForegroundServiceController { // Update appOp if there's an associated pending or visible notification: final String foregroundKey = getStandardLayoutKey(userId, packageName); if (foregroundKey != null) { - final NotificationEntry entry = mEntryManager.getPendingOrCurrentNotif( - foregroundKey); + final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(foregroundKey); if (entry != null && uid == entry.getSbn().getUid() && packageName.equals(entry.getSbn().getPackageName())) { diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index aab4041aba5f..4b28e4af7d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -141,7 +141,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv public void startServicesIfNeeded() { String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents); - startServicesIfNeeded(names); + startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names); } /** @@ -153,10 +153,10 @@ public class SystemUIApplication extends Application implements SysUiServiceProv void startSecondaryUserServicesIfNeeded() { String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser); - startServicesIfNeeded(names); + startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names); } - private void startServicesIfNeeded(String[] services) { + private void startServicesIfNeeded(String metricsPrefix, String[] services) { if (mServicesStarted) { return; } @@ -177,12 +177,12 @@ public class SystemUIApplication extends Application implements SysUiServiceProv Process.myUserHandle().getIdentifier() + "."); TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming", Trace.TRACE_TAG_APP); - log.traceBegin("StartServices"); + log.traceBegin(metricsPrefix); final int N = services.length; for (int i = 0; i < N; i++) { String clsName = services[i]; if (DEBUG) Log.d(TAG, "loading: " + clsName); - log.traceBegin("StartServices" + clsName); + log.traceBegin(metricsPrefix + clsName); long ti = System.currentTimeMillis(); try { SystemUI obj = mComponentHelper.resolveSystemUI(clsName); diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 4516996345b9..170c25a82101 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -71,7 +71,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac private final Runnable mHideHandles = this::hideHandles; private final Runnable mShowAndGo = this::showAndGoInternal; private final Provider<AssistHandleViewController> mAssistHandleViewController; - private final PhenotypeHelper mPhenotypeHelper; + private final DeviceConfigHelper mDeviceConfigHelper; private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap; private boolean mHandlesShowing = false; @@ -90,7 +90,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac AssistUtils assistUtils, @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler, Provider<AssistHandleViewController> assistHandleViewController, - PhenotypeHelper phenotypeHelper, + DeviceConfigHelper deviceConfigHelper, Map<AssistHandleBehavior, BehaviorController> behaviorMap, NavigationModeController navigationModeController, DumpController dumpController) { @@ -98,14 +98,14 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mAssistUtils = assistUtils; mHandler = handler; mAssistHandleViewController = assistHandleViewController; - mPhenotypeHelper = phenotypeHelper; + mDeviceConfigHelper = deviceConfigHelper; mBehaviorMap = behaviorMap; mInGesturalMode = QuickStepContract.isGesturalMode( navigationModeController.addListener(this::handleNavigationModeChange)); setBehavior(getBehaviorMode()); - mPhenotypeHelper.addOnPropertiesChangedListener( + mDeviceConfigHelper.addOnPropertiesChangedListener( mHandler::post, (properties) -> { if (properties.getKeyset().contains( @@ -205,19 +205,19 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac } private long getShownFrequencyThreshold() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS, DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS); } private long getShowAndGoDuration() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS, DEFAULT_SHOW_AND_GO_DURATION_MS); } private String getBehaviorMode() { - return mPhenotypeHelper.getString( + return mDeviceConfigHelper.getString( SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, DEFAULT_BEHAVIOR.toString()); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java index 46ae84a95fad..9793d727f038 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -155,7 +155,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { private final Clock mClock; private final Handler mHandler; - private final PhenotypeHelper mPhenotypeHelper; + private final DeviceConfigHelper mDeviceConfigHelper; private final Lazy<StatusBarStateController> mStatusBarStateController; private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper; private final Lazy<OverviewProxyService> mOverviewProxyService; @@ -189,7 +189,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { AssistHandleReminderExpBehavior( @Named(UPTIME_NAME) Clock clock, @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler, - PhenotypeHelper phenotypeHelper, + DeviceConfigHelper deviceConfigHelper, Lazy<StatusBarStateController> statusBarStateController, Lazy<ActivityManagerWrapper> activityManagerWrapper, Lazy<OverviewProxyService> overviewProxyService, @@ -199,7 +199,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { Lazy<BroadcastDispatcher> broadcastDispatcher) { mClock = clock; mHandler = handler; - mPhenotypeHelper = phenotypeHelper; + mDeviceConfigHelper = deviceConfigHelper; mStatusBarStateController = statusBarStateController; mActivityManagerWrapper = activityManagerWrapper; mOverviewProxyService = overviewProxyService; @@ -465,55 +465,55 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { } private long getLearningTimeMs() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS, DEFAULT_LEARNING_TIME_MS); } private int getLearningCount() { - return mPhenotypeHelper.getInt( + return mDeviceConfigHelper.getInt( SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_COUNT, DEFAULT_LEARNING_COUNT); } private long getShowAndGoDelayedShortDelayMs() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS, DEFAULT_SHOW_AND_GO_DELAYED_SHORT_DELAY_MS); } private long getShowAndGoDelayedLongDelayMs() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAYED_LONG_DELAY_MS, DEFAULT_SHOW_AND_GO_DELAYED_LONG_DELAY_MS); } private long getShowAndGoDelayResetTimeoutMs() { - return mPhenotypeHelper.getLong( + return mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS, DEFAULT_SHOW_AND_GO_DELAY_RESET_TIMEOUT_MS); } private boolean getSuppressOnLockscreen() { - return mPhenotypeHelper.getBoolean( + return mDeviceConfigHelper.getBoolean( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LOCKSCREEN, DEFAULT_SUPPRESS_ON_LOCKSCREEN); } private boolean getSuppressOnLauncher() { - return mPhenotypeHelper.getBoolean( + return mDeviceConfigHelper.getBoolean( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_LAUNCHER, DEFAULT_SUPPRESS_ON_LAUNCHER); } private boolean getSuppressOnApps() { - return mPhenotypeHelper.getBoolean( + return mDeviceConfigHelper.getBoolean( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SUPPRESS_ON_APPS, DEFAULT_SUPPRESS_ON_APPS); } private boolean getShowWhenTaught() { - return mPhenotypeHelper.getBoolean( + return mDeviceConfigHelper.getBoolean( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_WHEN_TAUGHT, DEFAULT_SHOW_WHEN_TAUGHT); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java index 05a01dd48641..86b7c748ea77 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/PhenotypeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/assist/DeviceConfigHelper.java @@ -28,15 +28,15 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * Wrapper class for retrieving phenotype flag values. + * Wrapper class for retrieving System UI device configuration values. * * Can be mocked in tests for ease of testing the effects of particular values. */ @Singleton -public class PhenotypeHelper { +public class DeviceConfigHelper { @Inject - public PhenotypeHelper() {} + public DeviceConfigHelper() {} public long getLong(String name, long defaultValue) { return whitelistIpcs(() -> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 9f7bdd43fb11..db1185fb96f9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -47,6 +47,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; @@ -78,10 +79,10 @@ import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; @@ -352,14 +353,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param userId the id of the user */ private void restoreBubbles(@UserIdInt int userId) { - NotificationData notificationData = - mNotificationEntryManager.getNotificationData(); ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId); if (savedBubbleKeys == null) { // There were no bubbles saved for this used. return; } - for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) { + for (NotificationEntry e : + mNotificationEntryManager.getActiveNotificationsForCurrentUser()) { if (savedBubbleKeys.contains(e.getKey()) && mNotificationInterruptionStateProvider.shouldBubbleUp(e) && canLaunchInActivityView(mContext, e)) { @@ -458,7 +458,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi public boolean isBubbleNotificationSuppressedFromShade(String key) { boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key) && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble(); - NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key); + NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key); String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); @@ -571,7 +571,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi new NotificationRemoveInterceptor() { @Override public boolean onNotificationRemoveRequested(String key, int reason) { - NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key); + NotificationEntry entry = + mNotificationEntryManager.getActiveNotificationUnfiltered(key); String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); @@ -768,7 +769,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi String notifKey = mBubbleData.getSummaryKey(groupKey); mBubbleData.removeSuppressedSummary(groupKey); NotificationEntry entry = - mNotificationEntryManager.getNotificationData().get(notifKey); + mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey); mNotificationEntryManager.performRemoveNotification( entry.getSbn(), UNDEFINED_DISMISS_REASON); } @@ -1007,8 +1008,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey()); return false; } + PackageManager packageManager = StatusBar.getPackageManagerForUser( + context, entry.getSbn().getUser().getIdentifier()); ActivityInfo info = - intent.getIntent().resolveActivityInfo(context.getPackageManager(), 0); + intent.getIntent().resolveActivityInfo(packageManager, 0); if (info == null) { Log.w(TAG, "Unable to send as bubble, " + entry.getKey() + " couldn't find activity info for intent: " diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index f4d48b2a71a7..2ca993bd200a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -184,8 +184,9 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + entry); } + Bubble bubble = getBubbleWithKey(entry.getKey()); - suppressFlyout = !entry.getRanking().visuallyInterruptive() || suppressFlyout; + suppressFlyout |= !shouldShowFlyout(entry); if (bubble == null) { // Create a new bubble @@ -298,6 +299,15 @@ public class BubbleData { return bubbleChildren; } + private boolean shouldShowFlyout(NotificationEntry notif) { + if (notif.getRanking().visuallyInterruptive()) { + return true; + } + final boolean suppressedFromShade = hasBubbleWithKey(notif.getKey()) + && !getBubbleWithKey(notif.getKey()).showInShadeWhenBubble(); + return suppressedFromShade; + } + private void doAdd(Bubble bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doAdd: " + bubble); @@ -510,7 +520,7 @@ public class BubbleData { * required to keep grouping intact. * * @param minPosition the first insert point to consider - * @param newBubble the bubble to insert + * @param newBubble the bubble to insert * @return the position where the bubble was inserted */ private int insertBubble(int minPosition, Bubble newBubble) { @@ -683,15 +693,19 @@ public class BubbleData { * Description of current bubble data state. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.print("selected: "); pw.println(mSelectedBubble != null + pw.print("selected: "); + pw.println(mSelectedBubble != null ? mSelectedBubble.getKey() : "null"); - pw.print("expanded: "); pw.println(mExpanded); - pw.print("count: "); pw.println(mBubbles.size()); + pw.print("expanded: "); + pw.println(mExpanded); + pw.print("count: "); + pw.println(mBubbles.size()); for (Bubble bubble : mBubbles) { bubble.dump(fd, pw, args); } - pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size()); + pw.print("summaryKeys: "); + pw.println(mSuppressedGroupKeys.size()); for (String key : mSuppressedGroupKeys.keySet()) { pw.println(" suppressing: " + key); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java index 6744d74004f0..7007f9defee6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.DozeServiceHost; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -50,6 +51,7 @@ import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightControllerImpl; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotControllerImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -257,4 +259,7 @@ public abstract class DependencyBinder { @Binds public abstract VolumeComponent provideVolumeComponent( VolumeDialogComponent volumeDialogComponent); + /** */ + @Binds + public abstract HeadsUpManager bindHeadsUpManager(HeadsUpManagerPhone headsUpManagerPhone); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 48c72d311b1c..f1d02bb61ef9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -33,7 +33,7 @@ import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -74,7 +74,7 @@ abstract class SystemUIDefaultModule { abstract DockManager bindDockManager(DockManagerImpl dockManager); @Binds - abstract NotificationData.KeyguardEnvironment bindKeyguardEnvironment( + abstract NotificationEntryManager.KeyguardEnvironment bindKeyguardEnvironment( KeyguardEnvironmentImpl keyguardEnvironment); @Binds diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 0134aa3a15df..5de6d1c42b4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -169,7 +169,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { v.setText(mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID)); v.setVisibility(View.VISIBLE); } else { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 3ff6d0d2278d..7dc3236915e8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -59,13 +59,16 @@ import android.graphics.Picture; import android.graphics.PixelFormat; import android.graphics.PointF; import android.graphics.Rect; +import android.media.ExifInterface; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -107,9 +110,15 @@ import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -323,11 +332,41 @@ public class GlobalScreenshot { final Uri uri = MediaStore.createPending(context, params); final MediaStore.PendingSession session = MediaStore.openPending(context, uri); try { + // First, write the actual data for our screenshot try (OutputStream out = session.openOutputStream()) { if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) { throw new IOException("Failed to compress"); } } + + // Next, write metadata to help index the screenshot + try (ParcelFileDescriptor pfd = session.open()) { + final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor()); + + exif.setAttribute(ExifInterface.TAG_SOFTWARE, + "Android " + Build.DISPLAY); + + exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, + Integer.toString(image.getWidth())); + exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, + Integer.toString(image.getHeight())); + + final ZonedDateTime time = ZonedDateTime.ofInstant( + Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault()); + exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, + DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time)); + exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + DateTimeFormatter.ofPattern("SSS").format(time)); + + if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) { + exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00"); + } else { + exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + DateTimeFormatter.ofPattern("XXX").format(time)); + } + + exif.saveAttributes(); + } session.publish(); } catch (Exception e) { session.abandon(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index c4de2d3572bd..98a267599f7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -110,8 +110,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { } String key = sbn.getKey(); - boolean isUpdate = - mEntryManager.getNotificationData().get(key) != null; + boolean isUpdate = mEntryManager.getActiveNotificationUnfiltered(key) != null; // In case we don't allow child notifications, we ignore children of // notifications that have a summary, since` we're not going to show them // anyway. This is true also when the summary is canceled, @@ -126,8 +125,7 @@ public class NotificationListener extends NotificationListenerWithPlugins { if (isUpdate) { mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON); } else { - mEntryManager.getNotificationData() - .updateRanking(rankingMap, "onNotificationPosted"); + mEntryManager.updateRanking(rankingMap, "onNotificationPosted"); } return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 571d3d7a11be..021e7e15c7f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar; +import static android.app.Notification.VISIBILITY_SECRET; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static com.android.systemui.DejankUtils.whitelistIpcs; @@ -126,7 +127,7 @@ public class NotificationLockscreenUserManagerImpl implements updatePublicMode(); // The filtering needs to happen before the update call below in order to make sure // the presenter has the updated notifications from the new user - getEntryManager().getNotificationData().filterAndSort("user switched"); + getEntryManager().reapplyFilterAndSort("user switched"); mPresenter.onUserSwitched(mCurrentUserId); for (UserChangedListener listener : mListeners) { @@ -148,17 +149,17 @@ public class NotificationLockscreenUserManagerImpl implements } } if (notificationKey != null) { - final int count = - getEntryManager().getNotificationData().getActiveNotifications().size(); - final int rank = getEntryManager().getNotificationData().getRank(notificationKey); + NotificationEntry entry = + getEntryManager().getActiveNotificationUnfiltered(notificationKey); + final int count = getEntryManager().getActiveNotificationsCount(); + final int rank = entry != null ? entry.getRanking().getRank() : 0; NotificationVisibility.NotificationLocation location = - NotificationLogger.getNotificationLocation( - getEntryManager().getNotificationData().get(notificationKey)); + NotificationLogger.getNotificationLocation(entry); final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, rank, count, true, location); try { mBarService.onNotificationClick(notificationKey, nv); - } catch (RemoteException e) { + } catch (RemoteException exception) { /* ignore */ } } @@ -311,9 +312,9 @@ public class NotificationLockscreenUserManagerImpl implements Log.wtf(TAG, "mEntryManager was null!", new Throwable()); return true; } - return isLockscreenPublicMode(mCurrentUserId) - && getEntryManager().getNotificationData().getVisibilityOverride(key) == - Notification.VISIBILITY_SECRET; + NotificationEntry visibleEntry = getEntryManager().getActiveNotificationUnfiltered(key); + return isLockscreenPublicMode(mCurrentUserId) && visibleEntry != null + && visibleEntry.getRanking().getVisibilityOverride() == VISIBILITY_SECRET; } public boolean shouldShowOnKeyguard(NotificationEntry entry) { @@ -326,8 +327,7 @@ public class NotificationLockscreenUserManagerImpl implements && hideSilentNotificationsOnLockscreen()) { exceedsPriorityThreshold = entry.getBucket() != BUCKET_SILENT; } else { - exceedsPriorityThreshold = - !getEntryManager().getNotificationData().isAmbient(entry.getKey()); + exceedsPriorityThreshold = !entry.getRanking().isAmbient(); } return mShowLockscreenNotifications && exceedsPriorityThreshold; } @@ -467,8 +467,9 @@ public class NotificationLockscreenUserManagerImpl implements Log.wtf(TAG, "mEntryManager was null!", new Throwable()); return true; } - return getEntryManager().getNotificationData().getVisibilityOverride(key) == - Notification.VISIBILITY_PRIVATE; + NotificationEntry entry = getEntryManager().getActiveNotificationUnfiltered(key); + return entry != null + && entry.getRanking().getVisibilityOverride() == Notification.VISIBILITY_PRIVATE; } private void updateCurrentProfilesCache() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index d668665f062c..a98f826c0284 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -102,8 +102,7 @@ public class NotificationMediaManager implements Dumpable { } - // Late binding - private NotificationEntryManager mEntryManager; + private final NotificationEntryManager mEntryManager; // Late binding, also @Nullable due to being in com.android.systemui.statusbar.phone package @Nullable @@ -258,8 +257,9 @@ public class NotificationMediaManager implements Dumpable { if (mMediaNotificationKey == null) { return null; } - synchronized (mEntryManager.getNotificationData()) { - NotificationEntry entry = mEntryManager.getNotificationData().get(mMediaNotificationKey); + synchronized (mEntryManager) { + NotificationEntry entry = mEntryManager + .getActiveNotificationUnfiltered(mMediaNotificationKey); if (entry == null || entry.expandedIcon == null) { return null; } @@ -281,8 +281,9 @@ public class NotificationMediaManager implements Dumpable { public void findAndUpdateMediaNotifications() { boolean metaDataChanged = false; - synchronized (mEntryManager.getNotificationData()) { - Set<NotificationEntry> allNotifications = mEntryManager.getAllNotifs(); + synchronized (mEntryManager) { + Set<NotificationEntry> allNotifications = + mEntryManager.getPendingAndActiveNotifications(); // 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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 35f06f9d95c8..e10d27b241cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -184,8 +184,9 @@ public class NotificationRemoteInputManager implements Dumpable { ViewGroup actionGroup = (ViewGroup) parent; buttonIndex = actionGroup.indexOfChild(view); } - final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); - final int rank = mEntryManager.getNotificationData().getRank(key); + final int count = mEntryManager.getActiveNotificationsCount(); + final int rank = mEntryManager + .getActiveNotificationUnfiltered(key).getRanking().getRank(); // Notification may be updated before this function is executed, and thus play safe // here and verify that the action object is still the one that where the click happens. @@ -202,7 +203,7 @@ public class NotificationRemoteInputManager implements Dumpable { } NotificationVisibility.NotificationLocation location = NotificationLogger.getNotificationLocation( - mEntryManager.getNotificationData().get(key)); + mEntryManager.getActiveNotificationUnfiltered(key)); final NotificationVisibility nv = NotificationVisibility.obtain(key, rank, count, true, location); try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 20a3e35791c4..ef733a967840 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -140,8 +140,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle Assert.isMainThread(); beginUpdate(); - ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData() - .getActiveNotifications(); + List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications(); ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { @@ -339,7 +338,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } for (ExpandableNotificationRow remove : toRemove) { parent.removeChildNotification(remove); - if (mEntryManager.getNotificationData().get( + if (mEntryManager.getActiveNotificationUnfiltered( remove.getStatusBarNotification().getKey()) == null) { // We only want to add an animation if the view is completely removed // otherwise it's just a transfer diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index 7bdb21d0eac5..40f8e394f054 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -73,8 +73,8 @@ public class SmartReplyController { public void smartActionClicked( NotificationEntry entry, int actionIndex, Notification.Action action, boolean generatedByAssistant) { - final int count = mEntryManager.getNotificationData().getActiveNotifications().size(); - final int rank = mEntryManager.getNotificationData().getRank(entry.getKey()); + final int count = mEntryManager.getActiveNotificationsCount(); + final int rank = entry.getRanking().getRank(); NotificationVisibility.NotificationLocation location = NotificationLogger.getNotificationLocation(entry); final NotificationVisibility nv = NotificationVisibility.obtain( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt index 314dc04e574f..015c32348bb0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/BypassHeadsUpNotifier.kt @@ -37,14 +37,14 @@ import javax.inject.Singleton */ @Singleton class BypassHeadsUpNotifier @Inject constructor( - private val context: Context, - private val bypassController: KeyguardBypassController, - private val statusBarStateController: StatusBarStateController, - private val headsUpManager: HeadsUpManagerPhone, - private val notificationLockscreenUserManager: NotificationLockscreenUserManager, - private val mediaManager: NotificationMediaManager, - tunerService: TunerService) : StatusBarStateController.StateListener, - NotificationMediaManager.MediaListener { + private val context: Context, + private val bypassController: KeyguardBypassController, + private val statusBarStateController: StatusBarStateController, + private val headsUpManager: HeadsUpManagerPhone, + private val notificationLockscreenUserManager: NotificationLockscreenUserManager, + private val mediaManager: NotificationMediaManager, + tunerService: TunerService +) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener { private lateinit var entryManager: NotificationEntryManager private var currentMediaEntry: NotificationEntry? = null @@ -77,7 +77,8 @@ class BypassHeadsUpNotifier @Inject constructor( override fun onMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) { val previous = currentMediaEntry - var newEntry = entryManager.notificationData.get(mediaManager.mediaNotificationKey) + var newEntry = entryManager + .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey) if (!NotificationMediaManager.isPlayingState(state)) { newEntry = null } @@ -101,7 +102,7 @@ class BypassHeadsUpNotifier @Inject constructor( */ private fun canAutoHeadsUp(entry: NotificationEntry): Boolean { if (!isAutoHeadsUpAllowed()) { - return false; + return false } if (entry.isSensitive) { // filter sensitive notifications @@ -111,7 +112,7 @@ class BypassHeadsUpNotifier @Inject constructor( // filter notifications invisible on Keyguard return false } - if (!entryManager.notificationData.activeNotifications.contains(entry)) { + if (entryManager.getActiveNotificationUnfiltered(entry.key) != null) { // filter notifications not the active list currently return false } @@ -125,7 +126,7 @@ class BypassHeadsUpNotifier @Inject constructor( /** * @return {@code true} if autoHeadsUp is possible right now. */ - private fun isAutoHeadsUpAllowed() : Boolean { + private fun isAutoHeadsUpAllowed(): Boolean { if (!enabled) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java index df78fa3fd4a4..06949208c2bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java @@ -99,7 +99,8 @@ public interface NotificationEntryListener { /** * Called whenever notification ranking changes, in response to * {@link NotificationListenerService#onNotificationRankingUpdate}. This is called after - * NotificationData has processed the update and notifications have been re-sorted and filtered. + * NotificationEntryManager has processed the update and notifications have been re-sorted + * and filtered. * * @param rankingMap provides access to ranking information on currently active notifications */ 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 13d90ffdfca2..7a58097f3ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -18,10 +18,12 @@ package com.android.systemui.statusbar.notification; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_ERROR; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.Log; @@ -36,9 +38,8 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.NotificationUpdateHandler; -import com.android.systemui.statusbar.notification.collection.NotificationData; -import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.logging.NotifEvent; import com.android.systemui.statusbar.notification.logging.NotifLog; @@ -46,12 +47,15 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationContentInflater; import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -62,9 +66,29 @@ import javax.inject.Inject; import javax.inject.Singleton; /** - * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. - * It also handles tasks such as their inflation and their interaction with other - * Notification.*Manager objects. + * NotificationEntryManager is responsible for the adding, removing, and updating of + * {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction + * with other Notification.*Manager objects. + * + * We track notification entries through this lifecycle: + * 1. Pending + * 2. Active + * 3. Sorted / filtered (visible) + * + * Every entry spends some amount of time in the pending state, while it is being inflated. Once + * inflated, an entry moves into the active state, where it _could_ potentially be shown to the + * user. After an entry makes its way into the active state, we sort and filter the entire set to + * repopulate the visible set. + * + * There are a few different things that other classes may be interested in, and most of them + * involve the current set of notifications. Here's a brief overview of things you may want to know: + * @see #getVisibleNotifications() for the visible set + * @see #getActiveNotificationUnfiltered(String) to check if a key exists + * @see #getPendingNotificationsIterator() for an iterator over the pending notifications + * @see #getPendingOrActiveNotif(String) to find a notification exists for that key in any list + * @see #getPendingAndActiveNotifications() to get the entire set of Notifications that we're + * aware of + * @see #getActiveNotificationsForCurrentUser() to see every notification that the current user owns */ @Singleton public class NotificationEntryManager implements @@ -78,12 +102,23 @@ public class NotificationEntryManager implements /** * Used when a notification is removed and it doesn't have a reason that maps to one of the * reasons defined in NotificationListenerService - * (e.g. {@link NotificationListenerService.REASON_CANCEL}) + * (e.g. {@link NotificationListenerService#REASON_CANCEL}) */ public static final int UNDEFINED_DISMISS_REASON = 0; + /** Pending notifications are ones awaiting inflation */ @VisibleForTesting protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>(); + /** + * Active notifications have been inflated / prepared and could become visible, but may get + * filtered out if for instance they are not for the current user + */ + private final ArrayMap<String, NotificationEntry> mActiveNotifications = new ArrayMap<>(); + @VisibleForTesting + /** This is the list of "active notifications for this user in this context" */ + protected final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>(); + private final List<NotificationEntry> mReadOnlyNotifications = + Collections.unmodifiableList(mSortedAndFiltered); private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications = new ArrayMap<>(); @@ -92,10 +127,12 @@ public class NotificationEntryManager implements private NotificationRemoteInputManager mRemoteInputManager; private NotificationRowBinder mNotificationRowBinder; + private final KeyguardEnvironment mKeyguardEnvironment; + private final NotificationGroupManager mGroupManager; + private final NotificationRankingManager mRankingManager; + private NotificationPresenter mPresenter; - private NotificationListenerService.RankingMap mLatestRankingMap; - @VisibleForTesting - protected NotificationData mNotificationData; + private RankingMap mLatestRankingMap; private NotifLog mNotifLog; @VisibleForTesting @@ -129,10 +166,14 @@ public class NotificationEntryManager implements @Inject public NotificationEntryManager( - NotificationData notificationData, - NotifLog notifLog) { - mNotificationData = notificationData; + NotifLog notifLog, + NotificationGroupManager groupManager, + NotificationRankingManager rankingManager, + KeyguardEnvironment keyguardEnvironment) { mNotifLog = notifLog; + mGroupManager = groupManager; + mRankingManager = rankingManager; + mKeyguardEnvironment = keyguardEnvironment; } /** Adds a {@link NotificationEntryListener}. */ @@ -171,7 +212,6 @@ public class NotificationEntryManager implements NotificationListContainer listContainer, HeadsUpManager headsUpManager) { mPresenter = presenter; - mNotificationData.setHeadsUpManager(headsUpManager); } /** Adds multiple {@link NotificationLifetimeExtender}s. */ @@ -188,10 +228,6 @@ public class NotificationEntryManager implements UNDEFINED_DISMISS_REASON)); } - public NotificationData getNotificationData() { - return mNotificationData; - } - @Override public void onReorderingAllowed() { updateNotifications("reordering is now allowed"); @@ -212,10 +248,17 @@ public class NotificationEntryManager implements } private NotificationVisibility obtainVisibility(String key) { - final int rank = mNotificationData.getRank(key); - final int count = mNotificationData.getActiveNotifications().size(); + NotificationEntry e = mActiveNotifications.get(key); + final int rank; + if (e != null) { + rank = e.getRanking().getRank(); + } else { + rank = 0; + } + + final int count = mActiveNotifications.size(); NotificationVisibility.NotificationLocation location = - NotificationLogger.getNotificationLocation(getNotificationData().get(key)); + NotificationLogger.getNotificationLocation(getActiveNotificationUnfiltered(key)); return NotificationVisibility.obtain(key, rank, count, true, location); } @@ -227,7 +270,7 @@ public class NotificationEntryManager implements mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry.getSbn(), null, "PendingNotification aborted. " + reason); } - NotificationEntry addedEntry = mNotificationData.get(key); + NotificationEntry addedEntry = getActiveNotificationUnfiltered(key); if (addedEntry != null) { addedEntry.abortTask(); mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.getSbn(), @@ -258,13 +301,13 @@ public class NotificationEntryManager implements // If there was an async task started after the removal, we don't want to add it back to // the list, otherwise we might get leaks. if (!entry.isRowRemoved()) { - boolean isNew = mNotificationData.get(entry.getKey()) == null; + boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null; if (isNew) { for (NotificationEntryListener listener : mNotificationEntryListeners) { mNotifLog.log(NotifEvent.INFLATED, entry); listener.onEntryInflated(entry, inflatedFlags); } - mNotificationData.add(entry); + addActiveNotification(entry); updateNotifications("onAsyncInflationFinished"); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onNotificationAdded(entry); @@ -278,8 +321,34 @@ public class NotificationEntryManager implements } } + /** + * Equivalent to the old NotificationData#add + * @param entry - an entry which is prepared for display + */ + private void addActiveNotification(NotificationEntry entry) { + Assert.isMainThread(); + + mActiveNotifications.put(entry.getKey(), entry); + mGroupManager.onEntryAdded(entry); + updateRankingAndSort(mRankingManager.getRankingMap(), "addEntryInternalInternal"); + } + + /** + * Available so that tests can directly manipulate the list of active notifications easily + * + * @param entry the entry to add directly to the visible notification map + */ + @VisibleForTesting + public void addActiveNotificationForTest(NotificationEntry entry) { + mActiveNotifications.put(entry.getKey(), entry); + mGroupManager.onEntryAdded(entry); + + reapplyFilterAndSort("addVisibleNotification"); + } + + @Override - public void removeNotification(String key, NotificationListenerService.RankingMap ranking, + public void removeNotification(String key, RankingMap ranking, int reason) { removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */, false /* removedByUser */, reason); @@ -287,7 +356,7 @@ public class NotificationEntryManager implements private void removeNotificationInternal( String key, - @Nullable NotificationListenerService.RankingMap ranking, + @Nullable RankingMap ranking, @Nullable NotificationVisibility visibility, boolean forceRemove, boolean removedByUser, @@ -300,7 +369,7 @@ public class NotificationEntryManager implements return; } - final NotificationEntry entry = mNotificationData.get(key); + final NotificationEntry entry = getActiveNotificationUnfiltered(key); boolean lifetimeExtended = false; // Notification was canceled before it got inflated @@ -355,8 +424,7 @@ public class NotificationEntryManager implements // Let's remove the children if this was a summary handleGroupSummaryRemoved(key); - - mNotificationData.remove(key, ranking); + removeVisibleNotification(key); updateNotifications("removeNotificationInternal"); Dependency.get(LeakDetector.class).trackGarbage(entry); removedByUser |= entryDismissed; @@ -381,7 +449,7 @@ public class NotificationEntryManager implements * */ private void handleGroupSummaryRemoved(String key) { - NotificationEntry entry = mNotificationData.get(key); + NotificationEntry entry = getActiveNotificationUnfiltered(key); if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) { if (entry.getSbn().getOverrideGroupKey() != null && !entry.isRowDismissed()) { // We don't want to remove children for autobundled notifications as they are not @@ -413,13 +481,14 @@ public class NotificationEntryManager implements } private void addNotificationInternal(StatusBarNotification notification, - NotificationListenerService.RankingMap rankingMap) throws InflationException { + RankingMap rankingMap) throws InflationException { String key = notification.getKey(); if (DEBUG) { Log.d(TAG, "addNotification key=" + key); } - mNotificationData.updateRanking(rankingMap, "addNotificationInternal"); + updateRankingAndSort(rankingMap, "addNotificationInternal"); + Ranking ranking = new Ranking(); rankingMap.getRanking(key, ranking); @@ -439,8 +508,7 @@ public class NotificationEntryManager implements } @Override - public void addNotification(StatusBarNotification notification, - NotificationListenerService.RankingMap ranking) { + public void addNotification(StatusBarNotification notification, RankingMap ranking) { try { addNotificationInternal(notification, ranking); } catch (InflationException e) { @@ -449,12 +517,12 @@ public class NotificationEntryManager implements } private void updateNotificationInternal(StatusBarNotification notification, - NotificationListenerService.RankingMap ranking) throws InflationException { + RankingMap ranking) throws InflationException { if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final String key = notification.getKey(); abortExistingInflation(key, "updateNotification"); - NotificationEntry entry = mNotificationData.get(key); + NotificationEntry entry = getActiveNotificationUnfiltered(key); if (entry == null) { return; } @@ -463,7 +531,11 @@ public class NotificationEntryManager implements // to keep its lifetime extended. cancelLifetimeExtension(entry); - mNotificationData.update(entry, ranking, notification, "updateNotificationInternal"); + updateRankingAndSort(ranking, "updateNotificationInternal"); + StatusBarNotification oldSbn = entry.getSbn(); + entry.setSbn(notification); + mGroupManager.onEntryUpdated(entry, oldSbn); + mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry.getSbn(), entry.getRanking()); for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onPreEntryUpdated(entry); @@ -486,8 +558,7 @@ public class NotificationEntryManager implements } @Override - public void updateNotification(StatusBarNotification notification, - NotificationListenerService.RankingMap ranking) { + public void updateNotification(StatusBarNotification notification, RankingMap ranking) { try { updateNotificationInternal(notification, ranking); } catch (InflationException e) { @@ -500,16 +571,16 @@ public class NotificationEntryManager implements * @param reason why the notifications are updating */ public void updateNotifications(String reason) { - mNotificationData.filterAndSort(reason); + reapplyFilterAndSort(reason); if (mPresenter != null) { mPresenter.updateNotificationViews(); } } @Override - public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) { + public void updateNotificationRanking(RankingMap rankingMap) { List<NotificationEntry> entries = new ArrayList<>(); - entries.addAll(mNotificationData.getActiveNotifications()); + entries.addAll(getVisibleNotifications()); entries.addAll(mPendingNotifications.values()); // Has a copy of the current UI adjustments. @@ -523,7 +594,7 @@ public class NotificationEntryManager implements } // Populate notification entries from the new rankings. - mNotificationData.updateRanking(rankingMap, "updateNotificationRanking"); + updateRankingAndSort(rankingMap, "updateNotificationRanking"); updateRankingOfPendingNotifications(rankingMap); // By comparing the old and new UI adjustments, reinflate the view accordingly. @@ -542,8 +613,7 @@ public class NotificationEntryManager implements } } - private void updateRankingOfPendingNotifications( - @Nullable NotificationListenerService.RankingMap rankingMap) { + private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) { if (rankingMap == null) { return; } @@ -565,23 +635,35 @@ public class NotificationEntryManager implements } /** - * @return all notification we're currently aware of (both pending and visible notifications) + * @return all notifications we're currently aware of (both pending and active notifications) */ - public Set<NotificationEntry> getAllNotifs() { + public Set<NotificationEntry> getPendingAndActiveNotifications() { Set<NotificationEntry> allNotifs = new HashSet<>(mPendingNotifications.values()); - allNotifs.addAll(mNotificationData.getActiveNotifications()); + 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. + * + * @see #getVisibleNotifications() for the currently sorted and filtered list + * + * @return a {@link NotificationEntry} if it has been prepared, else null + */ + public NotificationEntry getActiveNotificationUnfiltered(String key) { + return mActiveNotifications.get(key); + } + + /** * Gets the pending or visible notification entry with the given key. Returns null if * notification doesn't exist. */ - public NotificationEntry getPendingOrCurrentNotif(String key) { + public NotificationEntry getPendingOrActiveNotif(String key) { if (mPendingNotifications.containsKey(key)) { return mPendingNotifications.get(key); } else { - return mNotificationData.get(key); + return mActiveNotifications.get(key); } } @@ -608,4 +690,136 @@ public class NotificationEntryManager implements } return mNotificationRowBinder; } + + /* + * ----- + * Annexed from NotificationData below: + * Some of these methods may be redundant but require some reworking to remove. For now + * we'll try to keep the behavior the same and can simplify these interfaces in another pass + */ + + /** Internalization of NotificationData#remove */ + private void removeVisibleNotification(String key) { + // no need to synchronize if we're on the main thread dawg + Assert.isMainThread(); + + NotificationEntry removed = mActiveNotifications.remove(key); + + if (removed == null) return; + mGroupManager.onEntryRemoved(removed); + } + + /** @return list of active notifications filtered for the current user */ + public List<NotificationEntry> getActiveNotificationsForCurrentUser() { + Assert.isMainThread(); + ArrayList<NotificationEntry> filtered = new ArrayList<>(); + + final int len = mActiveNotifications.size(); + for (int i = 0; i < len; i++) { + NotificationEntry entry = mActiveNotifications.valueAt(i); + final StatusBarNotification sbn = entry.getSbn(); + if (!mKeyguardEnvironment.isNotificationForCurrentProfiles(sbn)) { + continue; + } + filtered.add(entry); + } + + return filtered; + } + + //TODO: Get rid of this in favor of NotificationUpdateHandler#updateNotificationRanking + /** + * @param rankingMap the {@link RankingMap} to apply to the current notification list + * @param reason the reason for calling this method, for {@link NotifLog} + */ + public void updateRanking(RankingMap rankingMap, String reason) { + updateRankingAndSort(rankingMap, reason); + } + + /** Resorts / filters the current notification set with the current RankingMap */ + public void reapplyFilterAndSort(String reason) { + updateRankingAndSort(mRankingManager.getRankingMap(), reason); + } + + /** Calls to NotificationRankingManager and updates mSortedAndFiltered */ + private void updateRankingAndSort(@NonNull RankingMap rankingMap, String reason) { + mSortedAndFiltered.clear(); + mSortedAndFiltered.addAll(mRankingManager.updateRanking( + rankingMap, mActiveNotifications.values(), reason)); + } + + /** dump the current active notification list. Called from StatusBar */ + public void dump(PrintWriter pw, String indent) { + pw.println("NotificationEntryManager"); + int filteredLen = mSortedAndFiltered.size(); + pw.print(indent); + pw.println("active notifications: " + filteredLen); + int active; + for (active = 0; active < filteredLen; active++) { + NotificationEntry e = mSortedAndFiltered.get(active); + dumpEntry(pw, indent, active, e); + } + synchronized (mActiveNotifications) { + int totalLen = mActiveNotifications.size(); + pw.print(indent); + pw.println("inactive notifications: " + (totalLen - active)); + int inactiveCount = 0; + for (int i = 0; i < totalLen; i++) { + NotificationEntry entry = mActiveNotifications.valueAt(i); + if (!mSortedAndFiltered.contains(entry)) { + dumpEntry(pw, indent, inactiveCount, entry); + inactiveCount++; + } + } + } + } + + private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) { + pw.print(indent); + pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.icon); + StatusBarNotification n = e.getSbn(); + pw.print(indent); + pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" + + e.getRanking().getImportance()); + pw.print(indent); + pw.println(" notification=" + n.getNotification()); + } + + /** + * This is the answer to the question "what notifications should the user be seeing right now?" + * These are sorted and filtered, and directly inform the notification shade what to show + * + * @return A read-only list of the currently active notifications + */ + public List<NotificationEntry> getVisibleNotifications() { + return mReadOnlyNotifications; + } + + /** @return A count of the active notifications */ + public int getActiveNotificationsCount() { + return mReadOnlyNotifications.size(); + } + + /** + * @return {@code true} if there is at least one notification that should be visible right now + */ + public boolean hasActiveNotifications() { + return mReadOnlyNotifications.size() != 0; + } + + /* + * End annexation + * ----- + */ + + + /** + * Provides access to keyguard state and user settings dependent data. + */ + public interface KeyguardEnvironment { + /** true if the device is provisioned (should always be true in practice) */ + boolean isDeviceProvisioned(); + /** true if the notification is for the current profiles */ + boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index b1164093acdd..e5f44bd3b9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -28,7 +28,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; @@ -44,7 +43,7 @@ public class NotificationFilter { private final NotificationGroupManager mGroupManager = Dependency.get( NotificationGroupManager.class); - private NotificationData.KeyguardEnvironment mEnvironment; + private NotificationEntryManager.KeyguardEnvironment mEnvironment; private ShadeController mShadeController; private ForegroundServiceController mFsc; private NotificationLockscreenUserManager mUserManager; @@ -52,9 +51,9 @@ public class NotificationFilter { @Inject public NotificationFilter() {} - private NotificationData.KeyguardEnvironment getEnvironment() { + private NotificationEntryManager.KeyguardEnvironment getEnvironment() { if (mEnvironment == null) { - mEnvironment = Dependency.get(NotificationData.KeyguardEnvironment.class); + mEnvironment = Dependency.get(NotificationEntryManager.KeyguardEnvironment.class); } return mEnvironment; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java deleted file mode 100644 index a0229d16d6eb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.Person; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.NotificationListenerService.RankingMap; -import android.service.notification.SnoozeCriterion; -import android.service.notification.StatusBarNotification; -import android.util.ArrayMap; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.notification.NotificationFilter; -import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.policy.HeadsUpManager; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; - -import javax.inject.Inject; - -/** - * The list of currently displaying notifications. - */ -public class NotificationData { - private static final String TAG = "NotificationData"; - - private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class); - - /** - * These dependencies are late init-ed - */ - private KeyguardEnvironment mEnvironment; - private NotificationMediaManager mMediaManager; - - private HeadsUpManager mHeadsUpManager; - - private final ArrayMap<String, NotificationEntry> mEntries = new ArrayMap<>(); - private final ArrayList<NotificationEntry> mSortedAndFiltered = new ArrayList<>(); - - private final NotificationGroupManager mGroupManager = - Dependency.get(NotificationGroupManager.class); - - private RankingMap mRankingMap; - private final Ranking mTmpRanking = new Ranking(); - private final boolean mUsePeopleFiltering; - private final NotifLog mNotifLog; - private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; - - @Inject - public NotificationData( - NotificationSectionsFeatureManager sectionsFeatureManager, - NotifLog notifLog, - PeopleNotificationIdentifier peopleNotificationIdentifier) { - mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled(); - mNotifLog = notifLog; - mPeopleNotificationIdentifier = peopleNotificationIdentifier; - } - - public void setHeadsUpManager(HeadsUpManager headsUpManager) { - mHeadsUpManager = headsUpManager; - } - - @VisibleForTesting - protected final Comparator<NotificationEntry> mRankingComparator = - new Comparator<NotificationEntry>() { - @Override - public int compare(NotificationEntry a, NotificationEntry b) { - final StatusBarNotification na = a.getSbn(); - final StatusBarNotification nb = b.getSbn(); - int aRank = getRank(a.getKey()); - int bRank = getRank(b.getKey()); - - boolean aPeople = isPeopleNotification(a); - boolean bPeople = isPeopleNotification(b); - - boolean aMedia = isImportantMedia(a); - boolean bMedia = isImportantMedia(b); - - boolean aSystemMax = isSystemMax(a); - boolean bSystemMax = isSystemMax(b); - - boolean aHeadsUp = a.isRowHeadsUp(); - boolean bHeadsUp = b.isRowHeadsUp(); - - if (mUsePeopleFiltering && aPeople != bPeople) { - return aPeople ? -1 : 1; - } else if (aHeadsUp != bHeadsUp) { - return aHeadsUp ? -1 : 1; - } else if (aHeadsUp) { - // Provide consistent ranking with headsUpManager - return mHeadsUpManager.compare(a, b); - } else if (aMedia != bMedia) { - // Upsort current media notification. - return aMedia ? -1 : 1; - } else if (aSystemMax != bSystemMax) { - // Upsort PRIORITY_MAX system notifications - return aSystemMax ? -1 : 1; - } else if (a.isHighPriority() != b.isHighPriority()) { - return -1 * Boolean.compare(a.isHighPriority(), b.isHighPriority()); - } else if (aRank != bRank) { - return aRank - bRank; - } else { - return Long.compare(nb.getNotification().when, na.getNotification().when); - } - } - }; - - private KeyguardEnvironment getEnvironment() { - if (mEnvironment == null) { - mEnvironment = Dependency.get(KeyguardEnvironment.class); - } - return mEnvironment; - } - - private NotificationMediaManager getMediaManager() { - if (mMediaManager == null) { - mMediaManager = Dependency.get(NotificationMediaManager.class); - } - return mMediaManager; - } - - /** - * Returns the sorted list of active notifications (depending on {@link KeyguardEnvironment} - * - * <p> - * This call doesn't update the list of active notifications. Call {@link #filterAndSort()} - * when the environment changes. - * <p> - * Don't hold on to or modify the returned list. - */ - public ArrayList<NotificationEntry> getActiveNotifications() { - return mSortedAndFiltered; - } - - public ArrayList<NotificationEntry> getNotificationsForCurrentUser() { - synchronized (mEntries) { - final int len = mEntries.size(); - ArrayList<NotificationEntry> filteredForUser = new ArrayList<>(len); - - for (int i = 0; i < len; i++) { - NotificationEntry entry = mEntries.valueAt(i); - final StatusBarNotification sbn = entry.getSbn(); - if (!getEnvironment().isNotificationForCurrentProfiles(sbn)) { - continue; - } - filteredForUser.add(entry); - } - return filteredForUser; - } - } - - public NotificationEntry get(String key) { - return mEntries.get(key); - } - - public void add(NotificationEntry entry) { - synchronized (mEntries) { - mEntries.put(entry.getSbn().getKey(), entry); - } - mGroupManager.onEntryAdded(entry); - - updateRankingAndSort(mRankingMap, "addEntry=" + entry.getSbn()); - } - - public NotificationEntry remove(String key, RankingMap ranking) { - NotificationEntry removed; - synchronized (mEntries) { - removed = mEntries.remove(key); - } - if (removed == null) return null; - mGroupManager.onEntryRemoved(removed); - updateRankingAndSort(ranking, "removeEntry=" + removed.getSbn()); - return removed; - } - - /** Updates the given notification entry with the provided ranking. */ - public void update( - NotificationEntry entry, - RankingMap ranking, - StatusBarNotification notification, - String reason) { - updateRanking(ranking, reason); - final StatusBarNotification oldNotification = entry.getSbn(); - entry.setSbn(notification); - mGroupManager.onEntryUpdated(entry, oldNotification); - } - - /** - * Update ranking and trigger a re-sort - */ - public void updateRanking(RankingMap ranking, String reason) { - updateRankingAndSort(ranking, reason); - } - - /** - * Returns true if this notification should be displayed in the high-priority notifications - * section - */ - public boolean isHighPriority(StatusBarNotification statusBarNotification) { - if (mRankingMap != null) { - getRanking(statusBarNotification.getKey(), mTmpRanking); - if (mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || hasHighPriorityCharacteristics( - mTmpRanking.getChannel(), statusBarNotification)) { - return true; - } - if (mGroupManager.isSummaryOfGroup(statusBarNotification)) { - final ArrayList<NotificationEntry> logicalChildren = - mGroupManager.getLogicalChildren(statusBarNotification); - for (NotificationEntry child : logicalChildren) { - if (isHighPriority(child.getSbn())) { - return true; - } - } - } - } - return false; - } - - private boolean hasHighPriorityCharacteristics(NotificationChannel channel, - StatusBarNotification statusBarNotification) { - - if (isImportantOngoing(statusBarNotification.getNotification()) - || statusBarNotification.getNotification().hasMediaSession() - || hasPerson(statusBarNotification.getNotification()) - || hasStyle(statusBarNotification.getNotification(), - Notification.MessagingStyle.class)) { - // Users who have long pressed and demoted to silent should not see the notification - // in the top section - if (channel != null && channel.hasUserSetImportance()) { - return false; - } - return true; - } - - return false; - } - - private boolean isImportantOngoing(Notification notification) { - return notification.isForegroundService() - && mTmpRanking.getImportance() >= NotificationManager.IMPORTANCE_LOW; - } - - private boolean hasStyle(Notification notification, Class targetStyle) { - Class<? extends Notification.Style> style = notification.getNotificationStyle(); - return targetStyle.equals(style); - } - - private boolean hasPerson(Notification notification) { - // TODO: cache favorite and recent contacts to check contact affinity - ArrayList<Person> people = notification.extras != null - ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) - : new ArrayList<>(); - return people != null && !people.isEmpty(); - } - - public boolean isAmbient(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.isAmbient(); - } - return false; - } - - public int getVisibilityOverride(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getVisibilityOverride(); - } - return Ranking.VISIBILITY_NO_OVERRIDE; - } - - public List<SnoozeCriterion> getSnoozeCriteria(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getSnoozeCriteria(); - } - return null; - } - - public NotificationChannel getChannel(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getChannel(); - } - return null; - } - - public int getRank(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.getRank(); - } - return 0; - } - - private boolean isImportantMedia(NotificationEntry e) { - int importance = e.getRanking().getImportance(); - boolean media = e.getKey().equals(getMediaManager().getMediaNotificationKey()) - && importance > NotificationManager.IMPORTANCE_MIN; - - return media; - } - - private boolean isSystemMax(NotificationEntry e) { - int importance = e.getRanking().getImportance(); - boolean sys = importance >= NotificationManager.IMPORTANCE_HIGH - && isSystemNotification(e.getSbn()); - - return sys; - } - - public boolean shouldHide(String key) { - if (mRankingMap != null) { - getRanking(key, mTmpRanking); - return mTmpRanking.isSuspended(); - } - return false; - } - - private void updateRankingAndSort(RankingMap rankingMap, String reason) { - if (rankingMap != null) { - mRankingMap = rankingMap; - synchronized (mEntries) { - final int len = mEntries.size(); - for (int i = 0; i < len; i++) { - NotificationEntry entry = mEntries.valueAt(i); - Ranking newRanking = new Ranking(); - if (!getRanking(entry.getKey(), newRanking)) { - continue; - } - entry.setRanking(newRanking); - - final StatusBarNotification oldSbn = entry.getSbn().cloneLight(); - final String overrideGroupKey = newRanking.getOverrideGroupKey(); - if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) { - entry.getSbn().setOverrideGroupKey(overrideGroupKey); - mGroupManager.onEntryUpdated(entry, oldSbn); - } - entry.setIsHighPriority(isHighPriority(entry.getSbn())); - } - } - } - filterAndSort(reason); - } - - /** - * Get the ranking from the current ranking map. - * - * @param key the key to look up - * @param outRanking the ranking to populate - * - * @return {@code true} if the ranking was properly obtained. - */ - @VisibleForTesting - protected boolean getRanking(String key, Ranking outRanking) { - return mRankingMap.getRanking(key, outRanking); - } - - // TODO: This should not be public. Instead the Environment should notify this class when - // anything changed, and this class should call back the UI so it updates itself. - /** - * Filters and sorts the list of notification entries - */ - public void filterAndSort(String reason) { - mNotifLog.log(NotifEvent.FILTER_AND_SORT, reason); - mSortedAndFiltered.clear(); - - synchronized (mEntries) { - final int len = mEntries.size(); - for (int i = 0; i < len; i++) { - NotificationEntry entry = mEntries.valueAt(i); - - if (mNotificationFilter.shouldFilterOut(entry)) { - continue; - } - - mSortedAndFiltered.add(entry); - } - } - - Collections.sort(mSortedAndFiltered, mRankingComparator); - - int bucket = BUCKET_PEOPLE; - for (NotificationEntry e : mSortedAndFiltered) { - assignBucketForEntry(e); - if (e.getBucket() < bucket) { - android.util.Log.wtf(TAG, "Detected non-contiguous bucket!"); - } - bucket = e.getBucket(); - } - } - - private void assignBucketForEntry(NotificationEntry e) { - boolean isHeadsUp = e.isRowHeadsUp(); - boolean isMedia = isImportantMedia(e); - boolean isSystemMax = isSystemMax(e); - - setBucket(e, isHeadsUp, isMedia, isSystemMax); - } - - private void setBucket( - NotificationEntry e, - boolean isHeadsUp, - boolean isMedia, - boolean isSystemMax) { - if (mUsePeopleFiltering && isPeopleNotification(e)) { - e.setBucket(BUCKET_PEOPLE); - } else if (isHeadsUp || isMedia || isSystemMax || e.isHighPriority()) { - e.setBucket(BUCKET_ALERTING); - } else { - e.setBucket(BUCKET_SILENT); - } - } - - private boolean isPeopleNotification(NotificationEntry e) { - return mPeopleNotificationIdentifier.isPeopleNotification(e.getSbn()); - } - - public void dump(PrintWriter pw, String indent) { - int filteredLen = mSortedAndFiltered.size(); - pw.print(indent); - pw.println("active notifications: " + filteredLen); - int active; - for (active = 0; active < filteredLen; active++) { - NotificationEntry e = mSortedAndFiltered.get(active); - dumpEntry(pw, indent, active, e); - } - synchronized (mEntries) { - int totalLen = mEntries.size(); - pw.print(indent); - pw.println("inactive notifications: " + (totalLen - active)); - int inactiveCount = 0; - for (int i = 0; i < totalLen; i++) { - NotificationEntry entry = mEntries.valueAt(i); - if (!mSortedAndFiltered.contains(entry)) { - dumpEntry(pw, indent, inactiveCount, entry); - inactiveCount++; - } - } - } - } - - private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) { - getRanking(e.getKey(), mTmpRanking); - pw.print(indent); - pw.println(" [" + i + "] key=" + e.getKey() + " icon=" + e.icon); - StatusBarNotification n = e.getSbn(); - pw.print(indent); - pw.println(" pkg=" + n.getPackageName() + " id=" + n.getId() + " importance=" - + mTmpRanking.getImportance()); - pw.print(indent); - pw.println(" notification=" + n.getNotification()); - } - - private static boolean isSystemNotification(StatusBarNotification sbn) { - String sbnPackage = sbn.getPackageName(); - return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage); - } - - /** - * Provides access to keyguard state and user settings dependent data. - */ - public interface KeyguardEnvironment { - boolean isDeviceProvisioned(); - boolean isNotificationForCurrentProfiles(StatusBarNotification sbn); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt new file mode 100644 index 000000000000..8bce528bab8c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2019 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 + +import android.app.Notification +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.IMPORTANCE_LOW +import android.app.NotificationManager.IMPORTANCE_MIN +import android.app.Person +import android.service.notification.NotificationListenerService.Ranking +import android.service.notification.NotificationListenerService.RankingMap +import android.service.notification.StatusBarNotification +import com.android.internal.annotations.VisibleForTesting + +import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationFilter +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.logging.NotifEvent +import com.android.systemui.statusbar.notification.logging.NotifLog +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT +import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.statusbar.policy.HeadsUpManager + +import java.util.Objects +import java.util.ArrayList + +import javax.inject.Inject + +import kotlin.Comparator + +import dagger.Lazy + +private const val TAG = "NotifRankingManager" + +/** + * NotificationRankingManager is responsible for holding on to the most recent [RankingMap], and + * updating SystemUI's set of [NotificationEntry]s with their own ranking. It also sorts and filters + * a set of entries (but retains none of them). We also set buckets on the entries here since + * bucketing is tied closely to sorting. + * + * For the curious: this class is one iteration closer to null of what used to be called + * NotificationData.java. + */ +open class NotificationRankingManager @Inject constructor( + private val mediaManagerLazy: Lazy<NotificationMediaManager>, + private val groupManager: NotificationGroupManager, + private val headsUpManager: HeadsUpManager, + private val notifFilter: NotificationFilter, + private val notifLog: NotifLog, + sectionsFeatureManager: NotificationSectionsFeatureManager +) { + + var rankingMap: RankingMap? = null + protected set + private val mediaManager by lazy { + mediaManagerLazy.get() + } + private val usePeopleFiltering: Boolean = sectionsFeatureManager.isFilteringEnabled() + private val rankingComparator: Comparator<NotificationEntry> = Comparator { a, b -> + val na = a.sbn + val nb = b.sbn + val aRank = a.ranking.rank + val bRank = b.ranking.rank + + val aMedia = isImportantMedia(a) + val bMedia = isImportantMedia(b) + + val aSystemMax = a.isSystemMax() + val bSystemMax = b.isSystemMax() + + val aHeadsUp = a.isRowHeadsUp + val bHeadsUp = b.isRowHeadsUp + + if (usePeopleFiltering && a.isPeopleNotification() != b.isPeopleNotification()) { + if (a.isPeopleNotification()) -1 else 1 + } else if (aHeadsUp != bHeadsUp) { + if (aHeadsUp) -1 else 1 + } else if (aHeadsUp) { + // Provide consistent ranking with headsUpManager + headsUpManager.compare(a, b) + } else if (aMedia != bMedia) { + // Upsort current media notification. + if (aMedia) -1 else 1 + } else if (aSystemMax != bSystemMax) { + // Upsort PRIORITY_MAX system notifications + if (aSystemMax) -1 else 1 + } else if (a.isHighPriority != b.isHighPriority) { + -1 * java.lang.Boolean.compare(a.isHighPriority, b.isHighPriority) + } else if (aRank != bRank) { + aRank - bRank + } else { + nb.notification.`when`.compareTo(na.notification.`when`) + } + } + + private fun isImportantMedia(entry: NotificationEntry): Boolean { + val importance = entry.ranking.importance + return entry.key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN + } + + @VisibleForTesting + protected fun isHighPriority(entry: NotificationEntry): Boolean { + if (entry.importance >= IMPORTANCE_DEFAULT || + hasHighPriorityCharacteristics(entry)) { + return true + } + + if (groupManager.isSummaryOfGroup(entry.sbn)) { + val logicalChildren = groupManager.getLogicalChildren(entry.sbn) + for (child in logicalChildren) { + if (isHighPriority(child)) { + return true + } + } + } + + return false + } + + private fun hasHighPriorityCharacteristics(entry: NotificationEntry): Boolean { + val c = entry.channel + val n = entry.sbn.notification + + if (((n.isForegroundService && entry.ranking.importance >= IMPORTANCE_LOW) || + n.hasMediaSession() || + n.hasPerson() || + n.hasStyle(Notification.MessagingStyle::class.java))) { + // Users who have long pressed and demoted to silent should not see the notification + // in the top section + if (c != null && c.hasUserSetImportance()) { + return false + } + + return true + } + + return false + } + + fun updateRanking( + newRankingMap: RankingMap?, + entries: Collection<NotificationEntry>, + reason: String + ): List<NotificationEntry> { + val eSeq = entries.asSequence() + + // TODO: may not be ideal to guard on null here, but this code is implementing exactly what + // NotificationData used to do + if (newRankingMap != null) { + rankingMap = newRankingMap + updateRankingForEntries(eSeq) + } + + val filtered: Sequence<NotificationEntry> + synchronized(this) { + filtered = filterAndSortLocked(eSeq, reason) + } + + return filtered.toList() + } + + /** Uses the [rankingComparator] to sort notifications which aren't filtered */ + private fun filterAndSortLocked( + entries: Sequence<NotificationEntry>, + reason: String + ): Sequence<NotificationEntry> { + notifLog.log(NotifEvent.FILTER_AND_SORT, reason) + + return entries.filter { !notifFilter.shouldFilterOut(it) } + .sortedWith(rankingComparator) + .map { + assignBucketForEntry(it) + it + } + } + + private fun assignBucketForEntry(entry: NotificationEntry) { + val isHeadsUp = entry.isRowHeadsUp + val isMedia = isImportantMedia(entry) + val isSystemMax = entry.isSystemMax() + setBucket(entry, isHeadsUp, isMedia, isSystemMax) + } + + private fun setBucket( + entry: NotificationEntry, + isHeadsUp: Boolean, + isMedia: Boolean, + isSystemMax: Boolean + ) { + if (usePeopleFiltering && entry.hasAssociatedPeople()) { + entry.bucket = BUCKET_PEOPLE + } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) { + entry.bucket = BUCKET_ALERTING + } else { + entry.bucket = BUCKET_SILENT + } + } + + private fun updateRankingForEntries(entries: Sequence<NotificationEntry>) { + rankingMap?.let { rankingMap -> + synchronized(entries) { + entries.forEach { entry -> + val newRanking = Ranking() + if (!rankingMap.getRanking(entry.key, newRanking)) { + return@forEach + } + entry.ranking = newRanking + + val oldSbn = entry.sbn.cloneLight() + val newOverrideGroupKey = newRanking.overrideGroupKey + if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) { + entry.sbn.overrideGroupKey = newOverrideGroupKey + // TODO: notify group manager here? + groupManager.onEntryUpdated(entry, oldSbn) + } + entry.setIsHighPriority(isHighPriority(entry)) + } + } + } + } +} + +// Convenience functions +private fun NotificationEntry.isSystemMax(): Boolean { + return importance >= IMPORTANCE_HIGH && sbn.isSystemNotification() +} + +private fun StatusBarNotification.isSystemNotification(): Boolean { + return "android" == packageName || "com.android.systemui" == packageName +} + +private fun Notification.hasPerson(): Boolean { + val people: ArrayList<Person> = + (extras?.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)) ?: ArrayList() + return people.isNotEmpty() +} + +private fun Notification.hasStyle(targetStyleClass: Class<*>): Boolean { + return targetStyleClass == notificationStyle +} + +private fun NotificationEntry.isPeopleNotification(): Boolean = + sbn.notification.hasStyle(Notification.MessagingStyle::class.java) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 52fd07937546..1c0a9d4f14af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -32,7 +32,6 @@ import android.view.ViewGroup; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.UiOffloadThread; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -61,7 +60,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Dependency.get(NotificationGroupManager.class); private final NotificationGutsManager mGutsManager = Dependency.get(NotificationGutsManager.class); - private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = Dependency.get(NotificationInterruptionStateProvider.class); @@ -81,16 +79,20 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; - private final NotificationLogger mNotificationLogger = Dependency.get(NotificationLogger.class); + private final NotificationLogger mNotificationLogger; - public NotificationRowBinderImpl(Context context, boolean allowLongPress, + public NotificationRowBinderImpl( + Context context, + boolean allowLongPress, KeyguardBypassController keyguardBypassController, - StatusBarStateController statusBarStateController) { + StatusBarStateController statusBarStateController, + NotificationLogger logger) { mContext = context; mMessagingUtil = new NotificationMessagingUtil(context); mAllowLongPress = allowLongPress; mKeyguardBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; + mNotificationLogger = logger; } private NotificationRemoteInputManager getRemoteInputManager() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 90c5502bd119..77ccf19f65ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -43,9 +43,9 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.policy.HeadsUpManager; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import javax.inject.Inject; @@ -113,7 +113,7 @@ public class NotificationLogger implements StateListener { public void run() { mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis(); - // 1. Loop over mNotificationData entries: + // 1. Loop over active entries: // A. Keep list of visible notifications. // B. Keep list of previously hidden, now visible notifications. // 2. Compute no-longer visible notifications by removing currently @@ -121,8 +121,7 @@ public class NotificationLogger implements StateListener { // notifications. // 3. Report newly visible and no-longer visible notifications. // 4. Keep currently visible notifications for next report. - ArrayList<NotificationEntry> activeNotifications = mEntryManager - .getNotificationData().getActiveNotifications(); + List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications(); int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationEntry entry = activeNotifications.get(i); @@ -403,7 +402,7 @@ public class NotificationLogger implements StateListener { */ public void onExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { NotificationVisibility.NotificationLocation location = - getNotificationLocation(mEntryManager.getNotificationData().get(key)); + getNotificationLocation(mEntryManager.getActiveNotificationUnfiltered(key)); mExpansionStateLogger.onExpansionChanged(key, isUserAction, isExpanded, location); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 0d8e30ef6988..462fa59c785c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -696,8 +696,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooter() { boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL); - boolean showFooterView = (showDismissView || - mEntryManager.getNotificationData().getActiveNotifications().size() != 0) + boolean showFooterView = (showDismissView || mEntryManager.hasActiveNotifications()) && mStatusBarState != StatusBarState.KEYGUARD && !mRemoteInputManager.getController().isRemoteInputActive(); @@ -5787,11 +5786,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public boolean hasActiveNotifications() { - return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); - } - - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateSpeedBumpIndex() { int speedBumpIndex = 0; int currentIndex = 0; @@ -6400,7 +6394,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public boolean onDraggedDown(View startingChild, int dragLengthY) { if (mStatusBarState == StatusBarState.KEYGUARD - && hasActiveNotifications()) { + && mEntryManager.hasActiveNotifications()) { mLockscreenGestureLogger.write( MetricsEvent.ACTION_LS_SHADE, (int) (dragLengthY / mDisplayMetrics.density), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java index 2c931ae1c8ef..e763496da859 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardEnvironmentImpl.java @@ -22,7 +22,7 @@ import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; +import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import javax.inject.Inject; @@ -42,12 +42,12 @@ public class KeyguardEnvironmentImpl implements KeyguardEnvironment { public KeyguardEnvironmentImpl() { } - @Override // NotificationData.KeyguardEnvironment + @Override // NotificationEntryManager.KeyguardEnvironment public boolean isDeviceProvisioned() { return mDeviceProvisionedController.isDeviceProvisioned(); } - @Override // NotificationData.KeyguardEnvironment + @Override // NotificationEntryManager.KeyguardEnvironment public boolean isNotificationForCurrentProfiles(StatusBarNotification n) { final int notificationUserId = n.getUserId(); if (DEBUG && MULTIUSER_DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java index 93887a6617f9..5703f0653398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java @@ -97,7 +97,7 @@ public class LightsOutNotifController { } private boolean hasActiveNotifications() { - return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); + return mEntryManager.hasActiveNotifications(); } @VisibleForTesting 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 1e10b6f025f2..3554b54db99b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -15,7 +15,6 @@ import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; @@ -26,7 +25,6 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -48,7 +46,6 @@ public class NotificationIconAreaController implements DarkReceiver, private static final long AOD_ICONS_APPEAR_DURATION = 200; private final ContrastColorUtil mContrastColorUtil; - private final NotificationEntryManager mEntryManager; private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons; private final StatusBarStateController mStatusBarStateController; private final NotificationMediaManager mMediaManager; @@ -91,7 +88,6 @@ public class NotificationIconAreaController implements DarkReceiver, mStatusBar = statusBar; mContrastColorUtil = ContrastColorUtil.getInstance(context); mContext = context; - mEntryManager = Dependency.get(NotificationEntryManager.class); mStatusBarStateController = statusBarStateController; mStatusBarStateController.addCallback(this); mMediaManager = notificationMediaManager; @@ -247,7 +243,7 @@ public class NotificationIconAreaController implements DarkReceiver, if (hideCenteredIcon && isCenteredNotificationIcon && !entry.isRowHeadsUp()) { return false; } - if (mEntryManager.getNotificationData().isAmbient(entry.getKey()) && !showAmbient) { + if (entry.getRanking().isAmbient() && !showAmbient) { return false; } if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 6839fb432c0a..6176cff82f6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -3562,8 +3562,7 @@ public class NotificationPanelView extends PanelView implements private void updateShowEmptyShadeView() { boolean showEmptyShadeView = - mBarState != StatusBarState.KEYGUARD && - mEntryManager.getNotificationData().getActiveNotifications().size() == 0; + mBarState != StatusBarState.KEYGUARD && mEntryManager.hasActiveNotifications(); showEmptyShadeView(showEmptyShadeView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 0e1985de4371..adea8c6aa014 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1228,7 +1228,8 @@ public class StatusBar extends SystemUI implements DemoMode, mContext, mAllowNotificationLongPress, mKeyguardBypassController, - mStatusBarStateController); + mStatusBarStateController, + mNotificationLogger); mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel, mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController, @@ -2494,8 +2495,8 @@ public class StatusBar extends SystemUI implements DemoMode, } if (DUMPTRUCK) { - synchronized (mEntryManager.getNotificationData()) { - mEntryManager.getNotificationData().dump(pw, " "); + synchronized (mEntryManager) { + mEntryManager.dump(pw, " "); } if (false) { @@ -2753,11 +2754,7 @@ public class StatusBar extends SystemUI implements DemoMode, }; public void resetUserExpandedStates() { - ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData() - .getActiveNotifications(); - final int notificationCount = activeNotifications.size(); - for (int i = 0; i < notificationCount; i++) { - NotificationEntry entry = activeNotifications.get(i); + for (NotificationEntry entry : mEntryManager.getVisibleNotifications()) { entry.resetUserExpansion(); } } @@ -2857,8 +2854,7 @@ public class StatusBar extends SystemUI implements DemoMode, try { // consider the transition from peek to expanded to be a panel open, // but not one that clears notification effects. - int notificationLoad = mEntryManager.getNotificationData() - .getActiveNotifications().size(); + int notificationLoad = mEntryManager.getActiveNotificationsCount(); mBarService.onPanelRevealed(false, notificationLoad); } catch (RemoteException ex) { // Won't fail unless the world has ended. @@ -2876,8 +2872,7 @@ public class StatusBar extends SystemUI implements DemoMode, !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mEntryManager.getNotificationData().getActiveNotifications() - .size(); + int notificationLoad = mEntryManager.getActiveNotificationsCount(); if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { notificationLoad = 1; } @@ -3862,10 +3857,6 @@ public class StatusBar extends SystemUI implements DemoMode, mScreenPinningRequest.showPrompt(taskId, allowCancel); } - public boolean hasActiveNotifications() { - return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); - } - @Override public void appTransitionCancelled(int displayId) { if (displayId == mDisplayId) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 64a45e16d749..1cf43cc310ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -326,12 +326,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit collapseOnMainThread(); } - final int count = - mEntryManager.getNotificationData().getActiveNotifications().size(); - final int rank = mEntryManager.getNotificationData().getRank(notificationKey); + //TODO(b/144306683): prove that this `activeEntry` is the same as `entry` above and simplify + // this call stack + NotificationEntry activeEntry = + mEntryManager.getActiveNotificationUnfiltered(notificationKey); + final int count = mEntryManager.getActiveNotificationsCount(); + final int rank = activeEntry != null ? activeEntry.getRanking().getRank() : 0; NotificationVisibility.NotificationLocation location = - NotificationLogger.getNotificationLocation( - mEntryManager.getNotificationData().get(notificationKey)); + NotificationLogger.getNotificationLocation(activeEntry); final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, rank, count, true, location); try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 38ff86227733..30e26e57e435 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -75,7 +75,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; -import java.util.ArrayList; +import java.util.List; public class StatusBarNotificationPresenter implements NotificationPresenter, ConfigurationController.ConfigurationListener, @@ -252,8 +252,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } private void updateNotificationOnUiModeChanged() { - ArrayList<NotificationEntry> userNotifications - = mEntryManager.getNotificationData().getNotificationsForCurrentUser(); + List<NotificationEntry> userNotifications = + mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { NotificationEntry entry = userNotifications.get(i); ExpandableNotificationRow row = entry.getRow(); @@ -264,8 +264,8 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } private void updateNotificationsOnDensityOrFontScaleChanged() { - ArrayList<NotificationEntry> userNotifications = - mEntryManager.getNotificationData().getNotificationsForCurrentUser(); + List<NotificationEntry> userNotifications = + mEntryManager.getActiveNotificationsForCurrentUser(); for (int i = 0; i < userNotifications.size(); i++) { NotificationEntry entry = userNotifications.get(i); entry.onDensityOrFontScaleChanged(); @@ -326,7 +326,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, } public boolean hasActiveNotifications() { - return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); + return mEntryManager.hasActiveNotifications(); } public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java index a60ca6201419..49ada1a5e41e 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java @@ -158,7 +158,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference String demoTime = "1010"; // 10:10, a classic choice of horologists try { - String[] versionParts = android.os.Build.VERSION.RELEASE.split("\\."); + String[] versionParts = android.os.Build.VERSION.RELEASE_OR_CODENAME.split("\\."); int majorVersion = Integer.valueOf(versionParts[0]); demoTime = String.format("%02d00", majorVersion % 24); } catch (IllegalArgumentException ex) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java index e63b6d6670fd..02a3766fb3b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java @@ -102,7 +102,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { public void testAppOps_appOpAddedToForegroundNotif() { // GIVEN a notification associated with a foreground service NotificationEntry entry = addFgEntry(); - when(mEntryManager.getPendingOrCurrentNotif(entry.getKey())).thenReturn(entry); + when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry); // WHEN we are notified of a new app op for this notification mFsc.onAppOpChanged( @@ -123,7 +123,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { // GIVEN a foreground service associated notification that already has the correct app op NotificationEntry entry = addFgEntry(); entry.mActiveAppOps.add(AppOpsManager.OP_CAMERA); - when(mEntryManager.getPendingOrCurrentNotif(entry.getKey())).thenReturn(entry); + when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(entry); // WHEN we are notified of the same app op for this notification mFsc.onAppOpChanged( @@ -143,7 +143,7 @@ public class ForegroundServiceControllerTest extends SysuiTestCase { public void testAppOps_appOpNotAddedToUnrelatedNotif() { // GIVEN no notification entries correspond to the newly updated appOp NotificationEntry entry = addFgEntry(); - when(mEntryManager.getPendingOrCurrentNotif(entry.getKey())).thenReturn(null); + when(mEntryManager.getPendingOrActiveNotif(entry.getKey())).thenReturn(null); // WHEN a new app op is detected mFsc.onAppOpChanged( diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java index fbb8e0c171cd..f832b7bf769f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java @@ -65,7 +65,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Mock private AssistUtils mMockAssistUtils; @Mock private Handler mMockHandler; - @Mock private PhenotypeHelper mMockPhenotypeHelper; + @Mock private DeviceConfigHelper mMockDeviceConfigHelper; @Mock private AssistHandleOffBehavior mMockOffBehavior; @Mock private AssistHandleLikeHomeBehavior mMockLikeHomeBehavior; @Mock private AssistHandleReminderExpBehavior mMockReminderExpBehavior; @@ -97,7 +97,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { mMockAssistUtils, mMockHandler, () -> mMockAssistHandleViewController, - mMockPhenotypeHelper, + mMockDeviceConfigHelper, behaviorMap, mMockNavigationModeController, mMockDumpController); @@ -216,7 +216,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { public void showAndGo_doesNothingIfRecentlyHidden() { // Arrange when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); - when(mMockPhenotypeHelper.getLong( + when(mMockDeviceConfigHelper.getLong( eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS), anyLong())).thenReturn(10000L); mAssistHandleBehaviorController.showAndGo(); @@ -297,7 +297,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { public void showAndGoDelayed_doesNothingIfRecentlyHidden() { // Arrange when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); - when(mMockPhenotypeHelper.getLong( + when(mMockDeviceConfigHelper.getLong( eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOWN_FREQUENCY_THRESHOLD_MS), anyLong())).thenReturn(10000L); mAssistHandleBehaviorController.showAndGo(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index a7c02045667d..8c9f75950eb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -72,7 +72,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.DozeParameters; @@ -141,8 +140,6 @@ public class BubbleControllerTest extends SysuiTestCase { private ExpandableNotificationRow mNonBubbleNotifRow; @Mock - private NotificationData mNotificationData; - @Mock private BubbleController.BubbleStateChangeListener mBubbleStateChangeListener; @Mock private BubbleController.BubbleExpandListener mBubbleExpandListener; @@ -183,10 +180,9 @@ public class BubbleControllerTest extends SysuiTestCase { mNonBubbleNotifRow = mNotificationTestHelper.createRow(); // Return non-null notification data from the NEM - when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData); - when(mNotificationData.get(mRow.getEntry().getKey())).thenReturn(mRow.getEntry()); - when(mNotificationData.getChannel(mRow.getEntry().getKey())).thenReturn( - mRow.getEntry().getChannel()); + when(mNotificationEntryManager + .getActiveNotificationUnfiltered(mRow.getEntry().getKey())).thenReturn( + mRow.getEntry()); mZenModeConfig.suppressedVisualEffects = 0; when(mZenModeController.getConfig()).thenReturn(mZenModeConfig); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 32361cd0c1d5..a9be30ba82a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -16,6 +16,8 @@ package com.android.systemui.bubbles; +import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; @@ -72,6 +74,8 @@ public class BubbleDataTest extends SysuiTestCase { private NotificationEntry mEntryB2; private NotificationEntry mEntryB3; private NotificationEntry mEntryC1; + private NotificationEntry mEntryInterruptive; + private NotificationEntry mEntryDismissed; private Bubble mBubbleA1; private Bubble mBubbleA2; @@ -110,6 +114,13 @@ public class BubbleDataTest extends SysuiTestCase { mEntryB3 = createBubbleEntry(1, "b3", "package.b"); mEntryC1 = createBubbleEntry(1, "c1", "package.c"); + mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d"); + modifyRanking(mEntryInterruptive) + .setVisuallyInterruptive(true) + .build(); + + mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d"); + mBubbleA1 = new Bubble(mContext, mEntryA1); mBubbleA2 = new Bubble(mContext, mEntryA2); mBubbleA3 = new Bubble(mContext, mEntryA3); @@ -160,6 +171,77 @@ public class BubbleDataTest extends SysuiTestCase { assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_USER_GESTURE); } + @Test + public void ifSuppress_hideFlyout() { + // Setup + mBubbleData.setListener(mListener); + + // Test + mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ true, /* showInShade */ + true); + + // Verify + verifyUpdateReceived(); + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.addedBubble.showFlyoutForBubble()).isFalse(); + } + + @Test + public void ifInterruptiveAndNotSuppressed_thenShowFlyout() { + // Setup + mBubbleData.setListener(mListener); + + // Test + mBubbleData.notificationEntryUpdated(mEntryInterruptive, /* suppressFlyout */ + false, /* showInShade */ + true); + + // Verify + verifyUpdateReceived(); + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.addedBubble.showFlyoutForBubble()).isTrue(); + } + + @Test + public void sameUpdate_InShade_thenHideFlyout() { + // Setup + mBubbleData.setListener(mListener); + + // Test + mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ false, /* showInShade */ + true); + verifyUpdateReceived(); + + mBubbleData.notificationEntryUpdated(mEntryC1, /* suppressFlyout */ false, /* showInShade */ + true); + verifyUpdateReceived(); + + // Verify + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.updatedBubble.showFlyoutForBubble()).isFalse(); + } + + @Test + public void sameUpdate_NotInShade_showFlyout() { + // Setup + mBubbleData.setListener(mListener); + setMetadataFlags(mEntryDismissed, + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag */ true); + + // Test + mBubbleData.notificationEntryUpdated(mEntryDismissed, /* suppressFlyout */ false, + /* showInShade */ false); + verifyUpdateReceived(); + + mBubbleData.notificationEntryUpdated(mEntryDismissed, /* suppressFlyout */ + false, /* showInShade */ false); + verifyUpdateReceived(); + + // Verify + BubbleData.Update update = mUpdateCaptor.getValue(); + assertThat(update.updatedBubble.showFlyoutForBubble()).isTrue(); + } + // COLLAPSED / ADD /** @@ -854,6 +936,23 @@ public class BubbleDataTest extends SysuiTestCase { } /** + * Sets the bubble metadata flags for this entry. These flags are normally set by + * NotificationManagerService when the notification is sent, however, these tests do not + * go through that path so we set them explicitly when testing. + */ + private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) { + Notification.BubbleMetadata bubbleMetadata = + entry.getSbn().getNotification().getBubbleMetadata(); + int flags = bubbleMetadata.getFlags(); + if (enableFlag) { + flags |= flag; + } else { + flags &= ~flag; + } + bubbleMetadata.setFlags(flags); + } + + /** * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is * required for BubbleData functionality and verification. NotificationTestHelper is used only * as a convenience to create a Notification w/BubbleMetadata. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index 86869bd65900..2f53a01b1d69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -37,7 +37,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import org.junit.Before; import org.junit.Test; @@ -54,7 +53,6 @@ public class NotificationListenerTest extends SysuiTestCase { @Mock private NotificationPresenter mPresenter; @Mock private NotificationListenerService.RankingMap mRanking; - @Mock private NotificationData mNotificationData; // Dependency mocks: @Mock private NotificationEntryManager mEntryManager; @@ -74,8 +72,6 @@ public class NotificationListenerTest extends SysuiTestCase { new Handler(TestableLooper.get(this).getLooper())); mContext.addMockSystemService(NotificationManager.class, mNotificationManager); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - mListener = new NotificationListener(mContext); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.CURRENT, null, 0); @@ -90,7 +86,7 @@ public class NotificationListenerTest extends SysuiTestCase { @Test public void testNotificationUpdateCallsUpdateNotification() { - when(mNotificationData.get(mSbn.getKey())) + when(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())) .thenReturn( new NotificationEntryBuilder() .setSbn(mSbn) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 548f7a86adf3..d54e24ba2602 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.content.Intent.ACTION_USER_SWITCHED; import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL; @@ -24,7 +25,6 @@ import static com.android.systemui.statusbar.notification.stack.NotificationSect import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -49,7 +49,6 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -72,7 +71,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // Dependency mocks: @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationData mNotificationData; @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private StatusBarKeyguardViewManager mKeyguardViewManager; @Mock private BroadcastDispatcher mBroadcastDispatcher; @@ -93,7 +91,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { when(mUserManager.getProfiles(mCurrentUserId)).thenReturn(Lists.newArrayList( new UserInfo(mCurrentUserId, "", 0), new UserInfo(mCurrentUserId + 1, "", 0))); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler.createAsync(Looper.myLooper())); @@ -170,9 +167,10 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1); - when(mNotificationData.isHighPriority(any())).thenReturn(false); - NotificationEntry entry = new NotificationEntryBuilder().build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_LOW) + .build(); entry.setBucket(BUCKET_SILENT); assertTrue(mLockscreenUserManager.shouldShowOnKeyguard(entry)); @@ -186,10 +184,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 0); - when(mNotificationData.isHighPriority(any())).thenReturn(false); - NotificationEntry entry = new NotificationEntryBuilder().build(); + NotificationEntry entry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_LOW) + .build(); entry.setBucket(BUCKET_SILENT); + entry.setIsHighPriority(true); assertFalse(mLockscreenUserManager.shouldShowOnKeyguard(entry)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 90bd0e9936be..88546b9b31a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.mock; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.Instrumentation; import android.app.Notification; import android.app.Notification.BubbleMetadata; import android.app.NotificationChannel; @@ -40,8 +39,6 @@ import android.text.TextUtils; import android.view.LayoutInflater; import android.widget.RemoteViews; -import androidx.test.InstrumentationRegistry; - import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; @@ -72,7 +69,6 @@ public class NotificationTestHelper { private static final String GROUP_KEY = "gruKey"; private final Context mContext; - private final Instrumentation mInstrumentation; private int mId; private final NotificationGroupManager mGroupManager; private ExpandableNotificationRow mRow; @@ -83,7 +79,7 @@ public class NotificationTestHelper { dependency.injectMockDependency(NotificationMediaManager.class); dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(StatusBarWindowController.class); - mInstrumentation = InstrumentationRegistry.getInstrumentation(); + dependency.injectMockDependency(SmartReplyController.class); StatusBarStateController stateController = mock(StatusBarStateController.class); mGroupManager = new NotificationGroupManager(stateController); mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 18649bfe68d3..99c94ac7dec2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -46,7 +46,6 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -73,7 +72,6 @@ import java.util.List; @TestableLooper.RunWithLooper public class NotificationViewHierarchyManagerTest extends SysuiTestCase { @Mock private NotificationPresenter mPresenter; - @Mock private NotificationData mNotificationData; @Spy private FakeListContainer mListContainer = new FakeListContainer(); // Dependency mocks: @@ -105,8 +103,6 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mHelper = new NotificationTestHelper(mContext, mDependency); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - mViewHierarchyManager = new NotificationViewHierarchyManager(mContext, mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager, mock(StatusBarStateControllerImpl.class), mEntryManager, @@ -139,7 +135,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mListContainer.addContainerView(entry0.getRow()); mListContainer.addContainerView(entry1.getRow()); mListContainer.addContainerView(entry2.getRow()); - when(mNotificationData.getActiveNotifications()).thenReturn( + when(mEntryManager.getVisibleNotifications()).thenReturn( Lists.newArrayList(entry0, entry1, entry2)); // Set up group manager to report that they should be bundled now. @@ -168,7 +164,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { // Set up the prior state to look like one top level notification. mListContainer.addContainerView(entry0.getRow()); - when(mNotificationData.getActiveNotifications()).thenReturn( + when(mEntryManager.getVisibleNotifications()).thenReturn( Lists.newArrayList(entry0, entry1, entry2)); // Set up group manager to report that they should not be bundled now. @@ -197,7 +193,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { // Set up the prior state to look like a top level notification. mListContainer.addContainerView(entry0.getRow()); - when(mNotificationData.getActiveNotifications()).thenReturn( + when(mEntryManager.getVisibleNotifications()).thenReturn( Lists.newArrayList(entry0, entry1)); // Set up group manager to report a suppressed summary now. @@ -219,7 +215,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { public void testUpdateNotificationViews_appOps() throws Exception { NotificationEntry entry0 = createEntry(); entry0.setRow(spy(entry0.getRow())); - when(mNotificationData.getActiveNotifications()).thenReturn( + when(mEntryManager.getVisibleNotifications()).thenReturn( Lists.newArrayList(entry0)); mListContainer.addContainerView(entry0.getRow()); 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 820f4652e685..d003b994f6dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java @@ -175,6 +175,11 @@ public class RankingBuilder { return this; } + public RankingBuilder setVisuallyInterruptive(boolean interruptive) { + mIsVisuallyInterruptive = interruptive; + return this; + } + public RankingBuilder setImportance(@Importance int importance) { mImportance = importance; return this; 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 86ef6e8593fc..a98945f49b99 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON; @@ -28,7 +29,6 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; @@ -40,14 +40,15 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; -import android.app.NotificationManager; +import android.app.NotificationChannel; import android.app.PendingIntent; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; -import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -73,18 +74,18 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; +import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.collection.NotificationData; -import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; +import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.RowInflaterTask; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -93,6 +94,7 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.Assert; import org.junit.Before; import org.junit.Test; @@ -104,13 +106,14 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper() public class NotificationEntryManagerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; @@ -123,7 +126,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationRemoveInterceptor mRemoveInterceptor; @Mock private NotificationRowBinderImpl.BindRowCallback mBindCallback; @Mock private HeadsUpManager mHeadsUpManager; - @Mock private NotificationListenerService.RankingMap mRankingMap; + @Mock private RankingMap mRankingMap; @Mock private RemoteInputController mRemoteInputController; // Dependency mocks: @@ -139,6 +142,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private SmartReplyController mSmartReplyController; @Mock private RowInflaterTask mAsyncInflationTask; @Mock private NotificationRowBinder mMockedRowBinder; + @Mock private NotifLog mNotifLog; private int mId; private NotificationEntry mEntry; @@ -146,43 +150,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase { private TestableNotificationEntryManager mEntryManager; private CountDownLatch mCountDownLatch; - private class TestableNotificationEntryManager extends NotificationEntryManager { - private final CountDownLatch mCountDownLatch; - - TestableNotificationEntryManager() { - super( - new NotificationData( - mock(NotificationSectionsFeatureManager.class), - mock(NotifLog.class), - mock(PeopleNotificationIdentifier.class)), - mock(NotifLog.class)); - mCountDownLatch = new CountDownLatch(1); - } - - public void setNotificationData(NotificationData data) { - mNotificationData = data; - } - - @Override - public void onAsyncInflationFinished(NotificationEntry entry, - @InflationFlag int inflatedFlags) { - super.onAsyncInflationFinished(entry, inflatedFlags); - - mCountDownLatch.countDown(); - } - - public CountDownLatch getCountDownLatch() { - return mCountDownLatch; - } - - public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() { - return mNotificationLifetimeExtenders; - } - } - private void setUserSentiment(String key, int sentiment) { doAnswer(invocationOnMock -> { - NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking) + Ranking ranking = (Ranking) invocationOnMock.getArguments()[1]; ranking.populate( key, @@ -190,16 +160,16 @@ public class NotificationEntryManagerTest extends SysuiTestCase { false, 0, 0, - NotificationManager.IMPORTANCE_DEFAULT, + IMPORTANCE_DEFAULT, null, null, null, null, null, true, sentiment, false, -1, false, null, null, false, false); return true; - }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); + }).when(mRankingMap).getRanking(eq(key), any(Ranking.class)); } private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) { doAnswer(invocationOnMock -> { - NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking) + Ranking ranking = (Ranking) invocationOnMock.getArguments()[1]; ranking.populate( key, @@ -207,13 +177,13 @@ public class NotificationEntryManagerTest extends SysuiTestCase { false, 0, 0, - NotificationManager.IMPORTANCE_DEFAULT, + IMPORTANCE_DEFAULT, null, null, null, null, null, true, - NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false, -1, + Ranking.USER_SENTIMENT_NEUTRAL, false, -1, false, smartActions, null, false, false); return true; - }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class)); + }).when(mRankingMap).getRanking(eq(key), any(Ranking.class)); } @Before @@ -249,7 +219,18 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - mEntryManager = new TestableNotificationEntryManager(); + mEntryManager = new TestableNotificationEntryManager( + mNotifLog, + mGroupManager, + new NotificationRankingManager( + () -> mock(NotificationMediaManager.class), + mGroupManager, + mHeadsUpManager, + mock(NotificationFilter.class), + mNotifLog, + mock(NotificationSectionsFeatureManager.class)), + mEnvironment + ); Dependency.get(InitController.class).executePostInitTasks(); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(mEntryListener); @@ -258,19 +239,19 @@ public class NotificationEntryManagerTest extends SysuiTestCase { NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, true, /* allowLongPress */ mock(KeyguardBypassController.class), - mock(StatusBarStateController.class)); + mock(StatusBarStateController.class), + mock(NotificationLogger.class)); notificationRowBinder.setUpWithPresenter( mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); mEntryManager.setRowBinder(notificationRowBinder); setUserSentiment( - mEntry.getKey(), NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); + mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); } @Test public void testAddNotification() throws Exception { - com.android.systemui.util.Assert.isNotMainThread(); TestableLooper.get(this).processAllMessages(); doAnswer(invocation -> { @@ -299,21 +280,20 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mEntryListener).onNotificationAdded(entry); verify(mPresenter).updateNotificationViews(); - assertEquals(mEntryManager.getNotificationData().get(mSbn.getKey()), entry); + assertEquals(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey()), entry); assertNotNull(entry.getRow()); assertEquals(mEntry.getUserSentiment(), - NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL); + Ranking.USER_SENTIMENT_NEUTRAL); } @Test public void testUpdateNotification() throws Exception { - com.android.systemui.util.Assert.isNotMainThread(); TestableLooper.get(this).processAllMessages(); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); setUserSentiment( - mEntry.getKey(), NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); + mEntry.getKey(), Ranking.USER_SENTIMENT_NEGATIVE); mEntryManager.updateNotification(mSbn, mRankingMap); TestableLooper.get(this).processMessages(1); @@ -327,19 +307,15 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mEntryListener).onPostEntryUpdated(mEntry); assertNotNull(mEntry.getRow()); - assertEquals(mEntry.getUserSentiment(), - NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); + assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, + mEntry.getUserSentiment()); } @Test public void testUpdateNotification_prePostEntryOrder() throws Exception { - com.android.systemui.util.Assert.isNotMainThread(); TestableLooper.get(this).processAllMessages(); - NotificationData notifData = mock(NotificationData.class); - when(notifData.get(mEntry.getKey())).thenReturn(mEntry); - - mEntryManager.setNotificationData(notifData); + mEntryManager.addActiveNotificationForTest(mEntry); mEntryManager.updateNotification(mSbn, mRankingMap); TestableLooper.get(this).processMessages(1); @@ -349,9 +325,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { verify(mEntryListener, never()).onInflationError(any(), any()); // Ensure that update callbacks happen in correct order - InOrder order = inOrder(mEntryListener, notifData, mPresenter, mEntryListener); + InOrder order = inOrder(mEntryListener, mPresenter, mEntryListener); order.verify(mEntryListener).onPreEntryUpdated(mEntry); - order.verify(notifData).filterAndSort(anyString()); order.verify(mPresenter).updateNotificationViews(); order.verify(mEntryListener).onPostEntryUpdated(mEntry); @@ -359,11 +334,12 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test - public void testRemoveNotification() throws Exception { - com.android.systemui.util.Assert.isNotMainThread(); + public void testRemoveNotification() { + // Row inflation happens off thread, so pretend that this test looper is main + Assert.sMainLooper = TestableLooper.get(this).getLooper(); mEntry.setRow(mRow); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON); @@ -374,12 +350,11 @@ public class NotificationEntryManagerTest extends SysuiTestCase { eq(mEntry), any(), eq(false) /* removedByUser */); verify(mRow).setRemoved(); - assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); + assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); } @Test public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() { - com.android.systemui.util.Assert.isNotMainThread(); mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON); @@ -388,8 +363,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { } @Test - public void testRemoveNotification_whilePending() throws InterruptedException { - com.android.systemui.util.Assert.isNotMainThread(); + public void testRemoveNotification_whilePending() { mEntryManager.setRowBinder(mMockedRowBinder); @@ -408,7 +382,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.setRow(mRow); mEntry.setInflationTask(mAsyncInflationTask); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); @@ -424,7 +398,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); mEntry.setRow(mRow); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); setSmartActions(mEntry.getKey(), null); mEntryManager.updateNotificationRanking(mRankingMap); @@ -438,7 +412,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); mEntry.setRow(null); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction()))); mEntryManager.updateNotificationRanking(mRankingMap); @@ -466,7 +440,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN a lifetime extender that always tries to extend lifetime NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); @@ -479,15 +453,18 @@ public class NotificationEntryManagerTest extends SysuiTestCase { // THEN the extender is asked to manage the lifetime verify(extender).setShouldManageLifetime(mEntry, true); // THEN the notification is retained - assertNotNull(mEntryManager.getNotificationData().get(mSbn.getKey())); + assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener, never()).onEntryRemoved(eq(mEntry), any(), eq(false)); } @Test public void testLifetimeExtenders_whenRetentionEndsNotificationIsRemoved() { + // Row inflation happens off thread, so pretend that this test looper is main + Assert.sMainLooper = TestableLooper.get(this).getLooper(); + // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender(); mEntryManager.addNotificationLifetimeExtender(extender); mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON); @@ -498,7 +475,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { extender.getCallback().onSafeToRemove(mEntry.getKey()); // THEN the notification is removed - assertNull(mEntryManager.getNotificationData().get(mSbn.getKey())); + assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener).onEntryRemoved(eq(mEntry), any(), eq(false)); } @@ -506,7 +483,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() { // GIVEN an entry manager with a notification whose life has been extended mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class); when(extender.shouldExtendLifetime(mEntry)).thenReturn(true); mEntryManager.addNotificationLifetimeExtender(extender); @@ -523,7 +500,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN two lifetime extenders, the first which never extends and the second which // always extends @@ -554,15 +531,15 @@ public class NotificationEntryManagerTest extends SysuiTestCase { */ @Test public void testPerformRemoveNotification_removedEntry() { - mEntryManager.getNotificationData().remove(mSbn.getKey(), null /* ranking */); + mEntryManager.removeNotification(mSbn.getKey(), null, 0); mEntryManager.performRemoveNotification(mSbn, REASON_CANCEL); } @Test - public void testRemoveInterceptor_interceptsDontGetRemoved() { + public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException { // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that intercepts that entry when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.getKey()), anyInt())) @@ -572,16 +549,19 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON); // THEN the interceptor intercepts & the entry is not removed & no listeners are called - assertNotNull(mEntryManager.getNotificationData().get(mEntry.getKey())); + assertNotNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener, never()).onEntryRemoved(eq(mEntry), any(NotificationVisibility.class), anyBoolean()); } @Test public void testRemoveInterceptor_notInterceptedGetsRemoved() { + // Row inflation happens off thread, so pretend that this test looper is main + Assert.sMainLooper = TestableLooper.get(this).getLooper(); + // GIVEN an entry manager with a notification mEntryManager.setRowBinder(mMockedRowBinder); - mEntryManager.getNotificationData().add(mEntry); + mEntryManager.addActiveNotificationForTest(mEntry); // GIVEN interceptor that doesn't intercept when(mRemoveInterceptor.onNotificationRemoveRequested(eq(mEntry.getKey()), anyInt())) @@ -591,7 +571,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.removeNotification(mEntry.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON); // THEN the interceptor intercepts & the entry is not removed & no listeners are called - assertNull(mEntryManager.getNotificationData().get(mEntry.getKey())); + assertNull(mEntryManager.getActiveNotificationUnfiltered(mSbn.getKey())); verify(mEntryListener, atLeastOnce()).onEntryRemoved(eq(mEntry), any(NotificationVisibility.class), anyBoolean()); } @@ -612,6 +592,63 @@ public class NotificationEntryManagerTest extends SysuiTestCase { .build(); } + /* Tests annexed from NotificationDataTest go here */ + + @Test + public void testChannelIsSetWhenAdded() { + NotificationChannel nc = new NotificationChannel( + "testId", + "testName", + IMPORTANCE_DEFAULT); + + Ranking r = new RankingBuilder() + .setKey(mEntry.getKey()) + .setChannel(nc) + .build(); + + RankingMap rm = new RankingMap(new Ranking[] { r }); + + // GIVEN: a notification is added, and the ranking updated + mEntryManager.addActiveNotificationForTest(mEntry); + mEntryManager.updateRanking(rm, "testReason"); + + // THEN the notification entry better have a channel on it + assertEquals( + "Channel must be set when adding a notification", + nc.getName(), + mEntry.getChannel().getName()); + } + + @Test + public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() { + Assert.sMainLooper = TestableLooper.get(this).getLooper(); + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + + NotificationEntry e2 = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setId(mId++) + .setNotification(n.build()) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + + mEntryManager.addActiveNotificationForTest(mEntry); + mEntryManager.addActiveNotificationForTest(e2); + + when(mEnvironment.isNotificationForCurrentProfiles(mEntry.getSbn())).thenReturn(false); + when(mEnvironment.isNotificationForCurrentProfiles(e2.getSbn())).thenReturn(true); + + List<NotificationEntry> result = mEntryManager.getActiveNotificationsForCurrentUser(); + assertEquals(result.size(), 1); + junit.framework.Assert.assertEquals(result.get(0), e2); + } + + /* End annex */ + private Notification.Action createAction() { return new Notification.Action.Builder( Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index d85f2752a247..68730d12dee3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -44,7 +44,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationTestHelper; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -71,7 +71,7 @@ public class NotificationFilterTest extends SysuiTestCase { @Mock ForegroundServiceController mFsc; @Mock - NotificationData.KeyguardEnvironment mEnvironment; + KeyguardEnvironment mEnvironment; private final IPackageManager mMockPackageManager = mock(IPackageManager.class); private NotificationFilter mNotificationFilter; @@ -96,7 +96,7 @@ public class NotificationFilterTest extends SysuiTestCase { new NotificationGroupManager(mock(StatusBarStateController.class))); mDependency.injectMockDependency(ShadeController.class); mDependency.injectMockDependency(NotificationLockscreenUserManager.class); - mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment); + mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); when(mEnvironment.isDeviceProvisioned()).thenReturn(true); when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java index cc56949d67f7..133d52b4f946 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java @@ -17,9 +17,7 @@ package com.android.systemui.statusbar.notification; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; @@ -34,14 +32,10 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.util.DeviceConfigProxyFake; import org.junit.Before; import org.junit.Test; @@ -67,13 +61,6 @@ public class NotificationListControllerTest extends SysuiTestCase { private NotificationEntryListener mEntryListener; private DeviceProvisionedListener mProvisionedListener; - // TODO: Remove this once EntryManager no longer needs to be mocked - private NotificationData mNotificationData = - new NotificationData( - new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext), - mock(NotifLog.class), - mock(PeopleNotificationIdentifier.class)); - private int mNextNotifId = 0; @Before @@ -81,8 +68,6 @@ public class NotificationListControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(NotificationLockscreenUserManager.class); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - mController = new NotificationListController( mEntryManager, mListContainer, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt new file mode 100644 index 000000000000..34beefe9843b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 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 + +import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager +import com.android.systemui.statusbar.notification.logging.NotifLog +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone +import com.android.systemui.statusbar.phone.NotificationGroupManager + +import java.util.concurrent.CountDownLatch + +/** + * Enable some test capabilities for NEM without making everything public on the base class + */ +class TestableNotificationEntryManager( + log: NotifLog, + gm: NotificationGroupManager, + rm: NotificationRankingManager, + ke: KeyguardEnvironment +) : NotificationEntryManager(log, gm, rm, ke) { + + public var countDownLatch: CountDownLatch = CountDownLatch(1) + + override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) { + super.onAsyncInflationFinished(entry, inflatedFlags) + countDownLatch.countDown() + } + + fun setUpForTest( + presenter: NotificationPresenter?, + listContainer: NotificationListContainer?, + headsUpManager: HeadsUpManagerPhone? + ) { + super.setUpWithPresenter(presenter, listContainer, headsUpManager) + } + + fun setActiveNotificationList(activeList: List<NotificationEntry>) { + mSortedAndFiltered.clear() + mSortedAndFiltered.addAll(activeList) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java deleted file mode 100644 index 1a469d881d2c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java +++ /dev/null @@ -1,697 +0,0 @@ -/* - * Copyright (C) 2017 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; - -import static android.app.Notification.CATEGORY_ALARM; -import static android.app.Notification.CATEGORY_CALL; -import static android.app.Notification.CATEGORY_EVENT; -import static android.app.Notification.CATEGORY_MESSAGE; -import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.NotificationManager.IMPORTANCE_DEFAULT; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; -import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_CHANNEL; -import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE; -import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK; -import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT; - -import static junit.framework.Assert.assertEquals; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.Manifest; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Person; -import android.content.Intent; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.graphics.drawable.Icon; -import android.media.session.MediaSession; -import android.os.Bundle; -import android.service.notification.NotificationListenerService; -import android.service.notification.NotificationListenerService.Ranking; -import android.service.notification.SnoozeCriterion; -import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.Dependency; -import com.android.systemui.ForegroundServiceController; -import com.android.systemui.InitController; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.NotificationEntryBuilder; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationTestHelper; -import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SbnBuilder; -import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; -import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment; -import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.ShadeController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class NotificationDataTest extends SysuiTestCase { - - private static final int UID_NORMAL = 123; - private static final int UID_ALLOW_DURING_SETUP = 456; - private static final NotificationChannel NOTIFICATION_CHANNEL = - new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE); - - private NotificationEntry mEntry; - - @Mock - ForegroundServiceController mFsc; - @Mock - NotificationData.KeyguardEnvironment mEnvironment; - - private final IPackageManager mMockPackageManager = mock(IPackageManager.class); - private TestableNotificationData mNotificationData; - private ExpandableNotificationRow mRow; - - @Before - public void setUp() throws Exception { - com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper(); - MockitoAnnotations.initMocks(this); - - mEntry = new NotificationEntryBuilder() - .setUid(UID_NORMAL) - .build(); - - when(mMockPackageManager.checkUidPermission( - eq(Manifest.permission.NOTIFICATION_DURING_SETUP), - eq(UID_NORMAL))) - .thenReturn(PackageManager.PERMISSION_DENIED); - when(mMockPackageManager.checkUidPermission( - eq(Manifest.permission.NOTIFICATION_DURING_SETUP), - eq(UID_ALLOW_DURING_SETUP))) - .thenReturn(PackageManager.PERMISSION_GRANTED); - - mDependency.injectTestDependency(ForegroundServiceController.class, mFsc); - mDependency.injectTestDependency(NotificationGroupManager.class, - new NotificationGroupManager(mock(StatusBarStateController.class))); - mDependency.injectMockDependency(ShadeController.class); - mDependency.injectMockDependency(NotificationLockscreenUserManager.class); - mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment); - when(mEnvironment.isDeviceProvisioned()).thenReturn(true); - when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true); - mNotificationData = new TestableNotificationData( - mock(NotificationSectionsFeatureManager.class)); - mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class), ""); - mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); - Dependency.get(InitController.class).executePostInitTasks(); - } - - @Test - public void testChannelSetWhenAdded() { - Bundle override = new Bundle(); - override.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL); - mNotificationData.rankingOverrides.put(mRow.getEntry().getKey(), override); - mNotificationData.add(mRow.getEntry()); - assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().getChannel()); - } - - @Test - public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() - throws Exception { - mNotificationData.add(mRow.getEntry()); - ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency) - .createRow(); - mNotificationData.add(row2.getEntry()); - - when(mEnvironment.isNotificationForCurrentProfiles( - mRow.getEntry().getSbn())).thenReturn(false); - when(mEnvironment.isNotificationForCurrentProfiles( - row2.getEntry().getSbn())).thenReturn(true); - ArrayList<NotificationEntry> result = - mNotificationData.getNotificationsForCurrentUser(); - - assertEquals(result.size(), 1); - junit.framework.Assert.assertEquals(result.get(0), row2.getEntry()); - } - - @Test - public void testIsExemptFromDndVisualSuppression_foreground() { - initStatusBarNotification(false); - - mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; - mEntry.setRow(mRow); - mNotificationData.add(mEntry); - Bundle override = new Bundle(); - override.putInt(OVERRIDE_VIS_EFFECTS, 255); - mNotificationData.rankingOverrides.put(mEntry.getKey(), override); - - assertTrue(mEntry.isExemptFromDndVisualSuppression()); - assertFalse(mEntry.shouldSuppressAmbient()); - } - - @Test - public void testIsExemptFromDndVisualSuppression_media() { - initStatusBarNotification(false); - Notification n = mEntry.getSbn().getNotification(); - Notification.Builder nb = Notification.Builder.recoverBuilder(mContext, n); - nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); - n = nb.build(); - modifySbn(mEntry) - .setNotification(n) - .build(); - mEntry.setRow(mRow); - mNotificationData.add(mEntry); - Bundle override = new Bundle(); - override.putInt(OVERRIDE_VIS_EFFECTS, 255); - mNotificationData.rankingOverrides.put(mEntry.getKey(), override); - - assertTrue(mEntry.isExemptFromDndVisualSuppression()); - assertFalse(mEntry.shouldSuppressAmbient()); - } - - @Test - public void testIsExemptFromDndVisualSuppression_system() { - initStatusBarNotification(false); - mEntry.setRow(mRow); - mEntry.mIsSystemNotification = true; - mNotificationData.add(mEntry); - Bundle override = new Bundle(); - override.putInt(OVERRIDE_VIS_EFFECTS, 255); - mNotificationData.rankingOverrides.put(mEntry.getKey(), override); - - assertTrue(mEntry.isExemptFromDndVisualSuppression()); - assertFalse(mEntry.shouldSuppressAmbient()); - } - - @Test - public void testIsNotExemptFromDndVisualSuppression_hiddenCategories() { - initStatusBarNotification(false); - NotificationEntry entry = new NotificationEntryBuilder() - .setUid(UID_NORMAL) - .build(); - entry.setRow(mRow); - entry.mIsSystemNotification = true; - Bundle override = new Bundle(); - override.putInt(OVERRIDE_VIS_EFFECTS, NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT); - mNotificationData.rankingOverrides.put(entry.getKey(), override); - mNotificationData.add(entry); - - modifySbn(entry) - .setNotification( - new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build()) - .build(); - assertFalse(entry.isExemptFromDndVisualSuppression()); - assertTrue(entry.shouldSuppressAmbient()); - - modifySbn(entry) - .setNotification( - new Notification.Builder(mContext, "") - .setCategory(CATEGORY_REMINDER) - .build()) - .build(); - assertFalse(entry.isExemptFromDndVisualSuppression()); - - modifySbn(entry) - .setNotification( - new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build()) - .build(); - assertFalse(entry.isExemptFromDndVisualSuppression()); - - modifySbn(entry) - .setNotification( - new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build()) - .build(); - assertFalse(entry.isExemptFromDndVisualSuppression()); - - modifySbn(entry) - .setNotification( - new Notification.Builder(mContext, "") - .setCategory(CATEGORY_MESSAGE) - .build()) - .build(); - assertFalse(entry.isExemptFromDndVisualSuppression()); - } - - @Test - public void testCreateNotificationDataEntry_RankingUpdate() { - StatusBarNotification sbn = new SbnBuilder().build(); - sbn.getNotification().actions = - new Notification.Action[] { createContextualAction("appGeneratedAction") }; - - ArrayList<Notification.Action> systemGeneratedSmartActions = - createActions("systemGeneratedAction"); - - SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation"); - ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>(); - snoozeCriterions.add(snoozeCriterion); - - Ranking ranking = new RankingBuilder() - .setKey(sbn.getKey()) - .setSmartActions(systemGeneratedSmartActions) - .setChannel(NOTIFICATION_CHANNEL) - .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) - .setSnoozeCriteria(snoozeCriterions) - .build(); - - NotificationEntry entry = - new NotificationEntry(sbn, ranking); - - assertEquals(systemGeneratedSmartActions, entry.getSmartActions()); - assertEquals(NOTIFICATION_CHANNEL, entry.getChannel()); - assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment()); - assertEquals(snoozeCriterions, entry.getSnoozeCriteria()); - } - - @Test - public void notificationDataEntry_testIsLastMessageFromReply() { - Person.Builder person = new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true); - - // EXTRA_MESSAGING_PERSON is the same Person as the sender in last message in EXTRA_MESSAGES - Bundle bundle = new Bundle(); - bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person.build()); - Bundle[] messagesBundle = new Bundle[]{ new Notification.MessagingStyle.Message( - "text", 0, person.build()).toBundle() }; - bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle); - - Notification notification = new Notification.Builder(mContext, "test") - .addExtras(bundle) - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("pkg") - .setOpPkg("pkg") - .setTag("tag") - .setNotification(notification) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - entry.setHasSentReply(); - - assertTrue(entry.isLastMessageFromReply()); - } - - @Test - public void personHighPriority() { - Person person = new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build(); - - Notification notification = new Notification.Builder(mContext, "test") - .addPerson(person) - .build(); - - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notification, mContext.getUser(), "", 0); - - assertTrue(mNotificationData.isHighPriority(sbn)); - } - - @Test - public void messagingStyleHighPriority() { - - Notification notification = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notification, mContext.getUser(), "", 0); - - assertTrue(mNotificationData.isHighPriority(sbn)); - } - - @Test - public void minForegroundNotHighPriority() { - Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notification, mContext.getUser(), "", 0); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_MIN); - mNotificationData.rankingOverrides.put(sbn.getKey(), override); - - assertFalse(mNotificationData.isHighPriority(sbn)); - } - - @Test - public void lowForegroundHighPriority() { - Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notification, mContext.getUser(), "", 0); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - mNotificationData.rankingOverrides.put(sbn.getKey(), override); - - assertTrue(mNotificationData.isHighPriority(sbn)); - } - - @Test - public void userChangeTrumpsHighPriorityCharacteristics() { - Person person = new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build(); - - Notification notification = new Notification.Builder(mContext, "test") - .addPerson(person) - .setStyle(new Notification.MessagingStyle("")) - .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .build(); - - StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, - notification, mContext.getUser(), "", 0); - - NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); - channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - - Bundle override = new Bundle(); - override.putParcelable(OVERRIDE_CHANNEL, channel); - mNotificationData.rankingOverrides.put(sbn.getKey(), override); - - assertFalse(mNotificationData.isHighPriority(sbn)); - } - - @Test - public void testSort_highPriorityTrumpsNMSRank() { - // NMS rank says A and then B. But A is not high priority and B is, so B should sort in - // front - Notification aN = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - NotificationEntry a = new NotificationEntryBuilder() - .setPkg("pkg") - .setOpPkg("pkg") - .setTag("tag") - .setNotification(aN) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - a.setRow(mock(ExpandableNotificationRow.class)); - a.setIsHighPriority(false); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - override.putInt(OVERRIDE_RANK, 1); - mNotificationData.rankingOverrides.put(a.getKey(), override); - - Notification bN = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - NotificationEntry b = new NotificationEntryBuilder() - .setPkg("pkg2") - .setOpPkg("pkg2") - .setTag("tag") - .setNotification(bN) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - b.setIsHighPriority(true); - b.setRow(mock(ExpandableNotificationRow.class)); - - Bundle bOverride = new Bundle(); - bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - bOverride.putInt(OVERRIDE_RANK, 2); - mNotificationData.rankingOverrides.put(b.getKey(), bOverride); - - assertEquals(1, mNotificationData.mRankingComparator.compare(a, b)); - } - - @Test - public void testSort_samePriorityUsesNMSRank() { - // NMS rank says A and then B, and they are the same priority so use that rank - Notification aN = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - NotificationEntry a = new NotificationEntryBuilder() - .setPkg("pkg") - .setOpPkg("pkg") - .setTag("tag") - .setNotification(aN) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - a.setRow(mock(ExpandableNotificationRow.class)); - a.setIsHighPriority(false); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - override.putInt(OVERRIDE_RANK, 1); - mNotificationData.rankingOverrides.put(a.getKey(), override); - - Notification bN = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - NotificationEntry b = new NotificationEntryBuilder() - .setPkg("pkg2") - .setOpPkg("pkg2") - .setTag("tag") - .setNotification(bN) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - b.setRow(mock(ExpandableNotificationRow.class)); - b.setIsHighPriority(false); - - Bundle bOverride = new Bundle(); - bOverride.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - bOverride.putInt(OVERRIDE_RANK, 2); - mNotificationData.rankingOverrides.put(b.getKey(), bOverride); - - assertEquals(-1, mNotificationData.mRankingComparator.compare(a, b)); - } - - @Test - public void testSort_properlySetsAlertingBucket() { - Notification notification = new Notification.Builder(mContext, "test") - .build(); - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("pkg") - .setOpPkg("pkg") - .setTag("tag") - .setNotification(notification) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_DEFAULT); - mNotificationData.rankingOverrides.put(entry.getKey(), override); - - entry.setRow(mRow); - mNotificationData.add(entry); - - assertEquals(entry.getBucket(), BUCKET_ALERTING); - } - - @Test - public void testSort_properlySetsSilentBucket() { - Notification notification = new Notification.Builder(mContext, "test") - .build(); - - NotificationEntry entry = new NotificationEntryBuilder() - .setPkg("pkg") - .setOpPkg("pkg") - .setTag("tag") - .setNotification(notification) - .setUser(mContext.getUser()) - .setOverrideGroupKey("") - .build(); - - Bundle override = new Bundle(); - override.putInt(OVERRIDE_IMPORTANCE, IMPORTANCE_LOW); - mNotificationData.rankingOverrides.put(entry.getKey(), override); - - entry.setRow(mRow); - mNotificationData.add(entry); - - assertEquals(entry.getBucket(), BUCKET_SILENT); - } - - private void initStatusBarNotification(boolean allowDuringSetup) { - Bundle bundle = new Bundle(); - bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); - Notification notification = new Notification.Builder(mContext, "test") - .addExtras(bundle) - .build(); - modifySbn(mEntry) - .setNotification(notification) - .build(); - } - - public static class TestableNotificationData extends NotificationData { - public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) { - super( - sectionsFeatureManager, - mock(NotifLog.class), - mock(PeopleNotificationIdentifier.class)); - } - - public static final String OVERRIDE_RANK = "r"; - public static final String OVERRIDE_DND = "dnd"; - public static final String OVERRIDE_VIS_OVERRIDE = "vo"; - public static final String OVERRIDE_VIS_EFFECTS = "ve"; - public static final String OVERRIDE_IMPORTANCE = "i"; - public static final String OVERRIDE_IMP_EXP = "ie"; - public static final String OVERRIDE_GROUP = "g"; - public static final String OVERRIDE_CHANNEL = "c"; - public static final String OVERRIDE_PEOPLE = "p"; - public static final String OVERRIDE_SNOOZE_CRITERIA = "sc"; - public static final String OVERRIDE_BADGE = "b"; - public static final String OVERRIDE_USER_SENTIMENT = "us"; - public static final String OVERRIDE_HIDDEN = "h"; - public static final String OVERRIDE_LAST_ALERTED = "la"; - public static final String OVERRIDE_NOISY = "n"; - public static final String OVERRIDE_SMART_ACTIONS = "sa"; - public static final String OVERRIDE_SMART_REPLIES = "sr"; - public static final String OVERRIDE_BUBBLE = "cb"; - public static final String OVERRIDE_VISUALLY_INTERRUPTIVE = "vi"; - - public Map<String, Bundle> rankingOverrides = new HashMap<>(); - - @Override - protected boolean getRanking(String key, Ranking outRanking) { - super.getRanking(key, outRanking); - - ArrayList<String> currentAdditionalPeople = new ArrayList<>(); - if (outRanking.getAdditionalPeople() != null) { - currentAdditionalPeople.addAll(outRanking.getAdditionalPeople()); - } - - ArrayList<SnoozeCriterion> currentSnooze = new ArrayList<>(); - if (outRanking.getSnoozeCriteria() != null) { - currentSnooze.addAll(outRanking.getSnoozeCriteria()); - } - - ArrayList<Notification.Action> currentActions = new ArrayList<>(); - if (outRanking.getSmartActions() != null) { - currentActions.addAll(outRanking.getSmartActions()); - } - - ArrayList<CharSequence> currentReplies = new ArrayList<>(); - if (outRanking.getSmartReplies() != null) { - currentReplies.addAll(outRanking.getSmartReplies()); - } - - if (rankingOverrides.get(key) != null) { - Bundle overrides = rankingOverrides.get(key); - outRanking.populate(key, - overrides.getInt(OVERRIDE_RANK, outRanking.getRank()), - overrides.getBoolean(OVERRIDE_DND, outRanking.matchesInterruptionFilter()), - overrides.getInt(OVERRIDE_VIS_OVERRIDE, outRanking.getVisibilityOverride()), - overrides.getInt(OVERRIDE_VIS_EFFECTS, - outRanking.getSuppressedVisualEffects()), - overrides.getInt(OVERRIDE_IMPORTANCE, outRanking.getImportance()), - overrides.getCharSequence(OVERRIDE_IMP_EXP, - outRanking.getImportanceExplanation()), - overrides.getString(OVERRIDE_GROUP, outRanking.getOverrideGroupKey()), - overrides.containsKey(OVERRIDE_CHANNEL) - ? (NotificationChannel) overrides.getParcelable(OVERRIDE_CHANNEL) - : outRanking.getChannel(), - overrides.containsKey(OVERRIDE_PEOPLE) - ? overrides.getStringArrayList(OVERRIDE_PEOPLE) - : currentAdditionalPeople, - overrides.containsKey(OVERRIDE_SNOOZE_CRITERIA) - ? overrides.getParcelableArrayList(OVERRIDE_SNOOZE_CRITERIA) - : currentSnooze, - overrides.getBoolean(OVERRIDE_BADGE, outRanking.canShowBadge()), - overrides.getInt(OVERRIDE_USER_SENTIMENT, outRanking.getUserSentiment()), - overrides.getBoolean(OVERRIDE_HIDDEN, outRanking.isSuspended()), - overrides.getLong(OVERRIDE_LAST_ALERTED, - outRanking.getLastAudiblyAlertedMillis()), - overrides.getBoolean(OVERRIDE_NOISY, outRanking.isNoisy()), - overrides.containsKey(OVERRIDE_SMART_ACTIONS) - ? overrides.getParcelableArrayList(OVERRIDE_SMART_ACTIONS) - : currentActions, - overrides.containsKey(OVERRIDE_SMART_REPLIES) - ? overrides.getCharSequenceArrayList(OVERRIDE_SMART_REPLIES) - : currentReplies, - overrides.getBoolean(OVERRIDE_BUBBLE, outRanking.canBubble()), - overrides.getBoolean(OVERRIDE_VISUALLY_INTERRUPTIVE, - outRanking.visuallyInterruptive())); - } else { - outRanking.populate( - new RankingBuilder() - .setKey(key) - .build()); - } - return true; - } - } - - private Notification.Action createContextualAction(String title) { - return new Notification.Action.Builder( - Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), - title, - PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)) - .setContextual(true) - .build(); - } - - private Notification.Action createAction(String title) { - return new Notification.Action.Builder( - Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), - title, - PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build(); - } - - private ArrayList<Notification.Action> createActions(String... titles) { - ArrayList<Notification.Action> actions = new ArrayList<>(); - for (String title : titles) { - actions.add(createAction(title)); - } - return actions; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java new file mode 100644 index 000000000000..536aeb4d0163 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2019 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; + +import static android.app.Notification.CATEGORY_ALARM; +import static android.app.Notification.CATEGORY_CALL; +import static android.app.Notification.CATEGORY_EVENT; +import static android.app.Notification.CATEGORY_MESSAGE; +import static android.app.Notification.CATEGORY_REMINDER; +import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; + +import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn; + +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 android.app.ActivityManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.media.session.MediaSession; +import android.os.Bundle; +import android.os.UserHandle; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationEntryBuilder; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.SbnBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class NotificationEntryTest extends SysuiTestCase { + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + private static final int UID_NORMAL = 123; + private static final NotificationChannel NOTIFICATION_CHANNEL = + new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE); + + private int mId; + + private NotificationEntry mEntry; + + @Before + public void setup() { + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + + mEntry = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE_NAME) + .setOpPkg(TEST_PACKAGE_NAME) + .setUid(TEST_UID) + .setId(mId++) + .setNotification(n.build()) + .setUser(new UserHandle(ActivityManager.getCurrentUser())) + .build(); + } + + @Test + public void testIsExemptFromDndVisualSuppression_foreground() { + mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; + + assertTrue(mEntry.isExemptFromDndVisualSuppression()); + assertFalse(mEntry.shouldSuppressAmbient()); + } + + @Test + public void testIsExemptFromDndVisualSuppression_media() { + Notification.Builder n = new Notification.Builder(mContext, "") + .setStyle(new Notification.MediaStyle() + .setMediaSession(mock(MediaSession.Token.class))) + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + NotificationEntry e1 = new NotificationEntryBuilder() + .setNotification(n.build()) + .build(); + + assertTrue(e1.isExemptFromDndVisualSuppression()); + assertFalse(e1.shouldSuppressAmbient()); + } + + @Test + public void testIsExemptFromDndVisualSuppression_system() { + mEntry.mIsSystemNotification = true; + + assertTrue(mEntry.isExemptFromDndVisualSuppression()); + assertFalse(mEntry.shouldSuppressAmbient()); + } + + @Test + public void testIsNotExemptFromDndVisualSuppression_hiddenCategories() { + NotificationEntry entry = new NotificationEntryBuilder() + .setUid(UID_NORMAL) + .build(); + entry.mIsSystemNotification = true; + modifyRanking(entry).setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT).build(); + + modifySbn(entry) + .setNotification( + new Notification.Builder(mContext, "").setCategory(CATEGORY_CALL).build()) + .build(); + assertFalse(entry.isExemptFromDndVisualSuppression()); + assertTrue(entry.shouldSuppressAmbient()); + + modifySbn(entry) + .setNotification( + new Notification.Builder(mContext, "") + .setCategory(CATEGORY_REMINDER) + .build()) + .build(); + assertFalse(entry.isExemptFromDndVisualSuppression()); + + modifySbn(entry) + .setNotification( + new Notification.Builder(mContext, "").setCategory(CATEGORY_ALARM).build()) + .build(); + assertFalse(entry.isExemptFromDndVisualSuppression()); + + modifySbn(entry) + .setNotification( + new Notification.Builder(mContext, "").setCategory(CATEGORY_EVENT).build()) + .build(); + assertFalse(entry.isExemptFromDndVisualSuppression()); + + modifySbn(entry) + .setNotification( + new Notification.Builder(mContext, "") + .setCategory(CATEGORY_MESSAGE) + .build()) + .build(); + assertFalse(entry.isExemptFromDndVisualSuppression()); + } + + @Test + public void testCreateNotificationDataEntry_RankingUpdate() { + StatusBarNotification sbn = new SbnBuilder().build(); + sbn.getNotification().actions = + new Notification.Action[]{createContextualAction("appGeneratedAction")}; + + ArrayList<Notification.Action> systemGeneratedSmartActions = + createActions("systemGeneratedAction"); + + SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation"); + ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>(); + snoozeCriterions.add(snoozeCriterion); + + Ranking ranking = new RankingBuilder() + .setKey(sbn.getKey()) + .setSmartActions(systemGeneratedSmartActions) + .setChannel(NOTIFICATION_CHANNEL) + .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE) + .setSnoozeCriteria(snoozeCriterions) + .build(); + + NotificationEntry entry = + new NotificationEntry(sbn, ranking); + + assertEquals(systemGeneratedSmartActions, entry.getSmartActions()); + assertEquals(NOTIFICATION_CHANNEL, entry.getChannel()); + assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment()); + assertEquals(snoozeCriterions, entry.getSnoozeCriteria()); + } + + @Test + public void notificationDataEntry_testIsLastMessageFromReply() { + Person.Builder person = new Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true); + + // EXTRA_MESSAGING_PERSON is the same Person as the sender in last message in EXTRA_MESSAGES + Bundle bundle = new Bundle(); + bundle.putParcelable(Notification.EXTRA_MESSAGING_PERSON, person.build()); + Bundle[] messagesBundle = new Bundle[]{new Notification.MessagingStyle.Message( + "text", 0, person.build()).toBundle()}; + bundle.putParcelableArray(Notification.EXTRA_MESSAGES, messagesBundle); + + Notification notification = new Notification.Builder(mContext, "test") + .addExtras(bundle) + .build(); + + NotificationEntry entry = new NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(notification) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build(); + entry.setHasSentReply(); + + assertTrue(entry.isLastMessageFromReply()); + } + + private Notification.Action createContextualAction(String title) { + return new Notification.Action.Builder( + Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), + title, + PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)) + .setContextual(true) + .build(); + } + + private Notification.Action createAction(String title) { + return new Notification.Action.Builder( + Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon), + title, + PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build(); + } + + private ArrayList<Notification.Action> createActions(String... titles) { + ArrayList<Notification.Action> actions = new ArrayList<>(); + for (String title : titles) { + actions.add(createAction(title)); + } + return actions; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt new file mode 100644 index 000000000000..01b2f8932d9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2019 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 + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_LOW +import android.app.Person +import android.service.notification.NotificationListenerService.RankingMap +import android.service.notification.StatusBarNotification +import android.testing.AndroidTestingRunner + +import org.junit.runner.RunWith + +import androidx.test.filters.SmallTest + +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationEntryBuilder +import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking +import com.android.systemui.statusbar.NotificationMediaManager +import com.android.systemui.statusbar.notification.NotificationFilter +import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.logging.NotifLog +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT +import com.android.systemui.statusbar.phone.NotificationGroupManager +import com.android.systemui.statusbar.policy.HeadsUpManager +import dagger.Lazy +import junit.framework.Assert.assertEquals +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue + +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NotificationRankingManagerTest + : SysuiTestCase() { + + private var lazyMedia: Lazy<NotificationMediaManager> = Lazy { + mock<NotificationMediaManager>(NotificationMediaManager::class.java) + } + + private val rankingManager = TestableNotificationRankingManager( + lazyMedia, + mock<NotificationGroupManager>(NotificationGroupManager::class.java), + mock<HeadsUpManager>(HeadsUpManager::class.java), + mock<NotificationFilter>(NotificationFilter::class.java), + mock<NotifLog>(NotifLog::class.java), + mock<NotificationSectionsFeatureManager>(NotificationSectionsFeatureManager::class.java) + ) + + @Before + fun setup() { + } + + @Test + fun testPeopleNotification_isHighPriority() { + val person = Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true) + .build() + + val notification = Notification.Builder(mContext, "test") + .addPerson(person) + .build() + + val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notification, mContext.user, "", 0) + + val e = NotificationEntryBuilder() + .setNotification(notification) + .setSbn(sbn) + .build() + + assertTrue(rankingManager.isHighPriority2(e)) + } + + @Test + fun messagingStyleHighPriority() { + + val notif = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + + val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notif, mContext.getUser(), "", 0) + + val e = NotificationEntryBuilder() + .setNotification(notif) + .setSbn(sbn) + .build() + + assertTrue(rankingManager.isHighPriority2(e)) + } + + @Test + fun lowForegroundHighPriority() { + val notification = mock(Notification::class.java) + `when`<Boolean>(notification.isForegroundService).thenReturn(true) + + val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notification, mContext.user, "", 0) + + val e = NotificationEntryBuilder() + .setNotification(notification) + .setSbn(sbn) + .build() + + modifyRanking(e) + .setImportance(IMPORTANCE_LOW) + .build() + + assertTrue(rankingManager.isHighPriority2(e)) + } + + @Test + fun userChangeTrumpsHighPriorityCharacteristics() { + val person = Person.Builder() + .setName("name") + .setKey("abc") + .setUri("uri") + .setBot(true) + .build() + + val notification = Notification.Builder(mContext, "test") + .addPerson(person) + .setStyle(Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build() + + val sbn = StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0, + notification, mContext.user, "", 0) + + val channel = NotificationChannel("a", "a", IMPORTANCE_LOW) + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE) + + val e = NotificationEntryBuilder() + .setSbn(sbn) + .setChannel(channel) + .build() + + assertFalse(rankingManager.isHighPriority2(e)) + } + + @Test + fun testSort_highPriorityTrumpsNMSRank() { + // NMS rank says A and then B. But A is not high priority and B is, so B should sort in + // front + val aN = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + val a = NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(aN) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + + a.setIsHighPriority(false) + + modifyRanking(a) + .setImportance(IMPORTANCE_LOW) + .setRank(1) + .build() + + val bN = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + val b = NotificationEntryBuilder() + .setPkg("pkg2") + .setOpPkg("pkg2") + .setTag("tag") + .setNotification(bN) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + b.setIsHighPriority(true) + + modifyRanking(b) + .setImportance(IMPORTANCE_LOW) + .setRank(2) + .build() + + assertEquals( + listOf(b, a), + rankingManager.updateRanking(null, listOf(a, b), "test")) + } + + @Test + fun testSort_samePriorityUsesNMSRank() { + // NMS rank says A and then B, and they are the same priority so use that rank + val aN = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + val a = NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(aN) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + a.setIsHighPriority(false) + + modifyRanking(a) + .setImportance(IMPORTANCE_LOW) + .setRank(1) + .build() + + val bN = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + val b = NotificationEntryBuilder() + .setPkg("pkg2") + .setOpPkg("pkg2") + .setTag("tag") + .setNotification(bN) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + b.setIsHighPriority(false) + + modifyRanking(b) + .setImportance(IMPORTANCE_LOW) + .setRank(2) + .build() + + assertEquals( + listOf(a, b), + rankingManager.updateRanking(null, listOf(a, b), "test")) + } + + @Test + fun testSort_properlySetsAlertingBucket() { + val notif = Notification.Builder(mContext, "test") .build() + + val e = NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(notif) + .setUser(mContext.user) + .setOverrideGroupKey("") + .build() + + modifyRanking(e).setImportance(IMPORTANCE_DEFAULT) .build() + + rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test") + assertEquals(e.bucket, BUCKET_ALERTING) + } + + @Test + fun testSort_properlySetsSilentBucket() { + val notif = Notification.Builder(mContext, "test") .build() + + val e = NotificationEntryBuilder() + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(notif) + .setUser(mContext.user) + .setOverrideGroupKey("") + .build() + + modifyRanking(e).setImportance(IMPORTANCE_LOW).build() + + rankingManager.updateRanking(RankingMap(arrayOf(e.ranking)), listOf(e), "test") + assertEquals(e.bucket, BUCKET_SILENT) + } + + internal class TestableNotificationRankingManager( + mediaManager: Lazy<NotificationMediaManager>, + groupManager: NotificationGroupManager, + headsUpManager: HeadsUpManager, + filter: NotificationFilter, + notifLog: NotifLog, + sectionsFeatureManager: NotificationSectionsFeatureManager + ) : NotificationRankingManager( + mediaManager, + groupManager, + headsUpManager, + filter, + notifLog, + sectionsFeatureManager + ) { + + fun isHighPriority2(e: NotificationEntry): Boolean { + return isHighPriority(e) + } + + fun applyTestRankingMap(r: RankingMap) { + rankingMap = r + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 47c17ad88fcb..d1398667f497 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -44,7 +44,6 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -71,7 +70,6 @@ public class NotificationLoggerTest extends SysuiTestCase { @Mock private NotificationListContainer mListContainer; @Mock private IStatusBarService mBarService; - @Mock private NotificationData mNotificationData; @Mock private ExpandableNotificationRow mRow; @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger; @@ -91,8 +89,6 @@ public class NotificationLoggerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mDependency.injectTestDependency(NotificationListener.class, mListener); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - mEntry = new NotificationEntryBuilder() .setPkg(TEST_PACKAGE_NAME) .setOpPkg(TEST_PACKAGE_NAME) @@ -131,7 +127,7 @@ public class NotificationLoggerTest extends SysuiTestCase { any(NotificationVisibility[].class)); when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); waitForUiOffloadThread(); @@ -153,7 +149,7 @@ public class NotificationLoggerTest extends SysuiTestCase { public void testStoppingNotificationLoggingReportsCurrentNotifications() throws Exception { when(mListContainer.isInVisibleLocation(any())).thenReturn(true); - when(mNotificationData.getActiveNotifications()).thenReturn(Lists.newArrayList(mEntry)); + when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry)); mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged(); TestableLooper.get(this).processAllMessages(); waitForUiOffloadThread(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 6f52e4a6686b..5b624bc6e4b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -20,7 +20,6 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -56,7 +55,9 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.NotificationLockscreenUserManager; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; @@ -65,9 +66,13 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.TestableNotificationEntryManager; +import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.FooterView; @@ -80,7 +85,6 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.StatusBarTest.TestableNotificationEntryManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.DeviceConfigProxyFake; @@ -97,6 +101,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.ArrayList; +import java.util.List; /** * Tests for {@link NotificationStackScrollLayout}. @@ -117,7 +122,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private NotificationGroupManager mGroupManager; @Mock private ExpandHelper mExpandHelper; @Mock private EmptyShadeView mEmptyShadeView; - @Mock private NotificationData mNotificationData; @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private RemoteInputController mRemoteInputController; @Mock private NotificationIconAreaController mNotificationIconAreaController; @@ -140,6 +144,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { NOTIFICATION_NEW_INTERRUPTION_MODEL, 1); // Inject dependencies before initializing the layout + mDependency.injectMockDependency(VisualStabilityManager.class); mDependency.injectTestDependency( NotificationBlockingHelperManager.class, mBlockingHelperManager); @@ -150,7 +155,18 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mDependency.injectMockDependency(ShadeController.class); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); - mEntryManager = new TestableNotificationEntryManager(mNotificationData); + mEntryManager = new TestableNotificationEntryManager( + mock(NotifLog.class), + mock(NotificationGroupManager.class), + new NotificationRankingManager( + () -> mock(NotificationMediaManager.class), + mGroupManager, + mHeadsUpManager, + mock(NotificationFilter.class), + mock(NotifLog.class), + mock(NotificationSectionsFeatureManager.class) + ), + mock(NotificationEntryManager.KeyguardEnvironment.class)); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); Dependency.get(InitController.class).executePostInitTasks(); mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager); @@ -161,8 +177,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // The actual class under test. You may need to work with this class directly when // testing anonymous class members of mStackScroller, like mMenuEventListener, // which refer to members of NotificationStackScrollLayout. The spy - // holds a copy of the CUT's instances of these classes, so they still refer to the CUT's - // member variables, not the spy's member variables. + // holds a copy of the CUT's instances of these KeyguardBypassController, so they still + // refer to the CUT's member variables, not the spy's member variables. mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null, true /* allowLongPress */, mNotificationRoundnessManager, mock(DynamicPrivacyController.class), @@ -293,7 +309,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_noNotifications() { setBarStateForTest(StatusBarState.SHADE); - assertEquals(0, mNotificationData.getActiveNotifications().size()); + assertEquals(0, mEntryManager.getActiveNotificationsCount()); mStackScroller.updateFooter(); verify(mStackScroller, atLeastOnce()).updateFooterView(false, false); @@ -303,8 +319,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { public void testUpdateFooter_remoteInput() { setBarStateForTest(StatusBarState.SHADE); ArrayList<NotificationEntry> entries = new ArrayList<>(); - entries.add(mock(NotificationEntry.class)); - when(mNotificationData.getActiveNotifications()).thenReturn(entries); + entries.add(new NotificationEntryBuilder().build()); + addEntriesToEntryManager(entries); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); when(row.canViewBeDismissed()).thenReturn(true); @@ -319,9 +335,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_oneClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + ArrayList<NotificationEntry> entries = new ArrayList<>(); - entries.add(mock(NotificationEntry.class)); - when(mNotificationData.getActiveNotifications()).thenReturn(entries); + entries.add(new NotificationEntryBuilder().build()); + addEntriesToEntryManager(entries); ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); when(row.canViewBeDismissed()).thenReturn(true); @@ -335,10 +352,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Test public void testUpdateFooter_oneNonClearableNotification() { setBarStateForTest(StatusBarState.SHADE); + ArrayList<NotificationEntry> entries = new ArrayList<>(); - entries.add(mock(NotificationEntry.class)); - when(mEntryManager.getNotificationData().getActiveNotifications()).thenReturn(entries); - assertTrue(mEntryManager.getNotificationData().getActiveNotifications().size() != 0); + entries.add(new NotificationEntryBuilder().build()); + addEntriesToEntryManager(entries); mStackScroller.updateFooter(); verify(mStackScroller).updateFooterView(true, false); @@ -460,4 +477,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // rather than the mock because the spy just coppied the anonymous inner /shruggie. mStackScroller.setStatusBarState(state); } + + private void addEntriesToEntryManager(List<NotificationEntry> entries) { + for (NotificationEntry e : entries) { + mEntryManager.addActiveNotificationForTest(e); + } + } + + private void addActiveNotificationsToManager(List<NotificationEntry> entries) { + mEntryManager.setActiveNotificationList(entries); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java index 64c1b510cbf9..a02445487dc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java @@ -40,7 +40,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import org.junit.Before; @@ -51,8 +50,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.ArrayList; - @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -61,7 +58,6 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS; @Mock private NotificationEntryManager mEntryManager; - @Mock private NotificationData mNotificationData; @Mock private CommandQueue mCommandQueue; @Mock private WindowManager mWindowManager; @Mock private Display mDisplay; @@ -71,7 +67,6 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { private View mLightsOutView; private LightsOutNotifController mLightsOutNotifController; - private ArrayList<NotificationEntry> mActiveNotifications = new ArrayList<>(); private int mDisplayId; private NotificationEntryListener mEntryListener; private CommandQueue.Callbacks mCallbacks; @@ -81,8 +76,6 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mDisplayId = mContext.getDisplayId(); mLightsOutView = new View(mContext); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); - when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications); when(mWindowManager.getDefaultDisplay()).thenReturn(mDisplay); when(mDisplay.getDisplayId()).thenReturn(mDisplayId); @@ -136,7 +129,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOut_withNotifs_onSystemBarAppearanceChanged() { // GIVEN active visible notifications - mActiveNotifications.add(mock(NotificationEntry.class)); + when(mEntryManager.hasActiveNotifications()).thenReturn(true); // WHEN lights out mCallbacks.onSystemBarAppearanceChanged( @@ -153,7 +146,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOut_withoutNotifs_onSystemBarAppearanceChanged() { // GIVEN no active visible notifications - mActiveNotifications.clear(); + when(mEntryManager.hasActiveNotifications()).thenReturn(false); // WHEN lights out mCallbacks.onSystemBarAppearanceChanged( @@ -170,7 +163,7 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testLightsOn_afterLightsOut_onSystemBarAppearanceChanged() { // GIVEN active visible notifications - mActiveNotifications.add(mock(NotificationEntry.class)); + when(mEntryManager.hasActiveNotifications()).thenReturn(true); // WHEN lights on mCallbacks.onSystemBarAppearanceChanged( @@ -187,13 +180,13 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testEntryAdded() { // GIVEN no visible notifications and lights out - mActiveNotifications.clear(); + when(mEntryManager.hasActiveNotifications()).thenReturn(false); mLightsOutNotifController.mAppearance = LIGHTS_OUT; mLightsOutNotifController.updateLightsOutView(); assertIsShowingDot(false); // WHEN an active notification is added - mActiveNotifications.add(mock(NotificationEntry.class)); + when(mEntryManager.hasActiveNotifications()).thenReturn(true); assertTrue(mLightsOutNotifController.shouldShowDot()); mEntryListener.onNotificationAdded(mock(NotificationEntry.class)); @@ -204,13 +197,13 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testEntryRemoved() { // GIVEN a visible notification and lights out - mActiveNotifications.add(mock(NotificationEntry.class)); + when(mEntryManager.hasActiveNotifications()).thenReturn(true); mLightsOutNotifController.mAppearance = LIGHTS_OUT; mLightsOutNotifController.updateLightsOutView(); assertIsShowingDot(true); // WHEN all active notifications are removed - mActiveNotifications.clear(); + when(mEntryManager.hasActiveNotifications()).thenReturn(false); assertFalse(mLightsOutNotifController.shouldShowDot()); mEntryListener.onEntryRemoved(mock(NotificationEntry.class), null, false); @@ -221,13 +214,13 @@ public class LightsOutNotifControllerTest extends SysuiTestCase { @Test public void testEntryUpdated() { // GIVEN no visible notifications and lights out - mActiveNotifications.clear(); + when(mEntryManager.hasActiveNotifications()).thenReturn(false); mLightsOutNotifController.mAppearance = LIGHTS_OUT; mLightsOutNotifController.updateLightsOutView(); assertIsShowingDot(false); // WHEN an active notification is added - mActiveNotifications.add(mock(NotificationEntry.class)); + when(mEntryManager.hasActiveNotifications()).thenReturn(true); assertTrue(mLightsOutNotifController.shouldShowDot()); mEntryListener.onPostEntryUpdated(mock(NotificationEntry.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java index 280cc90c0ca4..c165e561b393 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java @@ -54,11 +54,9 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.collection.NotificationData; +import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.logging.NotifLog; -import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -239,11 +237,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { mock(ShadeController.class), mock(NotificationLockscreenUserManager.class), new NotificationEntryManager( - new NotificationData( - mock(NotificationSectionsFeatureManager.class), - mock(NotifLog.class), - mock(PeopleNotificationIdentifier.class)), - mock(NotifLog.class)), + mock(NotifLog.class), + mock(NotificationGroupManager.class), + mock(NotificationRankingManager.class), + mock(NotificationEntryManager.KeyguardEnvironment.class)), mock(KeyguardStateController.class), statusBarStateController, mock(DozeLog.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index d8a68b0c230c..24a5d932fd85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -68,7 +68,6 @@ import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -117,7 +116,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private Intent mContentIntentInner; @Mock - private NotificationData mNotificationData; private NotificationActivityStarter mNotificationActivityStarter; @@ -134,7 +132,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController); - when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); when(mContentIntent.isActivity()).thenReturn(true); when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1)); @@ -157,7 +154,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mActiveNotifications = new ArrayList<>(); mActiveNotifications.add(mNotificationRow.getEntry()); mActiveNotifications.add(mBubbleNotificationRow.getEntry()); - when(mNotificationData.getActiveNotifications()).thenReturn(mActiveNotifications); + when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); mNotificationActivityStarter = new StatusBarNotificationActivityStarter(getContext(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index de87d3197ef6..1c02b60902b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -52,7 +52,6 @@ import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -100,11 +99,9 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mDependency.injectMockDependency(NotificationGutsManager.class); mDependency.injectMockDependency(StatusBarWindowController.class); mDependency.injectMockDependency(InitController.class); - NotificationData notificationData = mock(NotificationData.class); - when(notificationData.getNotificationsForCurrentUser()).thenReturn(new ArrayList<>()); NotificationEntryManager entryManager = mDependency.injectMockDependency(NotificationEntryManager.class); - when(entryManager.getNotificationData()).thenReturn(notificationData); + when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>()); StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class); when(statusBarWindowView.getResources()).thenReturn(mContext.getResources()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index f3273784c6ca..95929c3adcb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -101,7 +101,6 @@ import com.android.systemui.statusbar.NotificationEntryBuilder; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.PulseExpansionHandler; @@ -120,12 +119,9 @@ import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationData; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -177,7 +173,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ArrayList<NotificationEntry> mNotificationList; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; - @Mock private NotificationData mNotificationData; @Mock private NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor; @Mock private VisualStabilityManager mVisualStabilityManager; @Mock private NotificationListener mNotificationListener; @@ -240,6 +235,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ViewMediatorCallback mViewMediatorCallback; @Mock private DismissCallbackRegistry mDismissCallbackRegistry; @Mock private ScreenPinningRequest mScreenPinningRequest; + @Mock private NotificationEntryManager mEntryManager; @Before public void setup() throws Exception { @@ -260,10 +256,8 @@ public class StatusBarTest extends SysuiTestCase { mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); mMetricsLogger = new FakeMetricsLogger(); - TestableNotificationEntryManager entryManager = new TestableNotificationEntryManager( - mNotificationData); NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener, - Dependency.get(UiOffloadThread.class), entryManager, mStatusBarStateController, + Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController, mExpansionStateLogger); notificationLogger.setVisibilityReporter(mock(Runnable.class)); @@ -332,7 +326,7 @@ public class StatusBarTest extends SysuiTestCase { ), mNotificationGutsManager, notificationLogger, - entryManager, + mEntryManager, mNotificationInterruptionStateProvider, mNotificationViewHierarchyManager, mKeyguardViewMediator, @@ -407,9 +401,6 @@ public class StatusBarTest extends SysuiTestCase { mStatusBar.mStatusBarWindowViewController = mStatusBarWindowViewController; mStatusBar.startKeyguard(); Dependency.get(InitController.class).executePostInitTasks(); - entryManager.setUpForTest(mock(NotificationPresenter.class), mStackScroller, - mHeadsUpManager); - entryManager.addNotificationEntryListener(mEntryListener); notificationLogger.setUpWithContainer(mStackScroller); } @@ -645,8 +636,7 @@ public class StatusBarTest extends SysuiTestCase { public void testPanelOpenForHeadsUp() { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); - when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); - when(mNotificationList.size()).thenReturn(5); + when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -664,8 +654,8 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); - when(mNotificationList.size()).thenReturn(5); + when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); + when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.SHADE); @@ -683,8 +673,7 @@ public class StatusBarTest extends SysuiTestCase { @Test public void testPanelOpenAndNoClear() { when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); - when(mNotificationData.getActiveNotifications()).thenReturn(mNotificationList); - when(mNotificationList.size()).thenReturn(5); + when(mEntryManager.getActiveNotificationsCount()).thenReturn(5); when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false); mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD); @@ -842,19 +831,6 @@ public class StatusBarTest extends SysuiTestCase { any(UserHandle.class)); } - public static class TestableNotificationEntryManager extends NotificationEntryManager { - - public TestableNotificationEntryManager(NotificationData notificationData) { - super(notificationData, mock(NotifLog.class)); - } - - public void setUpForTest(NotificationPresenter presenter, - NotificationListContainer listContainer, - HeadsUpManagerPhone headsUpManager) { - super.setUpWithPresenter(presenter, listContainer, headsUpManager); - } - } - public static class TestableNotificationInterruptionStateProvider extends NotificationInterruptionStateProvider { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java index 0bc7868aab9c..e15ca1da928d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -64,7 +64,8 @@ public class FakeSensorManager extends SensorManager { .getDefaultSensor(Sensor.TYPE_PROXIMITY); if (proxSensor == null) { // No prox? Let's create a fake one! - proxSensor = createSensor(Sensor.TYPE_PROXIMITY, null); + proxSensor = + createSensor(Sensor.TYPE_PROXIMITY, null, 1 /* SENSOR_FLAG_WAKE_UP_SENSOR */); } mSensors = new FakeGenericSensor[]{ @@ -92,18 +93,6 @@ public class FakeSensorManager extends SensorManager { if (s != null) { return s; } - switch(type) { - case Sensor.TYPE_PROXIMITY: - try { - return createSensor(Sensor.TYPE_PROXIMITY, null); - } catch (Exception e) { - // fall through - } - break; - default: - break; - - } // Our mock sensors aren't wakeup, and it's a pain to create them that way. Instead, just // return non-wakeup sensors if we can't find a wakeup sensor. return getDefaultSensor(type, false /* wakeup */); @@ -208,6 +197,10 @@ public class FakeSensorManager extends SensorManager { } private Sensor createSensor(int type, @Nullable String stringType) throws Exception { + return createSensor(type, stringType, 0 /* flags */); + } + + private Sensor createSensor(int type, @Nullable String stringType, int flags) throws Exception { Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); constr.setAccessible(true); Sensor sensor = constr.newInstance(); @@ -225,7 +218,7 @@ public class FakeSensorManager extends SensorManager { setSensorField(sensor, "mPower", 1); setSensorField(sensor, "mMinDelay", 1000); setSensorField(sensor, "mMaxDelay", 1000000000); - setSensorField(sensor, "mFlags", 0); + setSensorField(sensor, "mFlags", flags); setSensorField(sensor, "mId", -1); return sensor; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6f435294268f..7fdd83bebbcd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -98,6 +98,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.xmlpull.v1.XmlPullParserException; @@ -179,6 +180,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final AccessibilityDisplayListener mA11yDisplayListener; + private final ActivityTaskManagerInternal mActivityTaskManagerService; + private final MainHandler mMainHandler; private final SystemActionPerformer mSystemActionPerformer; @@ -260,6 +263,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); + mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( @@ -1435,7 +1439,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, this, mWindowManagerService, mSystemActionPerformer, - mA11yWindowManager); + mA11yWindowManager, mActivityTaskManagerService); } else if (userState.mBoundServices.contains(service)) { continue; } @@ -2411,7 +2415,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState, mContext, COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, AccessibilityManagerService.this, mWindowManagerService, - mSystemActionPerformer, mA11yWindowManager) { + mSystemActionPerformer, mA11yWindowManager, mActivityTaskManagerService) { @Override public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) { return true; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index a0a755a30cb3..6cadb6d0f31f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -35,6 +35,7 @@ import android.provider.Settings; import android.util.Slog; import android.view.Display; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import java.lang.ref.WeakReference; @@ -59,6 +60,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect */ final WeakReference<AccessibilityUserState> mUserStateWeakReference; final Intent mIntent; + final ActivityTaskManagerInternal mActivityTaskManagerService; private final Handler mMainHandler; @@ -67,7 +69,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, WindowManagerInternal windowManagerInternal, - SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) { + SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm, + ActivityTaskManagerInternal activityTaskManagerService) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm); mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState); @@ -75,6 +78,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect mMainHandler = mainHandler; mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.accessibility_binding_label); + mActivityTaskManagerService = activityTaskManagerService; final long identity = Binder.clearCallingIdentity(); try { mIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, mSystemSupport.getPendingIntentActivity( @@ -101,6 +105,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } finally { Binder.restoreCallingIdentity(identity); } + mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), + mAccessibilityServiceInfo.getResolveInfo().serviceInfo.applicationInfo.uid, + userState.mUserId); } public void unbindLocked() { @@ -109,6 +116,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (userState == null) return; userState.removeServiceLocked(this); mSystemSupport.getMagnificationController().resetAllIfNeeded(mId); + mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), -1, + userState.mUserId); resetLocked(); } @@ -207,6 +216,11 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void onServiceDisconnected(ComponentName componentName) { binderDied(); + AccessibilityUserState userState = mUserStateWeakReference.get(); + if (userState != null) { + mActivityTaskManagerService.setAllowAppSwitches(mComponentName.flattenToString(), -1, + userState.mUserId); + } } @Override diff --git a/services/core/Android.bp b/services/core/Android.bp index 770de09382fe..c6bc1068da39 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -91,6 +91,7 @@ java_library_static { "android.hardware.light-V2.0-java", "android.hardware.power-V1.0-java", "android.hardware.tv.cec-V1.0-java", + "app-compat-annotations", ], required: [ diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9fa572f9fe9a..09cbc5c67ab7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2919,11 +2919,14 @@ public class AudioService extends IAudioService.Stub final boolean currentMute = AudioSystem.isMicrophoneMuted(); final long identity = Binder.clearCallingIdentity(); AudioSystem.muteMicrophone(muted); - Binder.restoreCallingIdentity(identity); - if (muted != currentMute) { - mContext.sendBroadcastAsUser( + try { + if (muted != currentMute) { + mContext.sendBroadcastAsUser( new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL); + } + } finally { + Binder.restoreCallingIdentity(identity); } } } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 4a62bc507d92..bc7307b3ee6c 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -394,6 +394,15 @@ public final class ContentService extends IContentService.Stub { * allowed. */ @Override + public void notifyChange(Uri[] uris, IContentObserver observer, + boolean observerWantsSelfNotifications, int flags, int userHandle, + int targetSdkVersion, String callingPackage) { + for (Uri uri : uris) { + notifyChange(uri, observer, observerWantsSelfNotifications, flags, userHandle, + targetSdkVersion, callingPackage); + } + } + public void notifyChange(Uri uri, IContentObserver observer, boolean observerWantsSelfNotifications, int flags, int userHandle, int targetSdkVersion, String callingPackage) { diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 99b1ef4a1d2e..f05b2bf9711f 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -16,8 +16,15 @@ package com.android.server.notification; +import android.app.AlarmManager; import android.app.NotificationHistory; import android.app.NotificationHistory.HistoricalNotification; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; import android.os.Handler; import android.util.AtomicFile; import android.util.Slog; @@ -33,11 +40,15 @@ import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; +import java.util.concurrent.TimeUnit; /** * Provides an interface to write and query for notification history data for a user from a Protocol @@ -52,32 +63,48 @@ public class NotificationHistoryDatabase { private static final String TAG = "NotiHistoryDatabase"; private static final boolean DEBUG = NotificationManagerService.DBG; private static final int HISTORY_RETENTION_DAYS = 2; + private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000; private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20; + private static final String ACTION_HISTORY_DELETION = + NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP"; + private static final int REQUEST_CODE_DELETION = 1; + private static final String SCHEME_DELETION = "delete"; + private static final String EXTRA_KEY = "key"; + + private final Context mContext; + private final AlarmManager mAlarmManager; private final Object mLock = new Object(); private Handler mFileWriteHandler; @VisibleForTesting // List of files holding history information, sorted newest to oldest final LinkedList<AtomicFile> mHistoryFiles; - private final GregorianCalendar mCal; private final File mHistoryDir; private final File mVersionFile; // Current version of the database files schema private int mCurrentVersion; private final WriteBufferRunnable mWriteBufferRunnable; + private final FileAttrProvider mFileAttrProvider; // Object containing posted notifications that have not yet been written to disk @VisibleForTesting NotificationHistory mBuffer; - public NotificationHistoryDatabase(File dir) { + public NotificationHistoryDatabase(Context context, File dir, + FileAttrProvider fileAttrProvider) { + mContext = context; + mAlarmManager = context.getSystemService(AlarmManager.class); mCurrentVersion = DEFAULT_CURRENT_VERSION; mVersionFile = new File(dir, "version"); mHistoryDir = new File(dir, "history"); mHistoryFiles = new LinkedList<>(); - mCal = new GregorianCalendar(); mBuffer = new NotificationHistory(); mWriteBufferRunnable = new WriteBufferRunnable(); + mFileAttrProvider = fileAttrProvider; + + IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION); + deletionFilter.addDataScheme(SCHEME_DELETION); + mContext.registerReceiver(mFileCleaupReceiver, deletionFilter); } public void init(Handler fileWriteHandler) { @@ -105,7 +132,8 @@ public class NotificationHistoryDatabase { } // Sort with newest files first - Arrays.sort(files, (lhs, rhs) -> Long.compare(rhs.lastModified(), lhs.lastModified())); + Arrays.sort(files, (lhs, rhs) -> Long.compare(mFileAttrProvider.getCreationTime(rhs), + mFileAttrProvider.getCreationTime(lhs))); for (File file : files) { mHistoryFiles.addLast(new AtomicFile(file)); @@ -197,31 +225,48 @@ public class NotificationHistoryDatabase { } /** - * Remove any files that are too old. + * Remove any files that are too old and schedule jobs to clean up the rest */ public void prune(final int retentionDays, final long currentTimeMillis) { synchronized (mLock) { - mCal.setTimeInMillis(currentTimeMillis); - mCal.add(Calendar.DATE, -1 * retentionDays); - - while (!mHistoryFiles.isEmpty()) { - final AtomicFile currentOldestFile = mHistoryFiles.getLast(); - final long age = currentTimeMillis - - currentOldestFile.getBaseFile().lastModified(); - if (age > mCal.getTimeInMillis()) { + GregorianCalendar retentionBoundary = new GregorianCalendar(); + retentionBoundary.setTimeInMillis(currentTimeMillis); + retentionBoundary.add(Calendar.DATE, -1 * retentionDays); + + for (int i = mHistoryFiles.size() - 1; i >= 0; i--) { + final AtomicFile currentOldestFile = mHistoryFiles.get(i); + final long creationTime = + mFileAttrProvider.getCreationTime(currentOldestFile.getBaseFile()); + if (creationTime <= retentionBoundary.getTimeInMillis()) { if (DEBUG) { Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName()); } currentOldestFile.delete(); mHistoryFiles.removeLast(); } else { - // all remaining files are newer than the cut off - return; + // all remaining files are newer than the cut off; schedule jobs to delete + final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS); + scheduleDeletion(currentOldestFile.getBaseFile(), deletionTime); } } } } + void scheduleDeletion(File file, long deletionTime) { + if (DEBUG) { + Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime); + } + final PendingIntent pi = PendingIntent.getBroadcast(mContext, + REQUEST_CODE_DELETION, + new Intent(ACTION_HISTORY_DELETION) + .setData(new Uri.Builder().scheme(SCHEME_DELETION) + .appendPath(file.getAbsolutePath()).build()) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(EXTRA_KEY, file.getAbsolutePath()), + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi); + } + private void writeLocked(AtomicFile file, NotificationHistory notifications) throws IOException { FileOutputStream fos = file.startWrite(); @@ -245,6 +290,25 @@ public class NotificationHistoryDatabase { } } + private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + if (ACTION_HISTORY_DELETION.equals(action)) { + try { + final String filePath = intent.getStringExtra(EXTRA_KEY); + AtomicFile fileToDelete = new AtomicFile(new File(filePath)); + fileToDelete.delete(); + } catch (Exception e) { + Slog.e(TAG, "Failed to delete notification history file", e); + } + } + } + }; + private final class WriteBufferRunnable implements Runnable { @Override public void run() { @@ -277,10 +341,7 @@ public class NotificationHistoryDatabase { // Remove packageName entries from pending history mBuffer.removeNotificationsFromWrite(mPkg); - // Remove packageName entries from files on disk, and rewrite them to disk - // Since we sort by modified date, we have to update the files oldest to newest to - // maintain the original ordering - Iterator<AtomicFile> historyFileItr = mHistoryFiles.descendingIterator(); + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); while (historyFileItr.hasNext()) { final AtomicFile af = historyFileItr.next(); try { @@ -297,4 +358,24 @@ public class NotificationHistoryDatabase { } } } + + public static final class NotificationHistoryFileAttrProvider implements + NotificationHistoryDatabase.FileAttrProvider { + final static String TAG = "NotifHistoryFileDate"; + + public long getCreationTime(File file) { + try { + BasicFileAttributes attr = Files.readAttributes(FileSystems.getDefault().getPath( + file.getAbsolutePath()), BasicFileAttributes.class); + return attr.creationTime().to(TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.w(TAG, "Cannot read creation data for file; using file name"); + return Long.valueOf(file.getName()); + } + } + } + + interface FileAttrProvider { + long getCreationTime(File file); + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 0a3c58150689..cbbf2a0ee04b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -757,7 +757,7 @@ public class PreferencesHelper implements RankingConfig { clearLockedFieldsLocked(channel); channel.setImportanceLockedByOEM(r.oemLockedImportance); if (!channel.isImportanceLockedByOEM()) { - if (r.futureOemLockedChannels.remove(channel.getId())) { + if (r.oemLockedChannels.contains(channel.getId())) { channel.setImportanceLockedByOEM(true); } } @@ -952,11 +952,10 @@ public class PreferencesHelper implements RankingConfig { NotificationChannel channel = r.channels.get(channelId); if (channel != null) { channel.setImportanceLockedByOEM(true); - } else { - // if this channel shows up in the future, make sure it'll - // be locked immediately - r.futureOemLockedChannels.add(channelId); } + // Also store the locked channels on the record, so they aren't + // temporarily lost when data is cleared on the package + r.oemLockedChannels.add(channelId); } } } @@ -1528,9 +1527,9 @@ public class PreferencesHelper implements RankingConfig { pw.print(" oemLocked="); pw.print(r.oemLockedImportance); } - if (!r.futureOemLockedChannels.isEmpty()) { + if (!r.oemLockedChannels.isEmpty()) { pw.print(" futureLockedChannels="); - pw.print(r.futureOemLockedChannels); + pw.print(r.oemLockedChannels); } pw.println(); for (NotificationChannel channel : r.channels.values()) { @@ -1940,7 +1939,7 @@ public class PreferencesHelper implements RankingConfig { // these fields are loaded on boot from a different source of truth and so are not // written to notification policy xml boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE; - List<String> futureOemLockedChannels = new ArrayList<>(); + List<String> oemLockedChannels = new ArrayList<>(); boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE; Delegate delegate = null; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 631df0ff9845..38649a772b8f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2213,8 +2213,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED); // Send broadcast to default launcher only if it's a new install + // TODO(b/144270665): Secure the usage of this broadcast. final boolean isNewInstall = extras == null || !extras.getBoolean(Intent.EXTRA_REPLACING); - if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts()) { + if (success && isNewInstall && mPm.mInstallerService.okToSendBroadcasts() + && (params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { mPm.sendSessionCommitBroadcast(generateInfo(), userId); } diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index c779ebf4a995..3aafd5e35dc4 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -403,7 +403,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn public String getStatus() { return mContext.getString( com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, + Build.VERSION.RELEASE_OR_CODENAME, Build.ID); } } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 0a861ade2900..73034b020a76 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -548,7 +548,7 @@ class ActivityMetricsLogger { private static boolean hasActivityToBeDrawn(Task t) { for (int i = t.getChildCount() - 1; i >= 0; --i) { final ActivityRecord r = t.getChildAt(i); - if (r.mVisibleRequested && !r.mDrawn && !r.finishing) { + if (r.visible && !r.mDrawn && !r.finishing) { return true; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2162bdeead05..3d41608763cb 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -121,7 +121,6 @@ import static com.android.server.am.ActivityRecordProto.PROC_ID; import static com.android.server.am.ActivityRecordProto.STATE; import static com.android.server.am.ActivityRecordProto.TRANSLUCENT; import static com.android.server.am.ActivityRecordProto.VISIBLE; -import static com.android.server.am.ActivityRecordProto.VISIBLE_REQUESTED; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; @@ -173,10 +172,12 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_W import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked; import static com.android.server.wm.AppWindowTokenProto.ALL_DRAWN; import static com.android.server.wm.AppWindowTokenProto.APP_STOPPED; -import static com.android.server.wm.AppWindowTokenProto.CLIENT_VISIBLE; +import static com.android.server.wm.AppWindowTokenProto.CLIENT_HIDDEN; import static com.android.server.wm.AppWindowTokenProto.DEFER_HIDING_CLIENT; import static com.android.server.wm.AppWindowTokenProto.FILLS_PARENT; import static com.android.server.wm.AppWindowTokenProto.FROZEN_BOUNDS; +import static com.android.server.wm.AppWindowTokenProto.HIDDEN_REQUESTED; +import static com.android.server.wm.AppWindowTokenProto.HIDDEN_SET_FROM_TRANSFERRED_STARTING_WINDOW; import static com.android.server.wm.AppWindowTokenProto.IS_ANIMATING; import static com.android.server.wm.AppWindowTokenProto.IS_WAITING_FOR_TRANSITION_START; import static com.android.server.wm.AppWindowTokenProto.LAST_ALL_DRAWN; @@ -191,7 +192,6 @@ import static com.android.server.wm.AppWindowTokenProto.STARTING_DISPLAYED; import static com.android.server.wm.AppWindowTokenProto.STARTING_MOVED; import static com.android.server.wm.AppWindowTokenProto.STARTING_WINDOW; import static com.android.server.wm.AppWindowTokenProto.THUMBNAIL; -import static com.android.server.wm.AppWindowTokenProto.VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW; import static com.android.server.wm.AppWindowTokenProto.WINDOW_TOKEN; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; @@ -462,16 +462,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private boolean keysPaused; // has key dispatching been paused for it? int launchMode; // the launch mode activity attribute. int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override - private boolean mVisible; // Should this token's windows be visible? + boolean visible; // does this activity's window need to be shown? boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard // might hide this activity? - // True if the visible state of this token was forced to true due to a transferred starting + // True if the hidden state of this token was forced to false due to a transferred starting // window. - private boolean mVisibleSetFromTransferredStartingWindow; - // TODO: figure out how to consolidate with the same variable in ActivityRecord. + private boolean mHiddenSetFromTransferredStartingWindow; + // TODO: figureout how to consolidate with the same variable in ActivityRecord. private boolean mDeferHidingClient; // If true we told WM to defer reporting to the client // process that it is hidden. - private boolean mLastDeferHidingClient; // If true we will defer setting mClientVisible to false + private boolean mLastDeferHidingClient; // If true we will defer setting mClientHidden to true // and reporting to the client that it is hidden. boolean sleeping; // have we told the activity to sleep? boolean nowVisible; // is this activity's window visible? @@ -536,8 +536,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private Task mLastParent; - // Have we told the window clients to show themselves? - private boolean mClientVisible; + // Have we told the window clients to hide themselves? + private boolean mClientHidden; boolean firstWindowDrawn; // Last drawn state we reported to the app token. @@ -622,11 +622,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // case do not clear allDrawn until the animation completes. boolean deferClearAllDrawn; - // Is this window's surface needed? This is almost like visible, except - // it will sometimes be true a little earlier: when the activity record has + // Is this window's surface needed? This is almost like hidden, except + // it will sometimes be true a little earlier: when the token has // been shown, but is still waiting for its app transition to execute // before making its windows shown. - boolean mVisibleRequested; + boolean hiddenRequested; // Last visibility state we reported to the app token. boolean reportedVisible; @@ -836,6 +836,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" finishing="); pw.println(finishing); pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); + pw.print(" visible="); pw.print(visible); pw.print(" sleeping="); pw.print(sleeping); pw.print(" idle="); pw.print(idle); pw.print(" mStartingWindowState="); @@ -854,14 +855,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(requestedVrComponent); } super.dump(pw, prefix, dumpAll); - pw.print(" visible="); pw.print(mVisible); if (appToken != null) { pw.println(prefix + "app=true mVoiceInteraction=" + mVoiceInteraction); } pw.print(prefix); pw.print(" mOccludesParent="); pw.print(mOccludesParent); pw.print(" mOrientation="); pw.println(mOrientation); - pw.println(prefix + "mVisibleRequested=" + mVisibleRequested - + " mClientVisible=" + mClientVisible + pw.println(prefix + "hiddenRequested=" + hiddenRequested + " mClientHidden=" + mClientHidden + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") + " reportedDrawn=" + reportedDrawn + " reportedVisible=" + reportedVisible); if (paused) { @@ -886,13 +885,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" mIsExiting="); pw.println(mIsExiting); } if (startingWindow != null || startingSurface != null - || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) { + || startingDisplayed || startingMoved || mHiddenSetFromTransferredStartingWindow) { pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow); pw.print(" startingSurface="); pw.print(startingSurface); pw.print(" startingDisplayed="); pw.print(startingDisplayed); pw.print(" startingMoved="); pw.print(startingMoved); pw.println(" mHiddenSetFromTransferredStartingWindow=" - + mVisibleSetFromTransferredStartingWindow); + + mHiddenSetFromTransferredStartingWindow); } if (!mFrozenBounds.isEmpty()) { pw.print(prefix); pw.print("mFrozenBounds="); pw.println(mFrozenBounds); @@ -1488,8 +1487,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Application tokens start out hidden. - setVisible(false); - mVisibleRequested = false; + setHidden(true); + hiddenRequested = true; ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService( ColorDisplayService.ColorDisplayServiceInternal.class); @@ -1518,9 +1517,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A deferRelaunchUntilPaused = false; keysPaused = false; inHistory = false; + visible = false; nowVisible = false; mDrawn = false; - mClientVisible = true; idle = false; hasBeenLaunched = false; mStackSupervisor = supervisor; @@ -2201,7 +2200,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * 2. App is delayed closing since it might enter PIP. */ boolean isClosingOrEnteringPip() { - return (isAnimating(TRANSITION | PARENTS) && !mVisibleRequested) || mWillCloseOrEnterPip; + return (isAnimating(TRANSITION | PARENTS) && hiddenRequested) || mWillCloseOrEnterPip; } /** * @return Whether AppOps allows this package to enter picture-in-picture. @@ -2460,7 +2459,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAtmService.getLockTaskController().clearLockedTask(task); } } else if (!isState(PAUSING)) { - if (mVisibleRequested) { + if (visible) { // Prepare and execute close transition. prepareActivityHideTransitionAnimation(transit); } @@ -2539,13 +2538,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere final ActivityRecord next = getDisplay().topRunningActivity( true /* considerKeyguardState */); - final boolean isVisible = mVisibleRequested || nowVisible; + final boolean isVisible = visible || nowVisible; // isNextNotYetVisible is to check if the next activity is invisible, or it has been // requested to be invisible but its windows haven't reported as invisible. If so, it // implied that the current finishing activity should be added into stopping list rather // than destroy immediately. - final boolean isNextNotYetVisible = next != null - && (!next.nowVisible || !next.mVisibleRequested); + final boolean isNextNotYetVisible = next != null && (!next.nowVisible || !next.visible); if (isVisible && isNextNotYetVisible) { // Add this activity to the list of stopping activities. It will be processed and // destroyed when the next activity reports idle. @@ -3213,7 +3211,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A "Removing starting %s from %s", tStartingWindow, fromActivity); fromActivity.removeChild(tStartingWindow); fromActivity.postWindowRemoveStartingWindowCleanup(tStartingWindow); - fromActivity.mVisibleSetFromTransferredStartingWindow = false; + fromActivity.mHiddenSetFromTransferredStartingWindow = false; addWindow(tStartingWindow); // Propagate other interesting state between the tokens. If the old token is displayed, @@ -3226,12 +3224,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (fromActivity.firstWindowDrawn) { firstWindowDrawn = true; } - if (fromActivity.isVisible()) { - setVisible(true); - mVisibleRequested = true; - mVisibleSetFromTransferredStartingWindow = true; + if (!fromActivity.isHidden()) { + setHidden(false); + hiddenRequested = false; + mHiddenSetFromTransferredStartingWindow = true; } - setClientVisible(fromActivity.mClientVisible); + setClientHidden(fromActivity.mClientHidden); transferAnimation(fromActivity); @@ -3277,7 +3275,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (fromActivity == this) { return; } - if (!fromActivity.mVisibleRequested && transferStartingWindow(fromActivity.token)) { + if (fromActivity.hiddenRequested && transferStartingWindow(fromActivity.token)) { return; } } @@ -3792,10 +3790,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return opts; } - boolean allowMoveToFront() { - return pendingOptions == null || !pendingOptions.getAvoidMoveToFront(); - } - void removeUriPermissionsLocked() { if (uriPermissions != null) { uriPermissions.removeUriPermissions(); @@ -3832,7 +3826,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } mDeferHidingClient = deferHidingClient; - if (!mDeferHidingClient && !mVisibleRequested) { + if (!mDeferHidingClient && !visible) { // Hiding the client is no longer deferred and the app isn't visible still, go ahead and // update the visibility. setVisibility(false); @@ -3843,14 +3837,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isVisible() { // If the activity isn't hidden then it is considered visible and there is no need to check // its children windows to see if they are visible. - return mVisible; - } - - void setVisible(boolean visible) { - if (visible != mVisible) { - mVisible = visible; - scheduleAnimation(); - } + return !isHidden(); } void setVisibility(boolean visible) { @@ -3859,17 +3846,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A + appToken); return; } - if (visible) { - mDeferHidingClient = false; - } setVisibility(visible, mDeferHidingClient); mAtmService.addWindowLayoutReasons( ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED); mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this); + } + + // TODO: Look into merging with #commitVisibility() + void setVisible(boolean newVisible) { + visible = newVisible; + mDeferHidingClient = !visible && mDeferHidingClient; + setVisibility(visible); mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true; } - @VisibleForTesting void setVisibility(boolean visible, boolean deferHidingClient) { final AppTransition appTransition = getDisplayContent().mAppTransition; @@ -3880,20 +3870,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // transition can be selected. // TODO: Probably a good idea to separate the concept of opening/closing apps from the // concept of setting visibility... - if (!visible && !mVisibleRequested) { + if (!visible && hiddenRequested) { if (!deferHidingClient && mLastDeferHidingClient) { // We previously deferred telling the client to hide itself when visibility was // initially set to false. Now we would like it to hide, so go ahead and set it. mLastDeferHidingClient = deferHidingClient; - setClientVisible(false); + setClientHidden(true); } return; } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", - appToken, visible, appTransition, isVisible(), mVisibleRequested, + "setAppVisibility(%s, visible=%b): %s hidden=%b hiddenRequested=%b Callers=%s", + appToken, visible, appTransition, isHidden(), hiddenRequested, Debug.getCallers(6)); final DisplayContent displayContent = getDisplayContent(); @@ -3904,7 +3894,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } displayContent.mChangingApps.remove(this); waitingToShow = false; - mVisibleRequested = visible; + hiddenRequested = !visible; mLastDeferHidingClient = deferHidingClient; if (!visible) { @@ -3923,15 +3913,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. - if (!isVisible() || mAppStopped) { + if (isHidden() || mAppStopped) { clearAllDrawn(); // If the app was already visible, don't reset the waitingToShow state. - if (!isVisible()) { + if (isHidden()) { waitingToShow = true; // If the client isn't hidden, we don't need to reset the drawing state. - if (!isClientVisible()) { + if (isClientHidden()) { // Let's reset the draw state in order to prevent the starting window to be // immediately dismissed when the app still has the surface. forAllWindows(w -> { @@ -3951,7 +3941,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // we still need to tell the client to make its windows visible so they get drawn. // Otherwise, we will wait on performing the transition until all windows have been // drawn, they never will be, and we are sad. - setClientVisible(true); + setClientHidden(false); requestUpdateWallpaperIfNeeded(); @@ -3997,9 +3987,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) { boolean delayed = false; - // Reset the state of mVisibleSetFromTransferredStartingWindow since visibility is actually + // Reset the state of mHiddenSetFromTransferredStartingWindow since visibility is actually // been set by the app now. - mVisibleSetFromTransferredStartingWindow = false; + mHiddenSetFromTransferredStartingWindow = false; // Allow for state changes and animation to be applied if: // * token is transitioning visibility state @@ -4009,7 +3999,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // * or the token is the opening app and visible while opening task behind existing one. final DisplayContent displayContent = getDisplayContent(); boolean visibilityChanged = false; - if (isVisible() != visible || (!isVisible() && mIsExiting) + if (isHidden() == visible || (isHidden() && mIsExiting) || (visible && waitingForReplacement()) || (visible && displayContent.mOpeningApps.contains(this) && displayContent.mAppTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND)) { @@ -4017,7 +4007,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mWmService.mAccessibilityController; boolean changed = false; ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "Changing app %s visible=%b performLayout=%b", this, isVisible(), + "Changing app %s hidden=%b performLayout=%b", this, isHidden(), performLayout); boolean runningAppAnimation = false; @@ -4042,8 +4032,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A changed |= win.onAppVisibilityChanged(visible, runningAppAnimation); } - setVisible(visible); - mVisibleRequested = visible; + setHidden(!visible); + hiddenRequested = !visible; visibilityChanged = true; if (!visible) { stopFreezingScreen(true, true); @@ -4061,8 +4051,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "commitVisibility: %s: visible=%b visibleRequested=%b", this, - isVisible(), mVisibleRequested); + "commitVisibility: %s: hidden=%b hiddenRequested=%b", this, + isHidden(), hiddenRequested); if (changed) { displayContent.getInputMonitor().setUpdateInputWindowsNeededLw(); @@ -4099,7 +4089,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If we're becoming invisible, update the client visibility if we are not running an // animation. Otherwise, we'll update client visibility in onAnimationFinished. if (visible || !isAnimating()) { - setClientVisible(visible); + setClientHidden(!visible); } if (!displayContent.mClosingApps.contains(this) @@ -4126,7 +4116,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // no animation but there will still be a transition set. // We still need to delay hiding the surface such that it // can be synchronized with showing the next surface in the transition. - if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) { + if (isHidden() && !delayed && !displayContent.mAppTransition.isTransitionSet()) { SurfaceControl.openTransaction(); for (int i = mChildren.size() - 1; i >= 0; i--) { mChildren.get(i).mWinAnimator.hide("immediately hidden"); @@ -4139,6 +4129,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override + void setHidden(boolean hidden) { + super.setHidden(hidden); + scheduleAnimation(); + } + + @Override void onAppTransitionDone() { sendingToBottom = false; } @@ -4401,7 +4397,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateOptionsLocked(returningOptions); stack.mUndrawnActivitiesBelowTopTranslucent.add(this); } - setVisibility(true); + setVisible(true); sleeping = false; app.postPendingUiCleanMsg(true); if (reportToClient) { @@ -4437,7 +4433,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void makeInvisible() { - if (!mVisibleRequested) { + if (!visible) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this); return; } @@ -4459,7 +4455,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean deferHidingClient = canEnterPictureInPicture && !isState(STOPPING, STOPPED, PAUSED); setDeferHidingClient(deferHidingClient); - setVisibility(false); + setVisible(false); switch (getState()) { case STOPPING: @@ -4646,8 +4642,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * state to match that fact. */ void completeResumeLocked() { - final boolean wasVisible = mVisibleRequested; - setVisibility(true); + final boolean wasVisible = visible; + setVisible(true); if (!wasVisible) { // Visibility has changed, so take a note of it so we call the TaskStackChangedListener mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true; @@ -4731,16 +4727,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } setState(STOPPING, "stopIfPossible"); if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "Stopping visibleRequested=" - + mVisibleRequested + " for " + this); + Slog.v(TAG_VISIBILITY, "Stopping visible=" + visible + " for " + this); } - if (!mVisibleRequested) { - setVisibility(false); + if (!visible) { + setVisible(false); } EventLogTags.writeAmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - StopActivityItem.obtain(mVisibleRequested, configChangeFlags)); + StopActivityItem.obtain(visible, configChangeFlags)); if (stack.shouldSleepOrShutDownActivities()) { setSleeping(true); } @@ -4903,10 +4898,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void startFreezingScreen() { ProtoLog.i(WM_DEBUG_ORIENTATION, - "Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s", - appToken, isVisible(), mFreezingScreen, mVisibleRequested, + "Set freezing of %s: hidden=%b freezing=%b hiddenRequested=%b. %s", + appToken, isHidden(), mFreezingScreen, hiddenRequested, new RuntimeException().fillInStackTrace()); - if (mVisibleRequested) { + if (!hiddenRequested) { if (!mFreezingScreen) { mFreezingScreen = true; mWmService.registerAppFreezeListener(this); @@ -4942,8 +4937,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } ProtoLog.v(WM_DEBUG_ORIENTATION, - "Clear freezing of %s: visible=%b freezing=%b", appToken, - isVisible(), isFreezingScreen()); + "Clear freezing of %s: hidden=%b freezing=%b", appToken, + isHidden(), isFreezingScreen()); stopFreezingScreen(true, force); } } @@ -5103,7 +5098,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean nowGone = mReportedVisibilityResults.nowGone; boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting; - boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && isVisible(); + boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !isHidden(); if (!nowGone) { // If the app is not yet gone, then it can only become visible/drawn. if (!nowDrawn) { @@ -5131,18 +5126,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - boolean isClientVisible() { - return mClientVisible; + boolean isClientHidden() { + return mClientHidden; } - void setClientVisible(boolean clientVisible) { - if (mClientVisible == clientVisible || (!clientVisible && mDeferHidingClient)) { + void setClientHidden(boolean hideClient) { + if (mClientHidden == hideClient || (hideClient && mDeferHidingClient)) { return; } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, - "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible, + "setClientHidden: %s clientHidden=%b Callers=%s", this, hideClient, Debug.getCallers(5)); - mClientVisible = clientVisible; + mClientHidden = hideClient; sendAppVisibilityToClients(); } @@ -5185,7 +5180,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController + " pv=" + w.isVisibleByPolicy() + " mDrawState=" + winAnimator.drawStateToString() - + " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested + + " ph=" + w.isParentWindowHidden() + " th=" + hiddenRequested + " a=" + isAnimating(TRANSITION)); } } @@ -5293,7 +5288,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * currently pausing, or is resumed. */ public boolean isInterestingToUserLocked() { - return mVisibleRequested || nowVisible || mState == PAUSING || mState == RESUMED; + return visible || nowVisible || mState == PAUSING || mState == RESUMED; } void setSleeping(boolean _sleeping) { @@ -5367,7 +5362,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // We're not ready for this kind of thing. return false; } - if (mVisibleRequested) { + if (visible) { // The user would notice this! return false; } @@ -5464,11 +5459,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // well there is no point now. ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Nulling last startingData"); mStartingData = null; - if (mVisibleSetFromTransferredStartingWindow) { - // We set the visible state to true for the token from a transferred starting - // window. We now reset it back to false since the starting window was the last - // window in the token. - setVisible(false); + if (mHiddenSetFromTransferredStartingWindow) { + // We set the hidden state to false for the token from a transferred starting window. + // We now reset it back to true since the starting window was the last window in the + // token. + setHidden(true); } } else if (mChildren.size() == 1 && startingSurface != null && !isRelaunching()) { // If this is the last window except for a starting transition window, @@ -5806,7 +5801,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void prepareSurfaces() { - final boolean show = isVisible() || isAnimating(); + final boolean show = !isHidden() || isAnimating(); if (mSurfaceControl != null) { if (show && !mLastSurfaceShowing) { @@ -5925,7 +5920,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A "AppWindowToken"); clearThumbnail(); - setClientVisible(isVisible() || mVisibleRequested); + setClientHidden(isHidden() && hiddenRequested); getDisplayContent().computeImeTargetIfNeeded(this); @@ -6522,7 +6517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (display == null) { return; } - if (mVisibleRequested) { + if (visible) { // It may toggle the UI for user to restart the size compatibility mode activity. display.handleActivitySizeCompatModeIfNeeded(this); } else if (mCompatDisplayInsets != null) { @@ -6819,7 +6814,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is relaunching " + this); - if (DEBUG_STATES && !mVisibleRequested) { + if (DEBUG_STATES && !visible) { Slog.v(TAG_STATES, "Config is relaunching invisible activity " + this + " called by " + Debug.getCallers(4)); } @@ -7005,7 +7000,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the existing override configuration so it can be updated according to the latest // configuration. clearSizeCompatMode(); - if (mVisibleRequested) { + if (visible) { // Configuration will be ensured when becoming visible, so if it is already visible, // then the manual update is needed. updateSizeCompatMode(); @@ -7018,7 +7013,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The restarting state avoids removing this record when process is died. setState(RESTARTING_PROCESS, "restartActivityProcess"); - if (!mVisibleRequested || mHaveState) { + if (!visible || mHaveState) { // Kill its process immediately because the activity should be in background. // The activity state will be update to {@link #DESTROYED} in // {@link ActivityStack#cleanUp} when handling process died. @@ -7309,13 +7304,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A writeToProto(proto, APP_WINDOW_TOKEN, WindowTraceLogLevel.ALL); writeIdentifierToProto(proto, IDENTIFIER); proto.write(STATE, mState.toString()); - proto.write(VISIBLE_REQUESTED, mVisibleRequested); + proto.write(VISIBLE, visible); proto.write(FRONT_OF_TASK, isRootOfTask()); if (hasProcess()) { proto.write(PROC_ID, app.getPid()); } proto.write(TRANSLUCENT, !occludesParent()); - proto.write(VISIBLE, mVisible); } public void writeToProto(ProtoOutputStream proto, long fieldId) { @@ -7346,8 +7340,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } proto.write(FILLS_PARENT, mOccludesParent); proto.write(APP_STOPPED, mAppStopped); - proto.write(com.android.server.wm.AppWindowTokenProto.VISIBLE_REQUESTED, mVisibleRequested); - proto.write(CLIENT_VISIBLE, mClientVisible); + proto.write(HIDDEN_REQUESTED, hiddenRequested); + proto.write(CLIENT_HIDDEN, mClientHidden); proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient); proto.write(REPORTED_DRAWN, reportedDrawn); proto.write(REPORTED_VISIBLE, reportedVisible); @@ -7361,12 +7355,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } proto.write(STARTING_DISPLAYED, startingDisplayed); proto.write(STARTING_MOVED, startingMoved); - proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW, - mVisibleSetFromTransferredStartingWindow); + proto.write(HIDDEN_SET_FROM_TRANSFERRED_STARTING_WINDOW, + mHiddenSetFromTransferredStartingWindow); for (Rect bounds : mFrozenBounds) { bounds.writeToProto(proto, FROZEN_BOUNDS); } - proto.write(com.android.server.wm.AppWindowTokenProto.VISIBLE, mVisible); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java index 6e75f9c9167f..c56a9e2ac560 100644 --- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java +++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java @@ -73,7 +73,7 @@ public class ActivityServiceConnectionsHolder<T> { public boolean isActivityVisible() { synchronized (mService.mGlobalLock) { - return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING); + return mActivity.visible || mActivity.isState(RESUMED, PAUSING); } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 2aea81724627..6f6e65933502 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -1629,8 +1629,7 @@ class ActivityStack extends TaskStack { prev = prev.completeFinishing("completePausedLocked"); } else if (prev.hasProcess()) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev - + " wasStopping=" + wasStopping - + " visibleRequested=" + prev.mVisibleRequested); + + " wasStopping=" + wasStopping + " visible=" + prev.visible); if (prev.deferRelaunchUntilPaused) { // Complete the deferred relaunch that was waiting for pause to complete. if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Re-launching after pause: " + prev); @@ -1640,7 +1639,7 @@ class ActivityStack extends TaskStack { // We can't clobber it, because the stop confirmation will not be handled. // We don't need to schedule another stop, we only need to let it happen. prev.setState(STOPPING, "completePausedLocked"); - } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) { + } else if (!prev.visible || shouldSleepOrShutDownActivities()) { // Clear out any deferred client hide we might currently have. prev.setDeferHidingClient(false); // If we were visible then resumeTopActivities will release resources before @@ -1761,7 +1760,7 @@ class ActivityStack extends TaskStack { boolean isTopActivityVisible() { final ActivityRecord topActivity = getTopActivity(); - return topActivity != null && topActivity.mVisibleRequested; + return topActivity != null && topActivity.visible; } /** @@ -1902,7 +1901,7 @@ class ActivityStack extends TaskStack { for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) { final Task task = getChildAt(taskNdx); ActivityRecord r = task.topRunningActivityLocked(); - if (r == null || r.finishing || !r.mVisibleRequested) { + if (r == null || r.finishing || !r.visible) { task.mLayerRank = -1; } else { task.mLayerRank = baseLayer + layer++; @@ -1991,7 +1990,7 @@ class ActivityStack extends TaskStack { if (!r.attachedToProcess()) { makeVisibleAndRestartIfNeeded(starting, configChanges, isTop, resumeTopActivity && isTop, r); - } else if (r.mVisibleRequested) { + } else if (r.visible) { // If this activity is already visible, then there is nothing to do here. if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r); @@ -2168,16 +2167,16 @@ class ActivityStack extends TaskStack { // invisible. If the app is already visible, it must have died while it was visible. In this // case, we'll show the dead window but will not restart the app. Otherwise we could end up // thrashing. - if (isTop || !r.mVisibleRequested) { + if (isTop || !r.visible) { // This activity needs to be visible, but isn't even running... // get it started and resume if no other stack in this stack is resumed. if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Start and freeze screen for " + r); if (r != starting) { r.startFreezingScreenLocked(configChanges); } - if (!r.mVisibleRequested || r.mLaunchTaskBehind) { + if (!r.visible || r.mLaunchTaskBehind) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r); - r.setVisibility(true); + r.setVisible(true); } if (r != starting) { mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); @@ -2620,8 +2619,7 @@ class ActivityStack extends TaskStack { if (next.attachedToProcess()) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next - + " stopped=" + next.stopped - + " visibleRequested=" + next.mVisibleRequested); + + " stopped=" + next.stopped + " visible=" + next.visible); // If the previous activity is translucent, force a visibility update of // the next activity, so that it's added to WM's opening app list, and @@ -2636,7 +2634,7 @@ class ActivityStack extends TaskStack { && !lastFocusedStack.mLastPausedActivity.occludesParent())); // This activity is now becoming visible. - if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) { + if (!next.visible || next.stopped || lastActivityTranslucent) { next.setVisibility(true); } @@ -2691,7 +2689,7 @@ class ActivityStack extends TaskStack { // Do over! mStackSupervisor.scheduleResumeTopActivities(); } - if (!next.mVisibleRequested || next.stopped) { + if (!next.visible || next.stopped) { next.setVisibility(true); } next.completeResumeLocked(); @@ -3360,7 +3358,7 @@ class ActivityStack extends TaskStack { final ActivityRecord top = stack.topRunningActivityLocked(); - if (stack.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) { + if (stack.isActivityTypeHome() && (top == null || !top.visible)) { // If we will be focusing on the home stack next and its current top activity isn't // visible, then use the move the home stack task to top to make the activity visible. stack.getDisplay().moveHomeActivityToTop(reason); @@ -3857,7 +3855,7 @@ class ActivityStack extends TaskStack { "Record #" + targetIndex + " " + r + ": app=" + r.app); if (r.app == app) { - if (r.mVisibleRequested) { + if (r.visible) { hasVisibleActivities = true; } final boolean remove; @@ -3873,8 +3871,8 @@ class ActivityStack extends TaskStack { // Don't currently have state for the activity, or // it is finishing -- always remove it. remove = true; - } else if (!r.mVisibleRequested && r.launchCount > 2 - && r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) { + } else if (!r.visible && r.launchCount > 2 && + r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) { // We have launched this activity too many times since it was // able to run, so give up and remove it. // (Note if the activity is visible, we don't remove the record. @@ -3910,7 +3908,7 @@ class ActivityStack extends TaskStack { // it died, we leave the dead window on screen so it's basically visible. // This is needed when user later tap on the dead window, we need to stop // other apps when user transfers focus to the restarted activity. - r.nowVisible = r.mVisibleRequested; + r.nowVisible = r.visible; } r.cleanUp(true /* cleanServices */, true /* setState */); if (remove) { @@ -4092,7 +4090,7 @@ class ActivityStack extends TaskStack { * Ensures all visible activities at or below the input activity have the right configuration. */ void ensureVisibleActivitiesConfigurationLocked(ActivityRecord start, boolean preserveWindow) { - if (start == null || !start.mVisibleRequested) { + if (start == null || !start.visible) { return; } @@ -4487,7 +4485,7 @@ class ActivityStack extends TaskStack { final ActivityRecord a = task.getChildAt(activityNdx); if (a.info.packageName.equals(packageName)) { a.forceNewConfig = true; - if (starting != null && a == starting && a.mVisibleRequested) { + if (starting != null && a == starting && a.visible) { a.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT); } } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 4828a8d864e9..572bf8381ffc 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -773,11 +773,12 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } if (r.getActivityStack().checkKeyguardVisibility(r, true /* shouldBeVisible */, - true /* isTop */) && r.allowMoveToFront()) { - // We only set the visibility to true if the activity is not being launched in - // background, and is allowed to be visible based on keyguard state. This avoids - // setting this into motion in window manager that is later cancelled due to later - // calls to ensure visible activities that set visibility back to false. + true /* isTop */)) { + // We only set the visibility to true if the activity is allowed to be visible + // based on + // keyguard state. This avoids setting this into motion in window manager that is + // later cancelled due to later calls to ensure visible activities that set + // visibility back to false. r.setVisibility(true); } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index ff1b42377f5f..bef6af350269 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -688,16 +688,15 @@ public class AppTransitionController { * compare z-order. * * @param apps The list of apps to search. - * @param ignoreInvisible If set to true, ignores apps that are not - * {@link ActivityRecord#isVisible}. + * @param ignoreHidden If set to true, ignores apps that are {@link ActivityRecord#isHidden}. * @return The top {@link ActivityRecord}. */ - private ActivityRecord getTopApp(ArraySet<ActivityRecord> apps, boolean ignoreInvisible) { + private ActivityRecord getTopApp(ArraySet<ActivityRecord> apps, boolean ignoreHidden) { int topPrefixOrderIndex = Integer.MIN_VALUE; ActivityRecord topApp = null; for (int i = apps.size() - 1; i >= 0; i--) { final ActivityRecord app = apps.valueAt(i); - if (ignoreInvisible && !app.isVisible()) { + if (ignoreHidden && app.isHidden()) { continue; } final int prefixOrderIndex = app.getPrefixOrderIndex(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 6eb9dba254d8..cb868e1a0bd9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -652,12 +652,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo + " config reported=" + w.isLastConfigReportedToClient()); final ActivityRecord activity = w.mActivityRecord; if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility - + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible() - + " visibleRequested=" + (activity != null && activity.mVisibleRequested) + + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden() + + " hiddenRequested=" + (activity != null && activity.hiddenRequested) + " parentHidden=" + w.isParentWindowHidden()); else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility - + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible() - + " visibleRequested=" + (activity != null && activity.mVisibleRequested) + + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden() + + " hiddenRequested=" + (activity != null && activity.hiddenRequested) + " parentHidden=" + w.isParentWindowHidden()); } @@ -3061,7 +3061,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo wsa.destroySurface(); mWmService.mForceRemoves.add(w); mTmpWindow = w; - } else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) { + } else if (w.mActivityRecord != null && w.mActivityRecord.isClientHidden()) { Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): " + w + " surface=" + wsa.mSurfaceController + " token=" + w.mActivityRecord); diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index fc74d00e82db..caf95de75357 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -107,7 +107,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mTargetActivityType); ActivityRecord targetActivity = getTargetActivity(targetStack); if (targetActivity != null) { - if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) { + if (targetActivity.visible || targetActivity.isTopRunningActivity()) { // The activity is ready. return; } @@ -189,7 +189,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Send launch hint if we are actually launching the target. If it's already visible // (shouldn't happen in general) we don't need to send it. - if (targetActivity == null || !targetActivity.mVisibleRequested) { + if (targetActivity == null || !targetActivity.visible) { mService.mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded( true /* forceSend */, targetActivity); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 520c26e65fee..a4c7bcd36953 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -60,7 +60,9 @@ import static com.android.server.EventLogTags.WM_TASK_CREATED; import static com.android.server.EventLogTags.WM_TASK_REMOVED; import static com.android.server.am.TaskRecordProto.ACTIVITIES; import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE; +import static com.android.server.am.TaskRecordProto.BOUNDS; import static com.android.server.am.TaskRecordProto.FULLSCREEN; +import static com.android.server.am.TaskRecordProto.ID; import static com.android.server.am.TaskRecordProto.LAST_NON_FULLSCREEN_BOUNDS; import static com.android.server.am.TaskRecordProto.MIN_HEIGHT; import static com.android.server.am.TaskRecordProto.MIN_WIDTH; @@ -87,8 +89,10 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.TaskProto.APP_WINDOW_TOKENS; +import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.DISPLAYED_BOUNDS; import static com.android.server.wm.TaskProto.FILLS_PARENT; +import static com.android.server.wm.TaskProto.ID; import static com.android.server.wm.TaskProto.SURFACE_HEIGHT; import static com.android.server.wm.TaskProto.SURFACE_WIDTH; import static com.android.server.wm.TaskProto.WINDOW_CONTAINER; @@ -2198,7 +2202,7 @@ class Task extends WindowContainer<ActivityRecord> implements ConfigurationConta void addStartingWindowsForVisibleActivities(boolean taskSwitch) { for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = getChildAt(activityNdx); - if (r.mVisibleRequested) { + if (r.visible) { r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch); } } @@ -2520,7 +2524,7 @@ class Task extends WindowContainer<ActivityRecord> implements ConfigurationConta for (int i = mChildren.size() - 1; i >= 0; i--) { final ActivityRecord token = mChildren.get(i); // skip hidden (or about to hide) apps - if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) { + if (token.mIsExiting || token.isClientHidden() || token.hiddenRequested) { continue; } final WindowState win = token.findMainWindow(); @@ -2738,9 +2742,10 @@ class Task extends WindowContainer<ActivityRecord> implements ConfigurationConta ActivityRecord getTopVisibleActivity() { for (int i = mChildren.size() - 1; i >= 0; i--) { - final ActivityRecord activity = mChildren.get(i); - if (!activity.mIsExiting && activity.isClientVisible() && activity.mVisibleRequested) { - return activity; + final ActivityRecord token = mChildren.get(i); + // skip hidden (or about to hide) apps + if (!token.mIsExiting && !token.isClientHidden() && !token.hiddenRequested) { + return token; } } return null; diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 0d4ec652f3bb..35f61a88522b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; + import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -248,6 +250,12 @@ class TaskSnapshotController { @Nullable SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task, float scaleFraction) { + return createTaskSnapshot(task, scaleFraction, PixelFormat.RGBA_8888); + } + + @Nullable + SurfaceControl.ScreenshotGraphicBuffer createTaskSnapshot(@NonNull Task task, + float scaleFraction, int pixelFormat) { if (task.getSurfaceControl() == null) { if (DEBUG_SCREENSHOT) { Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task); @@ -258,7 +266,7 @@ class TaskSnapshotController { mTmpRect.offsetTo(0, 0); final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = SurfaceControl.captureLayers( - task.getSurfaceControl(), mTmpRect, scaleFraction); + task.getSurfaceControl(), mTmpRect, scaleFraction, pixelFormat); final GraphicBuffer buffer = screenshotBuffer != null ? screenshotBuffer.getGraphicBuffer() : null; if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { @@ -299,8 +307,14 @@ class TaskSnapshotController { Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task); return null; } + final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; + final boolean isShowWallpaper = (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) != 0; + final int pixelFormat = mPersister.use16BitFormat() && activity.fillsParent() + && !(isWindowTranslucent && isShowWallpaper) + ? PixelFormat.RGB_565 + : PixelFormat.RGBA_8888; final SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer = - createTaskSnapshot(task, scaleFraction); + createTaskSnapshot(task, scaleFraction, pixelFormat); if (screenshotBuffer == null) { if (DEBUG_SCREENSHOT) { @@ -308,7 +322,8 @@ class TaskSnapshotController { } return null; } - final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE; + final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat) + && (!activity.fillsParent() || isWindowTranslucent); return new TaskSnapshot( System.currentTimeMillis() /* id */, activity.mActivityComponent, screenshotBuffer.getGraphicBuffer(), @@ -316,7 +331,7 @@ class TaskSnapshotController { activity.getTask().getConfiguration().orientation, getInsets(mainWindow), isLowRamDevice /* reduced */, scaleFraction /* scale */, true /* isRealSnapshot */, task.getWindowingMode(), getSystemUiVisibility(task), - !activity.fillsParent() || isWindowTranslucent); + isTranslucent); } private boolean shouldDisableSnapshots() { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java index 696e1c3a2602..22c1ea59d176 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java @@ -75,25 +75,35 @@ class TaskSnapshotLoader { final byte[] bytes = Files.readAllBytes(protoFile.toPath()); final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes); final Options options = new Options(); - options.inPreferredConfig = Config.HARDWARE; + options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent + ? Config.RGB_565 + : Config.ARGB_8888; final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options); if (bitmap == null) { Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath()); return null; } - final GraphicBuffer buffer = bitmap.createGraphicBufferHandle(); + + final Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false); + bitmap.recycle(); + if (hwBitmap == null) { + Slog.w(TAG, "Failed to create hardware bitmap: " + bitmapFile.getPath()); + return null; + } + final GraphicBuffer buffer = hwBitmap.createGraphicBufferHandle(); if (buffer == null) { Slog.w(TAG, "Failed to retrieve gralloc buffer for bitmap: " + bitmapFile.getPath()); return null; } + final ComponentName topActivityComponent = ComponentName.unflattenFromString( proto.topActivityComponent); // For legacy snapshots, restore the scale based on the reduced resolution state final float legacyScale = reducedResolution ? mPersister.getReducedScale() : 1f; final float scale = Float.compare(proto.scale, 0f) != 0 ? proto.scale : legacyScale; - return new TaskSnapshot(proto.id, topActivityComponent, buffer, bitmap.getColorSpace(), - proto.orientation, + return new TaskSnapshot(proto.id, topActivityComponent, buffer, + hwBitmap.getColorSpace(), proto.orientation, new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom), reducedResolution, scale, proto.isRealSnapshot, proto.windowingMode, proto.systemUiVisibility, proto.isTranslucent); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java index a156f5c240a8..59155907823b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java @@ -74,6 +74,7 @@ class TaskSnapshotPersister { private final Object mLock = new Object(); private final DirectoryResolver mDirectoryResolver; private final float mReducedScale; + private final boolean mUse16BitFormat; /** * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was @@ -92,6 +93,8 @@ class TaskSnapshotPersister { mReducedScale = ActivityManager.isLowRamDeviceStatic() ? LOW_RAM_REDUCED_SCALE : REDUCED_SCALE; } + mUse16BitFormat = service.mContext.getResources().getBoolean( + com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat); } /** @@ -164,6 +167,15 @@ class TaskSnapshotPersister { return mReducedScale; } + /** + * Return if task snapshots are stored in 16 bit pixel format. + * + * @return true if task snapshots are stored in 16 bit pixel format. + */ + boolean use16BitFormat() { + return mUse16BitFormat; + } + @TestApi void waitForQueueEmpty() { while (true) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 6ff4b2e504f1..3632284fdeb6 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -120,7 +120,7 @@ class WallpaperController { } mFindResults.resetTopWallpaper = true; - if (w.mActivityRecord != null && !w.mActivityRecord.isVisible() + if (w.mActivityRecord != null && w.mActivityRecord.isHidden() && !w.mActivityRecord.isAnimating(TRANSITION)) { // If this window's app token is hidden and not animating, it is of no interest to us. @@ -278,11 +278,9 @@ class WallpaperController { for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) { final WallpaperWindowToken token = mWallpaperTokens.get(i); token.hideWallpaperToken(wasDeferred, "hideWallpapers"); - if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) { - Slog.d(TAG, "Hiding wallpaper " + token - + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" - + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " ")); - } + if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token + + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev=" + + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " ")); } } @@ -534,9 +532,9 @@ class WallpaperController { } final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null - && !wallpaperTarget.mActivityRecord.mVisibleRequested; + && wallpaperTarget.mActivityRecord.hiddenRequested; final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null - && !prevWallpaperTarget.mActivityRecord.mVisibleRequested; + && prevWallpaperTarget.mActivityRecord.hiddenRequested; if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: " + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index d23bf978cbab..528cece9a78b 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -56,6 +56,7 @@ class WallpaperWindowToken extends WindowToken { final WindowState wallpaper = mChildren.get(j); wallpaper.hideWallpaperWindow(wasDeferred, reason); } + setHidden(true); } void sendWindowWallpaperCommand( @@ -87,7 +88,9 @@ class WallpaperWindowToken extends WindowToken { final int dw = displayInfo.logicalWidth; final int dh = displayInfo.logicalHeight; - if (isVisible() != visible) { + if (isHidden() == visible) { + setHidden(!visible); + // Need to do a layout to ensure the wallpaper now has the correct size. mDisplayContent.setLayoutNeeded(); } @@ -115,9 +118,10 @@ class WallpaperWindowToken extends WindowToken { void updateWallpaperWindows(boolean visible) { - if (isVisible() != visible) { + if (isHidden() == visible) { if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, - "Wallpaper token " + token + " visible=" + visible); + "Wallpaper token " + token + " hidden=" + !visible); + setHidden(!visible); // Need to do a layout to ensure the wallpaper now has the correct size. mDisplayContent.setLayoutNeeded(); } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index cf3e2bbe1569..6fed2cbf03be 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -490,6 +490,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } mSurfaceControl = null; + mLastSurfacePosition.set(0, 0); scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6e4f1ee29a64..88c0fad6a6c9 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1702,7 +1702,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; } - if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) { + if (win.mActivityRecord == null || !win.mActivityRecord.isClientHidden()) { res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; } @@ -2235,7 +2235,7 @@ public class WindowManagerService extends IWindowManager.Stub // associated appToken is not hidden. final boolean shouldRelayout = viewVisibility == View.VISIBLE && (win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING - || win.mActivityRecord.isClientVisible()); + || !win.mActivityRecord.isClientHidden()); // If we are not currently running the exit animation, we need to see about starting // one. diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 2e188b7cc86d..d63fbc217e54 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -533,7 +533,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio synchronized (mAtm.mGlobalLockWithoutBoost) { for (int i = mActivities.size() - 1; i >= 0; --i) { final ActivityRecord r = mActivities.get(i); - if (r.mVisibleRequested) { + if (r.visible) { return true; } } @@ -555,7 +555,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio continue; } ActivityRecord topActivity = task.getTopActivity(); - if (topActivity != null && topActivity.mVisibleRequested) { + if (topActivity != null && topActivity.visible) { return true; } } @@ -589,7 +589,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // - no longer visible OR // - not focusable (in PiP mode for instance) if (topDisplay == null - || !mPreQTopResumedActivity.mVisibleRequested + || !mPreQTopResumedActivity.visible || !mPreQTopResumedActivity.isFocusable()) { canUpdate = true; } @@ -739,7 +739,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } // Don't consider any activities that are currently not in a state where they // can be destroyed. - if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() + if (r.visible || !r.stopped || !r.hasSavedState() || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) { if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r); continue; @@ -793,7 +793,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio continue; } } - if (r.mVisibleRequested) { + if (r.visible) { final Task task = r.getTask(); if (task != null && minTaskLayer > 0) { final int layer = task.mLayerRank; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b8168319dd29..62a3512c3cae 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1539,7 +1539,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this? boolean isWinVisibleLw() { - return (mActivityRecord == null || mActivityRecord.mVisibleRequested + return (mActivityRecord == null || !mActivityRecord.hiddenRequested || mActivityRecord.isAnimating(TRANSITION)) && isVisible(); } @@ -1548,7 +1548,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * not the pending requested hidden state. */ boolean isVisibleNow() { - return (mToken.isVisible() || mAttrs.type == TYPE_APPLICATION_STARTING) + return (!mToken.isHidden() || mAttrs.type == TYPE_APPLICATION_STARTING) && isVisible(); } @@ -1570,7 +1570,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final ActivityRecord atoken = mActivityRecord; return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && isVisibleByPolicy() && !isParentWindowHidden() - && (atoken == null || atoken.mVisibleRequested) + && (atoken == null || !atoken.hiddenRequested) && !mAnimatingExit && !mDestroying; } @@ -1585,7 +1585,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final ActivityRecord atoken = mActivityRecord; if (atoken != null) { - return ((!isParentWindowHidden() && atoken.mVisibleRequested) + return ((!isParentWindowHidden() && !atoken.hiddenRequested) || isAnimating(TRANSITION | PARENTS)); } return !isParentWindowHidden() || isAnimating(TRANSITION | PARENTS); @@ -1621,7 +1621,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } final boolean parentAndClientVisible = !isParentWindowHidden() - && mViewVisibility == View.VISIBLE && mToken.isVisible(); + && mViewVisibility == View.VISIBLE && !mToken.isHidden(); return mHasSurface && isVisibleByPolicy() && !mDestroying && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS)); } @@ -1640,7 +1640,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { final Task task = getTask(); final boolean canFromTask = task != null && task.canAffectSystemUiFlags(); - return canFromTask && mActivityRecord.isVisible(); + return canFromTask && !mActivityRecord.isHidden(); } } @@ -1652,7 +1652,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public boolean isDisplayedLw() { final ActivityRecord atoken = mActivityRecord; return isDrawnLw() && isVisibleByPolicy() - && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested)) + && ((!isParentWindowHidden() && (atoken == null || !atoken.hiddenRequested)) || isAnimating(TRANSITION | PARENTS)); } @@ -1669,8 +1669,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final ActivityRecord atoken = mActivityRecord; return mViewVisibility == View.GONE || !mRelayoutCalled - || (atoken == null && !mToken.isVisible()) - || (atoken != null && !atoken.mVisibleRequested) + || (atoken == null && mToken.isHidden()) + || (atoken != null && atoken.hiddenRequested) || isParentWindowGoneForLayout() || (mAnimatingExit && !isAnimatingLw()) || mDestroying; @@ -2162,8 +2162,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " parentHidden=" + isParentWindowHidden() + " exiting=" + mAnimatingExit + " destroying=" + mDestroying); if (mActivityRecord != null) { - Slog.i(TAG_WM, " mActivityRecord.visibleRequested=" - + mActivityRecord.mVisibleRequested); + Slog.i(TAG_WM, " mActivityRecord.hiddenRequested=" + mActivityRecord.hiddenRequested); } } } @@ -2574,7 +2573,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * interacts with it. */ private boolean shouldKeepVisibleDeadAppWindow() { - if (!isWinVisibleLw() || mActivityRecord == null || !mActivityRecord.isClientVisible()) { + if (!isWinVisibleLw() || mActivityRecord == null || mActivityRecord.isClientHidden()) { // Not a visible app window or the app isn't dead. return false; } @@ -2611,14 +2610,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return showBecauseOfActivity || showBecauseOfWindow; } - /** @return {@code false} if this window desires touch events. */ + /** @return false if this window desires touch events. */ boolean cantReceiveTouchInput() { if (mActivityRecord == null || mActivityRecord.getTask() == null) { return false; } return mActivityRecord.getTask().getTaskStack().shouldIgnoreInput() - || !mActivityRecord.mVisibleRequested + || mActivityRecord.hiddenRequested || isAnimatingToRecents(); } @@ -2886,13 +2885,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void sendAppVisibilityToClients() { super.sendAppVisibilityToClients(); - final boolean clientVisible = mActivityRecord.isClientVisible(); - if (mAttrs.type == TYPE_APPLICATION_STARTING && !clientVisible) { + final boolean clientHidden = mActivityRecord.isClientHidden(); + if (mAttrs.type == TYPE_APPLICATION_STARTING && clientHidden) { // Don't hide the starting window. return; } - if (!clientVisible) { + if (clientHidden) { // Once we are notifying the client that it's visibility has changed, we need to prevent // it from destroying child surfaces until the animation has finished. We do this by // detaching any surface control the client added from the client. @@ -2906,8 +2905,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { if (DEBUG_VISIBILITY) Slog.v(TAG, - "Setting visibility of " + this + ": " + clientVisible); - mClient.dispatchAppVisibility(clientVisible); + "Setting visibility of " + this + ": " + (!clientHidden)); + mClient.dispatchAppVisibility(!clientHidden); } catch (RemoteException e) { } } @@ -4147,9 +4146,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING) + " during animation: policyVis=" + isVisibleByPolicy() + " parentHidden=" + isParentWindowHidden() - + " tok.visibleRequested=" - + (mActivityRecord != null && mActivityRecord.mVisibleRequested) - + " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible()) + + " tok.hiddenRequested=" + + (mActivityRecord != null && mActivityRecord.hiddenRequested) + + " tok.hidden=" + (mActivityRecord != null && mActivityRecord.isHidden()) + " animating=" + isAnimating(TRANSITION | PARENTS) + " tok animating=" + (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION)) @@ -4556,7 +4555,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " pv=" + isVisibleByPolicy() + " mDrawState=" + mWinAnimator.mDrawState + " ph=" + isParentWindowHidden() - + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested) + + " th=" + (mActivityRecord != null ? mActivityRecord.hiddenRequested : false) + " a=" + isAnimating(TRANSITION | PARENTS)); } } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 6480a15a4220..88a1458a783f 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -29,6 +29,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowTokenProto.HASH_CODE; +import static com.android.server.wm.WindowTokenProto.HIDDEN; import static com.android.server.wm.WindowTokenProto.PAUSED; import static com.android.server.wm.WindowTokenProto.WAITING_TO_SHOW; import static com.android.server.wm.WindowTokenProto.WINDOWS; @@ -71,6 +72,9 @@ class WindowToken extends WindowContainer<WindowState> { // Is key dispatching paused for this token? boolean paused = false; + // Should this token's windows be hidden? + private boolean mHidden; + // Temporary for finding which tokens no longer have visible windows. boolean hasVisible; @@ -124,6 +128,16 @@ class WindowToken extends WindowContainer<WindowState> { } } + void setHidden(boolean hidden) { + if (hidden != mHidden) { + mHidden = hidden; + } + } + + boolean isHidden() { + return mHidden; + } + void removeAllWindowsIfPossible() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowState win = mChildren.get(i); @@ -142,7 +156,7 @@ class WindowToken extends WindowContainer<WindowState> { // This token is exiting, so allow it to be removed when it no longer contains any windows. mPersistOnEmpty = false; - if (!isVisible()) { + if (mHidden) { return; } @@ -155,10 +169,7 @@ class WindowToken extends WindowContainer<WindowState> { changed |= win.onSetAppExiting(); } - final ActivityRecord app = asActivityRecord(); - if (app != null) { - app.setVisible(false); - } + setHidden(true); if (changed) { mWmService.mWindowPlacerLocked.performSurfacePlacement(); @@ -275,6 +286,7 @@ class WindowToken extends WindowContainer<WindowState> { final WindowState w = mChildren.get(i); w.writeToProto(proto, WINDOWS, logLevel); } + proto.write(HIDDEN, mHidden); proto.write(WAITING_TO_SHOW, waitingToShow); proto.write(PAUSED, paused); proto.end(token); @@ -284,7 +296,8 @@ class WindowToken extends WindowContainer<WindowState> { super.dump(pw, prefix, dumpAll); pw.print(prefix); pw.print("windows="); pw.println(mChildren); pw.print(prefix); pw.print("windowType="); pw.print(windowType); - pw.print(" hasVisible="); pw.println(hasVisible); + pw.print(" hidden="); pw.print(mHidden); + pw.print(" hasVisible="); pw.println(hasVisible); if (waitingToShow || sendingToBottom) { pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow); pw.print(" sendingToBottom="); pw.print(sendingToBottom); diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 91c05a858ce4..bffa44e868a7 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -4,6 +4,7 @@ java_library_static { libs: [ "services.core", + "app-compat-annotations", ], plugins: [ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 597d337c2450..99dd9a12eb72 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -44,6 +44,7 @@ import android.os.UserHandle; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import org.junit.After; @@ -80,6 +81,7 @@ public class AccessibilityServiceConnectionTest { @Mock ResolveInfo mMockResolveInfo; @Mock AccessibilitySecurityPolicy mMockSecurityPolicy; @Mock AccessibilityWindowManager mMockA11yWindowManager; + @Mock ActivityTaskManagerInternal mMockActivityTaskManagerInternal; @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; @Mock WindowManagerInternal mMockWindowManagerInternal; @Mock SystemActionPerformer mMockSystemActionPerformer; @@ -111,7 +113,8 @@ public class AccessibilityServiceConnectionTest { mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext, COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal, - mMockSystemActionPerformer, mMockA11yWindowManager); + mMockSystemActionPerformer, mMockA11yWindowManager, + mMockActivityTaskManagerInternal); when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index bcff2f81f805..608625f9fd10 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -25,7 +26,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.app.NotificationHistory.HistoricalNotification; +import android.content.Context; import android.graphics.drawable.Icon; import android.os.Handler; import android.util.AtomicFile; @@ -42,8 +45,17 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @RunWith(AndroidJUnit4.class) public class NotificationHistoryDatabaseTest extends UiServiceTestCase { @@ -51,6 +63,11 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { File mRootDir; @Mock Handler mFileWriteHandler; + @Mock + Context mContext; + @Mock + AlarmManager mAlarmManager; + TestFileAttrProvider mFileAttrProvider; NotificationHistoryDatabase mDataBase; @@ -85,36 +102,56 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); + when(mContext.getUser()).thenReturn(getContext().getUser()); + when(mContext.getPackageName()).thenReturn(getContext().getPackageName()); + mFileAttrProvider = new TestFileAttrProvider(); mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest"); - mDataBase = new NotificationHistoryDatabase(mRootDir); + mDataBase = new NotificationHistoryDatabase(mContext, mRootDir, mFileAttrProvider); mDataBase.init(mFileWriteHandler); } @Test - public void testPrune() { + public void testDeletionReceiver() { + verify(mContext, times(1)).registerReceiver(any(), any()); + } + + @Test + public void testPrune() throws Exception { + GregorianCalendar cal = new GregorianCalendar(); + cal.setTimeInMillis(10); int retainDays = 1; - for (long i = 10; i >= 5; i--) { + + List<AtomicFile> expectedFiles = new ArrayList<>(); + + // add 5 files with a creation date of "today" + for (long i = cal.getTimeInMillis(); i >= 5; i--) { File file = mock(File.class); - when(file.lastModified()).thenReturn(i); + mFileAttrProvider.creationDates.put(file, i); AtomicFile af = new AtomicFile(file); + expectedFiles.add(af); mDataBase.mHistoryFiles.addLast(af); } - GregorianCalendar cal = new GregorianCalendar(); - cal.setTimeInMillis(5); + cal.add(Calendar.DATE, -1 * retainDays); + // Add 5 more files more than retainDays old for (int i = 5; i >= 0; i--) { File file = mock(File.class); - when(file.lastModified()).thenReturn(cal.getTimeInMillis() - i); + mFileAttrProvider.creationDates.put(file, cal.getTimeInMillis() - i); AtomicFile af = new AtomicFile(file); mDataBase.mHistoryFiles.addLast(af); } - mDataBase.prune(retainDays, 10); - for (AtomicFile file : mDataBase.mHistoryFiles) { - assertThat(file.getBaseFile().lastModified() > 0); - } + // back to today; trim everything a day + old + cal.add(Calendar.DATE, 1 * retainDays); + mDataBase.prune(retainDays, cal.getTimeInMillis()); + + assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles); + + verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); + } @Test @@ -181,4 +218,12 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { verify(af2, never()).openRead(); } + private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { + public Map<File, Long> creationDates = new HashMap<>(); + + @Override + public long getCreationTime(File file) { + return creationDates.get(file); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 776c00e75846..8961796ed617 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2417,6 +2417,24 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testLockChannelsForOEM_channelSpecific_clearData() { + NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + mHelper.getImportance(PKG_O, UID_O); + mHelper.lockChannelsForOEM(new String[] {PKG_O + ":" + a.getId()}); + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByOEM()); + + mHelper.clearData(PKG_O, UID_O); + + // it's back! + mHelper.createNotificationChannel(PKG_O, UID_O, a, true, false); + // and still locked + assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false) + .isImportanceLockedByOEM()); + } + + @Test public void testLockChannelsForOEM_channelDoesNotExistYet_appWide() { NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH); NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_LOW); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index e560cb9a6cf7..9df7b4576427 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -346,7 +346,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); activity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; activity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; - activity.mVisibleRequested = true; + activity.visible = true; activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */); final ArrayList<CompletableFuture<IBinder>> resultWrapper = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java index 734761fd8048..3c619f73aa6f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -160,7 +160,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { public void testOnActivityLaunchCancelled_hasDrawn() { onActivityLaunched(); - mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true; + mTopActivity.visible = mTopActivity.mDrawn = true; // Cannot time already-visible activities. mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); @@ -171,7 +171,7 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { @Test public void testOnActivityLaunchCancelled_finishedBeforeDrawn() { - mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true; + mTopActivity.visible = mTopActivity.mDrawn = true; // Suppress resume when creating the record because we want to notify logger manually. mSupervisor.beginDeferResume(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index e22c419f5919..c51a46a76f4c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -221,7 +221,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testRestartProcessIfVisible() { doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity); - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.setSavedState(null /* savedState */); mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart"); prepareFixedAspectRatioUnresizableActivity(); @@ -502,7 +502,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mTask.setBounds(100, 100, 400, 600); mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - mActivity.mVisibleRequested = true; + mActivity.visible = true; ensureActivityConfiguration(); final Rect bounds = new Rect(mActivity.getBounds()); @@ -547,7 +547,7 @@ public class ActivityRecordTests extends ActivityTestsBase { .when(mActivity).getRequestedOrientation(); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1; - mActivity.mVisibleRequested = true; + mActivity.visible = true; ensureActivityConfiguration(); // The parent configuration doesn't change since the first resolved configuration, so the // activity shouldn't be in the size compatibility mode. @@ -589,7 +589,7 @@ public class ActivityRecordTests extends ActivityTestsBase { .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setMaxAspectRatio(1.5f) .build(); - mActivity.mVisibleRequested = true; + mActivity.visible = true; final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; @@ -614,7 +614,7 @@ public class ActivityRecordTests extends ActivityTestsBase { mTask.getRequestedOverrideConfiguration().orientation = Configuration.ORIENTATION_PORTRAIT; mActivity.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - mActivity.mVisibleRequested = true; + mActivity.visible = true; ensureActivityConfiguration(); final Rect originalBounds = new Rect(mActivity.getBounds()); @@ -661,7 +661,7 @@ public class ActivityRecordTests extends ActivityTestsBase { prepareFixedAspectRatioUnresizableActivity(); mActivity.setState(STOPPED, "testSizeCompatMode"); - mActivity.mVisibleRequested = false; + mActivity.visible = false; mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); // Make the parent bounds to be different so the activity is in size compatibility mode. setupDisplayAndParentSize(600, 1200); @@ -829,7 +829,7 @@ public class ActivityRecordTests extends ActivityTestsBase { // Prepare the activity record to be ready for immediate removal. It should be invisible and // have no process. Otherwise, request to finish it will send a message to client first. mActivity.setState(STOPPED, "test"); - mActivity.mVisibleRequested = false; + mActivity.visible = false; mActivity.nowVisible = false; // Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() - // this will cause NPE when updating task's process. @@ -838,7 +838,7 @@ public class ActivityRecordTests extends ActivityTestsBase { // Put a visible activity on top, so the finishing activity doesn't have to wait until the // next activity reports idle to destroy it. final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = true; + topActivity.visible = true; topActivity.nowVisible = true; topActivity.setState(RESUMED, "test"); @@ -924,7 +924,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testFinishActivityIfPossible_visibleResumedPreparesAppTransition() { mActivity.finishing = false; - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.setState(RESUMED, "test"); mActivity.finishIfPossible("test", false /* oomAdj */); @@ -940,7 +940,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testFinishActivityIfPossible_visibleNotResumedExecutesAppTransition() { mActivity.finishing = false; - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.setState(PAUSED, "test"); mActivity.finishIfPossible("test", false /* oomAdj */); @@ -958,7 +958,7 @@ public class ActivityRecordTests extends ActivityTestsBase { // Put an activity on top of test activity to make it invisible and prevent us from // accidentally resuming the topmost one again. new ActivityBuilder(mService).build(); - mActivity.mVisibleRequested = false; + mActivity.visible = false; mActivity.setState(STOPPED, "test"); mActivity.finishIfPossible("test", false /* oomAdj */); @@ -1010,7 +1010,7 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_keepStateOfNextInvisible() { final ActivityRecord currentTop = mActivity; - currentTop.mVisibleRequested = currentTop.nowVisible = true; + currentTop.visible = currentTop.nowVisible = true; // Simulates that {@code currentTop} starts an existing activity from background (so its // state is stopped) and the starting flow just goes to place it at top. @@ -1036,13 +1036,13 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_waitForNextVisible() { final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = true; + topActivity.visible = true; topActivity.nowVisible = true; topActivity.finishing = true; topActivity.setState(PAUSED, "true"); // Mark the bottom activity as not visible, so that we will wait for it before removing // the top one. - mActivity.mVisibleRequested = false; + mActivity.visible = false; mActivity.nowVisible = false; mActivity.setState(STOPPED, "test"); @@ -1061,13 +1061,13 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_noWaitForNextVisible_alreadyInvisible() { final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = false; + topActivity.visible = false; topActivity.nowVisible = false; topActivity.finishing = true; topActivity.setState(PAUSED, "true"); // Mark the bottom activity as not visible, so that we would wait for it before removing // the top one. - mActivity.mVisibleRequested = false; + mActivity.visible = false; mActivity.nowVisible = false; mActivity.setState(STOPPED, "test"); @@ -1083,12 +1083,12 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_waitForIdle() { final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = true; + topActivity.visible = true; topActivity.nowVisible = true; topActivity.finishing = true; topActivity.setState(PAUSED, "true"); // Mark the bottom activity as already visible, so that there is no need to wait for it. - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.nowVisible = true; mActivity.setState(RESUMED, "test"); @@ -1104,12 +1104,12 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_noWaitForNextVisible_stopped() { final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = false; + topActivity.visible = false; topActivity.nowVisible = false; topActivity.finishing = true; topActivity.setState(STOPPED, "true"); // Mark the bottom activity as already visible, so that there is no need to wait for it. - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.nowVisible = true; mActivity.setState(RESUMED, "test"); @@ -1125,12 +1125,12 @@ public class ActivityRecordTests extends ActivityTestsBase { @Test public void testCompleteFinishing_noWaitForNextVisible_nonFocusedStack() { final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); - topActivity.mVisibleRequested = true; + topActivity.visible = true; topActivity.nowVisible = true; topActivity.finishing = true; topActivity.setState(PAUSED, "true"); // Mark the bottom activity as already visible, so that there is no need to wait for it. - mActivity.mVisibleRequested = true; + mActivity.visible = true; mActivity.nowVisible = true; mActivity.setState(RESUMED, "test"); @@ -1139,7 +1139,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootActivityContainer).build(); final ActivityRecord focusedActivity = stack.getChildAt(0).getChildAt(0); focusedActivity.nowVisible = true; - focusedActivity.mVisibleRequested = true; + focusedActivity.visible = true; focusedActivity.setState(RESUMED, "test"); stack.mResumedActivity = focusedActivity; @@ -1346,7 +1346,7 @@ public class ActivityRecordTests extends ActivityTestsBase { setupDisplayContentForCompatDisplayInsets(); mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE; mActivity.info.maxAspectRatio = 1.5f; - mActivity.mVisibleRequested = true; + mActivity.visible = true; ensureActivityConfiguration(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index fc44652cc668..d0e07b619ad6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -1006,7 +1006,7 @@ public class ActivityStackTests extends ActivityTestsBase { // There is still an activity1 in stack1 so the activity2 should be added to finishing list // that will be destroyed until idle. - stack2.getTopActivity().mVisibleRequested = true; + stack2.getTopActivity().visible = true; final ActivityRecord activity2 = finishTopActivity(stack2); assertEquals(STOPPING, activity2.getState()); assertThat(mSupervisor.mStoppingActivities).contains(activity2); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java index 163268191df1..47b39b042fb5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java @@ -271,7 +271,7 @@ class ActivityTestsBase extends SystemServiceTestsBase { doReturn(true).when(activity).occludesParent(); mTask.addChild(activity); // Make visible by default... - activity.setVisible(true); + activity.setHidden(false); } final WindowProcessController wpc = new WindowProcessController(mService, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index d415f25baab9..60204539d178 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -62,7 +62,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); translucentOpening.setOccludesParent(false); - translucentOpening.setVisible(false); + translucentOpening.setHidden(true); mDisplayContent.mOpeningApps.add(behind); mDisplayContent.mOpeningApps.add(translucentOpening); assertEquals(WindowManager.TRANSIT_TRANSLUCENT_ACTIVITY_OPEN, @@ -90,7 +90,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { final ActivityRecord translucentOpening = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); translucentOpening.setOccludesParent(false); - translucentOpening.setVisible(false); + translucentOpening.setHidden(true); mDisplayContent.mOpeningApps.add(behind); mDisplayContent.mOpeningApps.add(translucentOpening); assertEquals(TRANSIT_TASK_CHANGE_WINDOWING_MODE, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java index d491569149a5..bd336ad2494f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java @@ -252,7 +252,7 @@ public class AppWindowTokenTests extends WindowTestsBase { @Test @Presubmit public void testGetOrientation() { - mActivity.setVisible(true); + mActivity.setHidden(false); mActivity.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); @@ -261,7 +261,7 @@ public class AppWindowTokenTests extends WindowTestsBase { assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mActivity.getOrientation()); mActivity.setOccludesParent(true); - mActivity.setVisible(false); + mActivity.setHidden(true); mActivity.sendingToBottom = true; // Can not specify orientation if app isn't visible even though it occludes parent. assertEquals(SCREEN_ORIENTATION_UNSET, mActivity.getOrientation()); @@ -314,7 +314,7 @@ public class AppWindowTokenTests extends WindowTestsBase { @Test public void testSetOrientation() { - mActivity.setVisible(true); + mActivity.setHidden(false); // Assert orientation is unspecified to start. assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOrientation()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 8db48584295a..9f4143ff95fa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -394,7 +394,7 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus); // Make sure top focused display not changed if there is a focused app. - window1.mActivityRecord.mVisibleRequested = false; + window1.mActivityRecord.hiddenRequested = true; window1.getDisplayContent().setFocusedApp(window1.mActivityRecord); updateFocusedWindow(); assertTrue(!window1.isFocused()); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 1abd3662165e..702600402d8e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -149,7 +149,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); final ActivityRecord hiddenActivity = createActivityRecord(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - hiddenActivity.setVisible(false); + hiddenActivity.setHidden(true); mDisplayContent.getConfiguration().windowConfiguration.setRotation( mDisplayContent.getRotation()); mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray(), homeActivity); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 06d96fee3757..41cbd8137f5e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -106,12 +106,12 @@ public class RecentsAnimationTest extends ActivityTestsBase { RecentsAnimationCallbacks recentsAnimation = startRecentsActivity( mRecentsComponent, true /* getRecentsAnimation */); // The launch-behind state should make the recents activity visible. - assertTrue(recentActivity.mVisibleRequested); + assertTrue(recentActivity.visible); // Simulate the animation is cancelled without changing the stack order. recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */); // The non-top recents activity should be invisible by the restored launch-behind state. - assertFalse(recentActivity.mVisibleRequested); + assertFalse(recentActivity.visible); } @Test @@ -158,7 +158,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { // The activity is started in background so it should be invisible and will be stopped. assertThat(recentsActivity).isNotNull(); assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity); - assertFalse(recentsActivity.mVisibleRequested); + assertFalse(recentsActivity.visible); // Assume it is stopped to test next use case. recentsActivity.activityStoppedLocked(null /* newIcicle */, null /* newPersistentState */, @@ -361,7 +361,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { true); // Ensure we find the task for the right user and it is made visible - assertTrue(otherUserHomeActivity.mVisibleRequested); + assertTrue(otherUserHomeActivity.visible); } private void startRecentsActivity() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 05457ccd7303..814004522b28 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -221,6 +221,32 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testRemoveImmediatelyClearsLastSurfacePosition() { + reset(mTransaction); + try (MockSurfaceBuildingContainer top = new MockSurfaceBuildingContainer(mWm)) { + final WindowContainer<WindowContainer> child1 = new WindowContainer(mWm); + child1.setBounds(1, 1, 10, 10); + + top.addChild(child1, 0); + assertEquals(1, child1.getLastSurfacePosition().x); + assertEquals(1, child1.getLastSurfacePosition().y); + + WindowContainer child11 = new WindowContainer(mWm); + child1.addChild(child11, 0); + + child1.setBounds(2, 2, 20, 20); + assertEquals(2, child1.getLastSurfacePosition().x); + assertEquals(2, child1.getLastSurfacePosition().y); + + child1.removeImmediately(); + assertEquals(0, child1.getLastSurfacePosition().x); + assertEquals(0, child1.getLastSurfacePosition().y); + assertEquals(0, child11.getLastSurfacePosition().x); + assertEquals(0, child11.getLastSurfacePosition().y); + } + } + + @Test public void testAddChildByIndex() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0b7cbceb8f95..e1f92dddf053 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -383,11 +383,11 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanAffectSystemUiFlags() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mActivityRecord.setVisible(true); + app.mToken.setHidden(false); assertTrue(app.canAffectSystemUiFlags()); - app.mActivityRecord.setVisible(false); + app.mToken.setHidden(true); assertFalse(app.canAffectSystemUiFlags()); - app.mActivityRecord.setVisible(true); + app.mToken.setHidden(false); app.mAttrs.alpha = 0.0f; assertFalse(app.canAffectSystemUiFlags()); } @@ -395,7 +395,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCanAffectSystemUiFlags_disallow() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mActivityRecord.setVisible(true); + app.mToken.setHidden(false); assertTrue(app.canAffectSystemUiFlags()); app.getTask().setCanAffectSystemUiFlags(false); assertFalse(app.canAffectSystemUiFlags()); @@ -569,7 +569,7 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCantReceiveTouchWhenAppTokenHiddenRequested() { final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); - win0.mActivityRecord.mVisibleRequested = false; + win0.mActivityRecord.hiddenRequested = true; assertTrue(win0.cantReceiveTouchInput()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index 26743c842122..797a6bc7e087 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java @@ -74,8 +74,8 @@ class WindowTestUtils { private static void postCreateActivitySetup(ActivityRecord activity, DisplayContent dc) { activity.onDisplayChanged(dc); activity.setOccludesParent(true); - activity.setVisible(true); - activity.mVisibleRequested = true; + activity.setHidden(false); + activity.hiddenRequested = false; } static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 047fcecd5a5b..9967bebf20b8 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -488,7 +488,7 @@ public class UsbHostManager { * Opens the specified USB device */ public ParcelFileDescriptor openDevice(String deviceAddress, - UsbUserPermissionManager permissions, String packageName, int uid) { + UsbUserPermissionManager permissions, String packageName, int pid, int uid) { synchronized (mLock) { if (isBlackListed(deviceAddress)) { throw new SecurityException("USB device is on a restricted bus"); @@ -500,7 +500,7 @@ public class UsbHostManager { "device " + deviceAddress + " does not exist or is restricted"); } - permissions.checkPermission(device, packageName, uid); + permissions.checkPermission(device, packageName, pid, uid); return nativeOpenDevice(deviceAddress); } } diff --git a/services/usb/java/com/android/server/usb/UsbSerialReader.java b/services/usb/java/com/android/server/usb/UsbSerialReader.java index 3151679eb545..86016bb6036f 100644 --- a/services/usb/java/com/android/server/usb/UsbSerialReader.java +++ b/services/usb/java/com/android/server/usb/UsbSerialReader.java @@ -93,7 +93,7 @@ class UsbSerialReader extends IUsbSerialReader.Stub { int userId = UserHandle.getUserId(uid); if (mDevice instanceof UsbDevice) { mPermissionManager.getPermissionsForUser(userId) - .checkPermission((UsbDevice) mDevice, packageName, uid); + .checkPermission((UsbDevice) mDevice, packageName, pid, uid); } else { mPermissionManager.getPermissionsForUser(userId) .checkPermission((UsbAccessory) mDevice, uid); diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 04936377bbfb..275319491e39 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -262,6 +262,7 @@ public class UsbService extends IUsbManager.Stub { if (mHostManager != null) { if (deviceName != null) { int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); int user = UserHandle.getUserId(uid); long ident = clearCallingIdentity(); @@ -269,7 +270,7 @@ public class UsbService extends IUsbManager.Stub { synchronized (mLock) { if (mUserManager.isSameProfileGroup(user, mCurrentUserId)) { fd = mHostManager.openDevice(deviceName, getPermissionsForUser(user), - packageName, uid); + packageName, pid, uid); } else { Slog.w(TAG, "Cannot open " + deviceName + " for user " + user + " as user is not active."); @@ -469,11 +470,12 @@ public class UsbService extends IUsbManager.Stub { @Override public boolean hasDevicePermission(UsbDevice device, String packageName) { final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserId(uid); final long token = Binder.clearCallingIdentity(); try { - return getPermissionsForUser(userId).hasPermission(device, packageName, uid); + return getPermissionsForUser(userId).hasPermission(device, packageName, pid, uid); } finally { Binder.restoreCallingIdentity(token); } @@ -495,11 +497,12 @@ public class UsbService extends IUsbManager.Stub { @Override public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) { final int uid = Binder.getCallingUid(); + final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserId(uid); final long token = Binder.clearCallingIdentity(); try { - getPermissionsForUser(userId).requestPermission(device, packageName, pi, uid); + getPermissionsForUser(userId).requestPermission(device, packageName, pi, pid, uid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java index e700f19adbd4..58f5484657c5 100644 --- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java +++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java @@ -186,12 +186,14 @@ class UsbUserPermissionManager { * Returns true if package with uid has permission to access the device. * * @param device to check permission for + * @param pid to check permission for * @param uid to check permission for * @return {@code true} if package with uid has permission */ - boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int uid) { + boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int pid, + int uid) { if (isCameraDevicePresent(device)) { - if (!isCameraPermissionGranted(packageName, uid)) { + if (!isCameraPermissionGranted(packageName, pid, uid)) { return false; } } @@ -615,10 +617,11 @@ class UsbUserPermissionManager { * Check for camera permission of the calling process. * * @param packageName Package name of the caller. + * @param pid Linux pid of the calling process. * @param uid Linux uid of the calling process. * @return True in case camera permission is available, False otherwise. */ - private boolean isCameraPermissionGranted(String packageName, int uid) { + private boolean isCameraPermissionGranted(String packageName, int pid, int uid) { int targetSdkVersion = android.os.Build.VERSION_CODES.P; try { ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0); @@ -634,7 +637,7 @@ class UsbUserPermissionManager { } if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) { - int allowed = mContext.checkCallingPermission(android.Manifest.permission.CAMERA); + int allowed = mContext.checkPermission(android.Manifest.permission.CAMERA, pid, uid); if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) { Slog.i(TAG, "Camera permission required for USB video class devices"); return false; @@ -644,8 +647,8 @@ class UsbUserPermissionManager { return true; } - public void checkPermission(UsbDevice device, String packageName, int uid) { - if (!hasPermission(device, packageName, uid)) { + public void checkPermission(UsbDevice device, String packageName, int pid, int uid) { + if (!hasPermission(device, packageName, pid, uid)) { throw new SecurityException("User has not given " + uid + "/" + packageName + " permission to access device " + device.getDeviceName()); } @@ -678,11 +681,12 @@ class UsbUserPermissionManager { requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi); } - public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) { + public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int pid, + int uid) { Intent intent = new Intent(); // respond immediately if permission has already been granted - if (hasPermission(device, packageName, uid)) { + if (hasPermission(device, packageName, pid, uid)) { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true); try { @@ -693,7 +697,7 @@ class UsbUserPermissionManager { return; } if (isCameraDevicePresent(device)) { - if (!isCameraPermissionGranted(packageName, uid)) { + if (!isCameraPermissionGranted(packageName, pid, uid)) { intent.putExtra(UsbManager.EXTRA_DEVICE, device); intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); try { diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index dfcfed715523..1770671b2380 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3182,13 +3182,14 @@ public class SubscriptionManager { } /** - * Get active data subscription id. - * See {@link PhoneStateListener#onActiveDataSubscriptionIdChanged(int)} for the details. + * Get active data subscription id. Active data subscription refers to the subscription + * currently chosen to provide cellular internet connection to the user. This may be + * different from getDefaultDataSubscriptionId(). Eg. Opportunistics data * - * @return Active data subscription id + * See {@link PhoneStateListener#onActiveDataSubscriptionIdChanged(int)} for the details. * - * //TODO: Refactor this API in b/134702460 - * @hide + * @return Active data subscription id if any is chosen, or + * SubscriptionManager.INVALID_SUBSCRIPTION_ID if not. */ public static int getActiveDataSubscriptionId() { try { diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 2f90a3dce724..cb66a9650f2f 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -863,7 +863,7 @@ public class EuiccManager { * @param callbackIntent a PendingIntent to launch when the operation completes. * * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase - * and use {@link #eraseSubscriptionsWithOptions(int, PendingIntent)} instead + * and use {@link #eraseSubscriptions(int, PendingIntent)} instead * * @hide */ @@ -895,7 +895,7 @@ public class EuiccManager { */ @SystemApi @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) - public void eraseSubscriptionsWithOptions( + public void eraseSubscriptions( @ResetOption int options, @NonNull PendingIntent callbackIntent) { if (!isEnabled()) { sendUnavailableError(callbackIntent); diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index eb0e2f7b8786..5fd0af564d34 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -979,25 +979,25 @@ public class ImsMmTelManager implements RegistrationManager { /** * Get the status of the MmTel Feature registered on this subscription. + * @param executor The executor that will be used to call the callback. * @param callback A callback containing an Integer describing the current state of the * MmTel feature, Which will be one of the following: * {@link ImsFeature#STATE_UNAVAILABLE}, * {@link ImsFeature#STATE_INITIALIZING}, * {@link ImsFeature#STATE_READY}. Will be called using the executor * specified when the service state has been retrieved from the IMS service. - * @param executor The executor that will be used to call the callback. * @throws ImsException if the IMS service associated with this subscription is not available or * the IMS service is not available. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void getFeatureState(@NonNull @ImsFeature.ImsState Consumer<Integer> callback, - @NonNull @CallbackExecutor Executor executor) throws ImsException { - if (callback == null) { - throw new IllegalArgumentException("Must include a non-null Consumer."); - } + public void getFeatureState(@NonNull @CallbackExecutor Executor executor, + @NonNull @ImsFeature.ImsState Consumer<Integer> callback) throws ImsException { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } + if (callback == null) { + throw new IllegalArgumentException("Must include a non-null Consumer."); + } try { getITelephony().getImsMmTelFeatureState(mSubId, new IIntegerConsumer.Stub() { @Override diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 0b5d4460612d..aa4174ad40f4 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -27,7 +27,10 @@ java_sdk_library { ":framework-core-sources-for-test-mock", ":framework_native_aidl", ], - libs: ["framework-all"], + libs: [ + "framework-all", + "app-compat-annotations", + ], api_packages: [ "android.test.mock", diff --git a/test-mock/src/android/test/mock/MockContentResolver.java b/test-mock/src/android/test/mock/MockContentResolver.java index a70152c8b732..8283019a10ec 100644 --- a/test-mock/src/android/test/mock/MockContentResolver.java +++ b/test-mock/src/android/test/mock/MockContentResolver.java @@ -16,6 +16,8 @@ package android.test.mock; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; @@ -130,17 +132,47 @@ public class MockContentResolver extends ContentResolver { } /** - * Overrides {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean) - * ContentResolver.notifChange(Uri, ContentObserver, boolean)}. All parameters are ignored. - * The method hides providers linked to MockContentResolver from other observers in the system. + * Overrides the behavior from the parent class to completely ignore any + * content notifications sent to this object. This effectively hides clients + * from observers elsewhere in the system. + */ + @Override + public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer) { + } + + /** + * Overrides the behavior from the parent class to completely ignore any + * content notifications sent to this object. This effectively hides clients + * from observers elsewhere in the system. * - * @param uri (Ignored) The uri of the content provider. - * @param observer (Ignored) The observer that originated the change. - * @param syncToNetwork (Ignored) If true, attempt to sync the change to the network. + * @deprecated callers should consider migrating to + * {@link #notifyChange(Uri, ContentObserver, int)}, as it + * offers support for many more options than just + * {@link #NOTIFY_SYNC_TO_NETWORK}. */ @Override - public void notifyChange(Uri uri, - ContentObserver observer, + @Deprecated + public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer, boolean syncToNetwork) { } + + /** + * Overrides the behavior from the parent class to completely ignore any + * content notifications sent to this object. This effectively hides clients + * from observers elsewhere in the system. + */ + @Override + public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer, + @NotifyFlags int flags) { + } + + /** + * Overrides the behavior from the parent class to completely ignore any + * content notifications sent to this object. This effectively hides clients + * from observers elsewhere in the system. + */ + @Override + public void notifyChange(@NonNull Iterable<Uri> uris, @Nullable ContentObserver observer, + @NotifyFlags int flags) { + } } diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 1be4ea8eccda..6f442300bce7 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -210,6 +210,7 @@ genrule { tools: [":soong_zip"], srcs: [ "Configuration.proto", + "ResourcesInternal.proto", "Resources.proto", ], out: ["aapt2-protos.zip"], diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 30c24d37a494..90343d4798a7 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -2627,7 +2627,6 @@ public class WifiConfiguration implements Parcelable { out.writeInt(apBand); out.writeInt(apChannel); BackupUtils.writeString(out, preSharedKey); - BackupUtils.writeString(out, saePasswordId); out.writeInt(getAuthType()); out.writeBoolean(hiddenSSID); return baos.toByteArray(); @@ -2651,7 +2650,6 @@ public class WifiConfiguration implements Parcelable { config.apBand = in.readInt(); config.apChannel = in.readInt(); config.preSharedKey = BackupUtils.readString(in); - config.saePasswordId = BackupUtils.readString(in); config.allowedKeyManagement.set(in.readInt()); if (version >= 3) { config.hiddenSSID = in.readBoolean(); |