diff options
890 files changed, 26939 insertions, 10210 deletions
diff --git a/Android.bp b/Android.bp index d52f08f89038..778aa55b54d2 100644 --- a/Android.bp +++ b/Android.bp @@ -82,6 +82,7 @@ filegroup { ":framework-mca-filterpacks-sources", ":framework-media-sources", ":framework-mms-sources", + ":framework-omapi-sources", ":framework-opengl-sources", ":framework-rs-sources", ":framework-sax-sources", @@ -273,6 +274,7 @@ java_library { "android.hardware.vibrator-V1.2-java", "android.hardware.vibrator-V1.3-java", "android.hardware.vibrator-V2-java", + "android.se.omapi-V1-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", @@ -324,8 +326,12 @@ java_defaults { "error_prone_android_framework", ], required: [ + // TODO(b/120066492): remove default_television.xml when the build system + // propagates "required" properly. + "default_television.xml", "framework-platform-compat-config", - // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. + // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build + // system propagates "required" properly. "gps_debug.conf", "icu4j-platform-compat-config", "protolog.conf.json.gz", @@ -378,6 +384,9 @@ java_library { "//frameworks/base/packages/Tethering/tests/unit", "//packages/modules/Connectivity/Tethering/tests/unit", ], + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + }, errorprone: { javacflags: [ "-Xep:AndroidFrameworkBinderIdentity:ERROR", diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 392df732953f..1620983c4137 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -5142,7 +5142,7 @@ public class AlarmManagerService extends SystemService { Slog.d(TAG, "mBroadcastRefCount -> " + mBroadcastRefCount); } if (mBroadcastRefCount == 0) { - mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0).sendToTarget(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 0, 0).sendToTarget(); mWakeLock.release(); if (mInFlight.size() > 0) { mLog.w("Finished all dispatches with " + mInFlight.size() @@ -5314,7 +5314,7 @@ public class AlarmManagerService extends SystemService { if (mBroadcastRefCount == 0) { setWakelockWorkSource(alarm.workSource, alarm.creatorUid, alarm.statsTag, true); mWakeLock.acquire(); - mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1).sendToTarget(); + mHandler.obtainMessage(AlarmHandler.REPORT_ALARMS_ACTIVE, 1, 0).sendToTarget(); } final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); 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 ef442f0413cc..3da508d490f1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1371,7 +1371,11 @@ public class JobSchedulerService extends com.android.server.SystemService jobStatus.hasContentTriggerConstraint(), jobStatus.isRequestedExpeditedJob(), /* isRunningAsExpeditedJob */ false, - JobProtoEnums.STOP_REASON_UNDEFINED); + JobProtoEnums.STOP_REASON_UNDEFINED, + jobStatus.getJob().isPrefetch(), + jobStatus.getJob().getPriority(), + jobStatus.getEffectivePriority(), + jobStatus.getNumFailures()); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index b44178fc9ac9..9cae864576ce 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -356,7 +356,11 @@ public final class JobServiceContext implements ServiceConnection { job.hasContentTriggerConstraint(), job.isRequestedExpeditedJob(), job.shouldTreatAsExpeditedJob(), - JobProtoEnums.STOP_REASON_UNDEFINED); + JobProtoEnums.STOP_REASON_UNDEFINED, + job.getJob().isPrefetch(), + job.getJob().getPriority(), + job.getEffectivePriority(), + job.getNumFailures()); try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); } catch (RemoteException e) { @@ -1028,7 +1032,11 @@ public final class JobServiceContext implements ServiceConnection { completedJob.hasContentTriggerConstraint(), completedJob.isRequestedExpeditedJob(), completedJob.startedAsExpeditedJob, - mParams.getStopReason()); + mParams.getStopReason(), + completedJob.getJob().isPrefetch(), + completedJob.getJob().getPriority(), + completedJob.getEffectivePriority(), + completedJob.getNumFailures()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), internalStopReason); diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING index 7d12b9513981..d9c463294a69 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING @@ -50,6 +50,12 @@ {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testMeteredNetworkAccess_expeditedJob"}, {"include-filter": "com.android.cts.net.HostsideRestrictBackgroundNetworkTests#testNonMeteredNetworkAccess_expeditedJob"} ] + }, + { + "name": "CtsStatsdAtomHostTestCases", + "options": [ + {"include-filter": "android.cts.statsdatom.jobscheduler"} + ] } ] } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 788bfe4a0d5d..9749c8087caf 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; +import android.appwidget.AppWidgetManager; import android.content.Context; import android.os.Handler; import android.os.Looper; @@ -63,6 +64,13 @@ public class PrefetchController extends StateController { private final PcConstants mPcConstants; private final PcHandler mHandler; + // Note: when determining prefetch bit satisfaction, we mark the bit as satisfied for apps with + // active widgets assuming that any prefetch jobs are being used for the widget. However, we + // don't have a callback telling us when widget status changes, which is incongruent with the + // aforementioned assumption. This inconsistency _should_ be fine since any jobs scheduled + // before the widget is activated are definitely not for the widget and don't have to be updated + // to "satisfied=true". + private AppWidgetManager mAppWidgetManager; private final UsageStatsManagerInternal mUsageStatsManagerInternal; @GuardedBy("mLock") @@ -118,6 +126,11 @@ public class PrefetchController extends StateController { } @Override + public void onSystemServicesReady() { + mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); + } + + @Override @GuardedBy("mLock") public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { if (jobStatus.getJob().isPrefetch()) { @@ -298,11 +311,23 @@ public class PrefetchController extends StateController { // Mark a prefetch constraint as satisfied in the following scenarios: // 1. The app is not open but it will be launched soon // 2. The app is open and the job is already running (so we let it finish) + // 3. The app is not open but has an active widget (we can't tell if a widget displays + // status/data, so this assumes the prefetch job is to update the data displayed on + // the widget). final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid()); final boolean satisfied; if (!appIsOpen) { - satisfied = willBeLaunchedSoonLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now); + final int userId = jobStatus.getSourceUserId(); + final String pkgName = jobStatus.getSourcePackageName(); + satisfied = willBeLaunchedSoonLocked(userId, pkgName, now) + // At the time of implementation, isBoundWidgetPackage() results in a process ID + // check and then a lookup into a map. Calling the method here every time + // is based on the assumption that widgets won't change often and + // AppWidgetManager won't be a bottleneck, so having a local cache won't provide + // huge performance gains. If anything changes, we should reconsider having a + // local cache. + || (mAppWidgetManager != null + && mAppWidgetManager.isBoundWidgetPackage(pkgName, userId)); } else { satisfied = mService.isCurrentlyRunningLocked(jobStatus); } diff --git a/apex/media/framework/java/android/media/MediaTranscodingManager.java b/apex/media/framework/java/android/media/MediaTranscodingManager.java index 3bfffbcd4451..aff320401061 100644 --- a/apex/media/framework/java/android/media/MediaTranscodingManager.java +++ b/apex/media/framework/java/android/media/MediaTranscodingManager.java @@ -949,6 +949,8 @@ public final class MediaTranscodingManager { * * @return the video track format to be used if transcoding should be performed, * and null otherwise. + * @throws IllegalArgumentException if the hinted source video format contains invalid + * parameters. */ @Nullable public MediaFormat resolveVideoFormat() { @@ -959,20 +961,19 @@ public final class MediaTranscodingManager { MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint); videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); - int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH); - int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT); + int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH, -1); + int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT, -1); if (width <= 0 || height <= 0) { throw new IllegalArgumentException( "Source Width and height must be larger than 0"); } - float frameRate = 30.0f; // default to 30fps. - if (mSrcVideoFormatHint.containsKey(MediaFormat.KEY_FRAME_RATE)) { - frameRate = mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE); - if (frameRate <= 0) { - throw new IllegalArgumentException( - "frameRate must be larger than 0"); - } + float frameRate = + mSrcVideoFormatHint.getNumber(MediaFormat.KEY_FRAME_RATE, 30.0) + .floatValue(); + if (frameRate <= 0) { + throw new IllegalArgumentException( + "frameRate must be larger than 0"); } int bitrate = getAVCBitrate(width, height, frameRate); diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt index 5f27cc722105..82269d4a2467 100644 --- a/boot/boot-image-profile.txt +++ b/boot/boot-image-profile.txt @@ -2899,10 +2899,8 @@ HSPLandroid/app/assist/AssistStructure$ViewNode;->getAutofillId()Landroid/view/a HSPLandroid/app/assist/AssistStructure$ViewNode;->getChildCount()I HSPLandroid/app/assist/AssistStructure$ViewNode;->writeSelfToParcel(Landroid/os/Parcel;Landroid/os/PooledStringWriter;Z[FZ)I+]Landroid/view/autofill/AutofillId;Landroid/view/autofill/AutofillId;]Landroid/graphics/Matrix;Landroid/graphics/Matrix;]Landroid/app/assist/AssistStructure$ViewNodeText;Landroid/app/assist/AssistStructure$ViewNodeText;]Landroid/os/Parcel;Landroid/os/Parcel; HSPLandroid/app/assist/AssistStructure$ViewNode;->writeString(Landroid/os/Parcel;Landroid/os/PooledStringWriter;Ljava/lang/String;)V+]Landroid/os/PooledStringWriter;Landroid/os/PooledStringWriter;]Landroid/os/Parcel;Landroid/os/Parcel; -HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;-><init>()V HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getChildCount()I HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getNodeText()Landroid/app/assist/AssistStructure$ViewNodeText; -HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->getViewNode()Landroid/app/assist/AssistStructure$ViewNode; HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->newChild(I)Landroid/view/ViewStructure; HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->setAutofillHints([Ljava/lang/String;)V HSPLandroid/app/assist/AssistStructure$ViewNodeBuilder;->setAutofillId(Landroid/view/autofill/AutofillId;)V @@ -3883,8 +3881,6 @@ HSPLandroid/content/ContentResolver$ResultListener;-><init>()V HSPLandroid/content/ContentResolver$ResultListener;-><init>(Landroid/content/ContentResolver$1;)V HSPLandroid/content/ContentResolver$ResultListener;->onResult(Landroid/os/Bundle;)V+]Landroid/os/ParcelableException;Landroid/os/ParcelableException;]Ljava/lang/Object;Landroid/content/ContentResolver$StringResultListener;,Landroid/content/ContentResolver$UriResultListener;]Landroid/os/Bundle;Landroid/os/Bundle;]Landroid/content/ContentResolver$ResultListener;Landroid/content/ContentResolver$UriResultListener;,Landroid/content/ContentResolver$StringResultListener; HSPLandroid/content/ContentResolver$ResultListener;->waitForResult(J)V+]Ljava/lang/Object;Landroid/content/ContentResolver$StringResultListener; -HSPLandroid/content/ContentResolver$StringResultListener;-><init>()V -HSPLandroid/content/ContentResolver$StringResultListener;-><init>(Landroid/content/ContentResolver$1;)V HSPLandroid/content/ContentResolver$StringResultListener;->getResultFromBundle(Landroid/os/Bundle;)Ljava/lang/Object;+]Landroid/content/ContentResolver$StringResultListener;Landroid/content/ContentResolver$StringResultListener; HSPLandroid/content/ContentResolver$StringResultListener;->getResultFromBundle(Landroid/os/Bundle;)Ljava/lang/String;+]Landroid/os/Bundle;Landroid/os/Bundle; HSPLandroid/content/ContentResolver;-><init>(Landroid/content/Context;)V @@ -7029,7 +7025,6 @@ HSPLandroid/graphics/RectF;->width()F HSPLandroid/graphics/Region$1;->createFromParcel(Landroid/os/Parcel;)Landroid/graphics/Region; HSPLandroid/graphics/Region$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object;+]Landroid/graphics/Region$1;Landroid/graphics/Region$1; HSPLandroid/graphics/Region;-><init>()V -HSPLandroid/graphics/Region;-><init>(IIII)V HSPLandroid/graphics/Region;-><init>(J)V HSPLandroid/graphics/Region;->access$000(Landroid/os/Parcel;)J HSPLandroid/graphics/Region;->equals(Ljava/lang/Object;)Z @@ -7075,7 +7070,6 @@ HSPLandroid/graphics/RenderNode;->getTranslationZ()F HSPLandroid/graphics/RenderNode;->hasDisplayList()Z HSPLandroid/graphics/RenderNode;->hasIdentityMatrix()Z HSPLandroid/graphics/RenderNode;->isAttached()Z+]Landroid/graphics/RenderNode$AnimationHost;Landroid/view/ViewAnimationHostBridge; -HSPLandroid/graphics/RenderNode;->isPivotExplicitlySet()Z HSPLandroid/graphics/RenderNode;->offsetTopAndBottom(I)Z HSPLandroid/graphics/RenderNode;->setAlpha(F)Z HSPLandroid/graphics/RenderNode;->setAnimationMatrix(Landroid/graphics/Matrix;)Z+]Landroid/graphics/Matrix;Landroid/graphics/Matrix; @@ -8283,7 +8277,6 @@ HSPLandroid/hardware/CameraStatus$1;->newArray(I)[Ljava/lang/Object; HSPLandroid/hardware/GeomagneticField$LegendreTable;-><init>(IF)V HSPLandroid/hardware/GeomagneticField;-><init>(FFFJ)V HSPLandroid/hardware/GeomagneticField;->computeGeocentricCoordinates(FFF)V -HSPLandroid/hardware/GeomagneticField;->getDeclination()F HSPLandroid/hardware/HardwareBuffer$1;->createFromParcel(Landroid/os/Parcel;)Landroid/hardware/HardwareBuffer; HSPLandroid/hardware/HardwareBuffer$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object; HSPLandroid/hardware/HardwareBuffer;-><init>(J)V+]Llibcore/util/NativeAllocationRegistry;Llibcore/util/NativeAllocationRegistry;]Ljava/lang/Class;Ljava/lang/Class;]Ldalvik/system/CloseGuard;Ldalvik/system/CloseGuard; @@ -11353,7 +11346,6 @@ HSPLandroid/media/SoundPool$Builder;->setMaxStreams(I)Landroid/media/SoundPool$B HSPLandroid/media/SoundPool$EventHandler;->handleMessage(Landroid/os/Message;)V HSPLandroid/media/SoundPool;-><init>(ILandroid/media/AudioAttributes;)V HSPLandroid/media/SoundPool;-><init>(ILandroid/media/AudioAttributes;Landroid/media/SoundPool$1;)V -HSPLandroid/media/SoundPool;->load(Ljava/lang/String;I)I HSPLandroid/media/SoundPool;->postEventFromNative(Ljava/lang/Object;IIILjava/lang/Object;)V HSPLandroid/media/SoundPool;->setOnLoadCompleteListener(Landroid/media/SoundPool$OnLoadCompleteListener;)V HSPLandroid/media/SubtitleController$1;->handleMessage(Landroid/os/Message;)Z @@ -11389,10 +11381,8 @@ HSPLandroid/media/Utils;->parseSizeRange(Ljava/lang/Object;)Landroid/util/Pair; HSPLandroid/media/Utils;->sortDistinctRanges([Landroid/util/Range;)V HSPLandroid/media/audiofx/AudioEffect$Descriptor;-><init>(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V HSPLandroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;-><init>(II[Landroid/media/AudioAttributes;)V -HSPLandroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;->supportsStreamType(I)Z HSPLandroid/media/audiopolicy/AudioProductStrategy;-><init>(Ljava/lang/String;I[Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;)V HSPLandroid/media/audiopolicy/AudioProductStrategy;->attributesMatches(Landroid/media/AudioAttributes;Landroid/media/AudioAttributes;)Z+]Landroid/media/AudioAttributes;Landroid/media/AudioAttributes; -HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioAttributesForLegacyStreamType(I)Landroid/media/AudioAttributes;+]Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup; HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioAttributesForStrategyWithLegacyStreamType(I)Landroid/media/AudioAttributes; HSPLandroid/media/audiopolicy/AudioProductStrategy;->getAudioProductStrategies()Ljava/util/List; HSPLandroid/media/audiopolicy/AudioProductStrategy;->getLegacyStreamTypeForStrategyWithAudioAttributes(Landroid/media/AudioAttributes;)I+]Landroid/media/audiopolicy/AudioProductStrategy;Landroid/media/audiopolicy/AudioProductStrategy;]Ljava/util/List;Ljava/util/ArrayList;]Ljava/util/Iterator;Ljava/util/ArrayList$Itr; @@ -13125,7 +13115,6 @@ HSPLandroid/os/ParcelableException;-><init>(Ljava/lang/Throwable;)V HSPLandroid/os/ParcelableParcel$1;->createFromParcel(Landroid/os/Parcel;)Landroid/os/ParcelableParcel; HSPLandroid/os/ParcelableParcel$1;->createFromParcel(Landroid/os/Parcel;)Ljava/lang/Object; HSPLandroid/os/ParcelableParcel;-><init>(Landroid/os/Parcel;Ljava/lang/ClassLoader;)V -HSPLandroid/os/ParcelableParcel;-><init>(Ljava/lang/ClassLoader;)V HSPLandroid/os/ParcelableParcel;->getClassLoader()Ljava/lang/ClassLoader; HSPLandroid/os/ParcelableParcel;->getParcel()Landroid/os/Parcel; HSPLandroid/os/ParcelableParcel;->writeToParcel(Landroid/os/Parcel;I)V @@ -13481,7 +13470,6 @@ HSPLandroid/os/TelephonyServiceManager;->getTelephonyServiceRegisterer()Landroid HSPLandroid/os/Temperature;-><init>(FILjava/lang/String;I)V HSPLandroid/os/Temperature;->getStatus()I HSPLandroid/os/Temperature;->isValidStatus(I)Z -HSPLandroid/os/Temperature;->isValidType(I)Z HSPLandroid/os/ThreadLocalWorkSource$$ExternalSyntheticLambda0;->get()Ljava/lang/Object; HSPLandroid/os/ThreadLocalWorkSource;->getToken()J+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/ThreadLocal;Ljava/lang/ThreadLocal$SuppliedThreadLocal; HSPLandroid/os/ThreadLocalWorkSource;->getUid()I+]Ljava/lang/Integer;Ljava/lang/Integer;]Ljava/lang/ThreadLocal;Ljava/lang/ThreadLocal$SuppliedThreadLocal; @@ -18488,7 +18476,6 @@ HSPLandroid/view/ViewRootImpl$HighContrastTextManager;-><init>(Landroid/view/Vie HSPLandroid/view/ViewRootImpl$ImeInputStage;-><init>(Landroid/view/ViewRootImpl;Landroid/view/ViewRootImpl$InputStage;Ljava/lang/String;)V HSPLandroid/view/ViewRootImpl$ImeInputStage;->onFinishedInputEvent(Ljava/lang/Object;Z)V HSPLandroid/view/ViewRootImpl$ImeInputStage;->onProcess(Landroid/view/ViewRootImpl$QueuedInputEvent;)I -HSPLandroid/view/ViewRootImpl$InputMetricsListener;-><init>(Landroid/view/ViewRootImpl;)V HSPLandroid/view/ViewRootImpl$InputMetricsListener;->onFrameMetricsAvailable(I)V+]Landroid/view/ViewRootImpl$WindowInputEventReceiver;Landroid/view/ViewRootImpl$WindowInputEventReceiver;]Ljava/lang/StringBuilder;Ljava/lang/StringBuilder;]Landroid/view/InputEventReceiver;Landroid/view/ViewRootImpl$WindowInputEventReceiver; HSPLandroid/view/ViewRootImpl$InputStage;-><init>(Landroid/view/ViewRootImpl;Landroid/view/ViewRootImpl$InputStage;)V HSPLandroid/view/ViewRootImpl$InputStage;->apply(Landroid/view/ViewRootImpl$QueuedInputEvent;I)V+]Landroid/view/ViewRootImpl$InputStage;megamorphic_types @@ -20395,7 +20382,6 @@ HSPLandroid/widget/OverScroller$SplineOverScroller;->finish()V HSPLandroid/widget/OverScroller$SplineOverScroller;->fling(IIIII)V HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineDeceleration(I)D HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineFlingDistance(I)D -HSPLandroid/widget/OverScroller$SplineOverScroller;->getSplineFlingDuration(I)I HSPLandroid/widget/OverScroller$SplineOverScroller;->onEdgeReached()V HSPLandroid/widget/OverScroller$SplineOverScroller;->springback(III)Z HSPLandroid/widget/OverScroller$SplineOverScroller;->startScroll(III)V @@ -22568,10 +22554,7 @@ HSPLcom/android/internal/widget/ILockSettings$Stub;->asInterface(Landroid/os/IBi HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$1;->onIsNonStrongBiometricAllowedChanged(ZI)V HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$1;->onStrongAuthRequiredChanged(II)V HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker$H;->handleMessage(Landroid/os/Message;)V -HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;-><init>(Landroid/content/Context;)V -HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;-><init>(Landroid/content/Context;Landroid/os/Looper;)V HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->getStrongAuthForUser(I)I+]Landroid/util/SparseIntArray;Landroid/util/SparseIntArray; -HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->getStub()Landroid/app/trust/IStrongAuthTracker$Stub; HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->handleIsNonStrongBiometricAllowedChanged(ZI)V HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->handleStrongAuthRequiredChanged(II)V HSPLcom/android/internal/widget/LockPatternUtils$StrongAuthTracker;->isNonStrongBiometricAllowedAfterIdleTimeout(I)Z diff --git a/boot/hiddenapi/hiddenapi-unsupported.txt b/boot/hiddenapi/hiddenapi-unsupported.txt index 002d42dbf1dc..522e88f82e25 100644 --- a/boot/hiddenapi/hiddenapi-unsupported.txt +++ b/boot/hiddenapi/hiddenapi-unsupported.txt @@ -124,10 +124,8 @@ Landroid/content/pm/IPackageInstallObserver2$Stub;->asInterface(Landroid/os/IBin Landroid/content/pm/IPackageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IPackageManager$Stub$Proxy;->checkUidPermission(Ljava/lang/String;I)I Landroid/content/pm/IPackageManager$Stub$Proxy;->getAppOpPermissionPackages(Ljava/lang/String;)[Ljava/lang/String; -Landroid/content/pm/IPackageManager$Stub$Proxy;->getInstalledPackages(II)Landroid/content/pm/ParceledListSlice; Landroid/content/pm/IPackageManager$Stub$Proxy;->getInstallLocation()I Landroid/content/pm/IPackageManager$Stub$Proxy;->getLastChosenActivity(Landroid/content/Intent;Ljava/lang/String;I)Landroid/content/pm/ResolveInfo; -Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackageInfo(Ljava/lang/String;II)Landroid/content/pm/PackageInfo; Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackagesForUid(I)[Ljava/lang/String; Landroid/content/pm/IPackageManager$Stub$Proxy;->getSystemSharedLibraryNames()[Ljava/lang/String; Landroid/content/pm/IPackageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageManager; diff --git a/core/api/current.txt b/core/api/current.txt index 9d74a8c9ced5..c0d313d17633 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -129,6 +129,7 @@ package android { field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"; field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE"; field public static final String READ_LOGS = "android.permission.READ_LOGS"; + field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY"; field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"; field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE"; @@ -3255,6 +3256,28 @@ package android.accessibilityservice { method public boolean willContinue(); } + public final class MagnificationConfig implements android.os.Parcelable { + method public int describeContents(); + method public float getCenterX(); + method public float getCenterY(); + method public int getMode(); + method public float getScale(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR; + field public static final int DEFAULT_MODE = 0; // 0x0 + field public static final int FULLSCREEN_MODE = 1; // 0x1 + field public static final int WINDOW_MODE = 2; // 0x2 + } + + public static final class MagnificationConfig.Builder { + ctor public MagnificationConfig.Builder(); + method @NonNull public android.accessibilityservice.MagnificationConfig build(); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int); + method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(float); + } + public final class TouchInteractionController { method public int getDisplayId(); method public int getMaxPointerCount(); @@ -7226,8 +7249,8 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName); method public long getMaximumTimeToLock(@Nullable android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName); - method public int getNearbyAppStreamingPolicy(); - method public int getNearbyNotificationStreamingPolicy(); + method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy(); + method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy(); method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); method @Nullable public CharSequence getOrganizationName(@NonNull android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName); @@ -8720,6 +8743,7 @@ package android.bluetooth { method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter(); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout(); method public int getLeMaximumAdvertisingDataLength(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int); @@ -9014,11 +9038,15 @@ package android.bluetooth { public final class BluetoothClass implements android.os.Parcelable { method public int describeContents(); + method public boolean doesClassMatch(int); method public int getDeviceClass(); method public int getMajorDeviceClass(); method public boolean hasService(int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothClass> CREATOR; + field public static final int PROFILE_A2DP = 1; // 0x1 + field public static final int PROFILE_HEADSET = 0; // 0x0 + field public static final int PROFILE_HID = 3; // 0x3 } public static class BluetoothClass.Device { @@ -9332,7 +9360,8 @@ package android.bluetooth { method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]); method public android.bluetooth.BluetoothGattService getService(java.util.UUID); method public java.util.List<android.bluetooth.BluetoothGattService> getServices(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]); @@ -9926,6 +9955,16 @@ package android.bluetooth.le { package android.companion { + public final class AssociationInfo implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.net.MacAddress getDeviceMacAddress(); + method @Nullable public String getDeviceProfile(); + method @Nullable public CharSequence getDisplayName(); + method public int getId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR; + } + public final class AssociationRequest implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -9938,6 +9977,7 @@ package android.companion { method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>); method @NonNull public android.companion.AssociationRequest build(); method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String); + method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence); method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean); } @@ -9973,20 +10013,26 @@ package android.companion { } public final class CompanionDeviceManager { - method @RequiresPermission(value=android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler); - method public void disassociate(@NonNull String); - method @NonNull public java.util.List<java.lang.String> getAssociations(); + method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler); + method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback); + method @Deprecated public void disassociate(@NonNull String); + method public void disassociate(int); + method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations(); + method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations(); method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName); method public void requestNotificationAccess(android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; - field public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; + field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"; + field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; } public abstract static class CompanionDeviceManager.Callback { ctor public CompanionDeviceManager.Callback(); - method public abstract void onDeviceFound(android.content.IntentSender); - method public abstract void onFailure(CharSequence); + method public void onAssociationCreated(@NonNull android.companion.AssociationInfo); + method public void onAssociationPending(@NonNull android.content.IntentSender); + method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender); + method public abstract void onFailure(@Nullable CharSequence); } public abstract class CompanionDeviceService extends android.app.Service { @@ -11279,6 +11325,7 @@ package android.content { field public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW"; field public static final String ACTION_REBOOT = "android.intent.action.REBOOT"; field public static final String ACTION_RUN = "android.intent.action.RUN"; + field public static final String ACTION_SAFETY_CENTER = "android.intent.action.SAFETY_CENTER"; field public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF"; field public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; field public static final String ACTION_SEARCH = "android.intent.action.SEARCH"; @@ -12532,6 +12579,7 @@ package android.content.pm { method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException; method public void removeChildSessionId(int); method public void removeSplit(@NonNull String) throws java.io.IOException; + method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException; method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException; method public void setStagingProgress(float); method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; @@ -16487,11 +16535,13 @@ package android.graphics.drawable { public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { ctor public AdaptiveIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable); + ctor public AdaptiveIconDrawable(@Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable); method public void draw(android.graphics.Canvas); method public android.graphics.drawable.Drawable getBackground(); method public static float getExtraInsetFraction(); method public android.graphics.drawable.Drawable getForeground(); method public android.graphics.Path getIconMask(); + method @Nullable public android.graphics.drawable.Drawable getMonochrome(); method public int getOpacity(); method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable); method public void scheduleDrawable(@NonNull android.graphics.drawable.Drawable, @NonNull Runnable, long); @@ -20660,6 +20710,7 @@ package android.media { method public boolean isMicrophoneMute(); method public boolean isMusicActive(); method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes); + method public boolean isRampingRingerEnabled(); method public boolean isSpeakerphoneOn(); method public boolean isStreamMute(int); method public boolean isSurroundFormatEnabled(int); @@ -32348,6 +32399,7 @@ package android.os { field public static final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper"; field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile"; field public static final String DISALLOW_SHARE_LOCATION = "no_share_location"; + field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi"; field public static final String DISALLOW_SMS = "no_sms"; field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password"; @@ -32384,13 +32436,16 @@ package android.os { method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationAttributes> CREATOR; field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1; // 0x1 + field public static final int USAGE_ACCESSIBILITY = 66; // 0x42 field public static final int USAGE_ALARM = 17; // 0x11 field public static final int USAGE_CLASS_ALARM = 1; // 0x1 field public static final int USAGE_CLASS_FEEDBACK = 2; // 0x2 field public static final int USAGE_CLASS_MASK = 15; // 0xf + field public static final int USAGE_CLASS_MEDIA = 3; // 0x3 field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0 field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41 field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32 + field public static final int USAGE_MEDIA = 19; // 0x13 field public static final int USAGE_NOTIFICATION = 49; // 0x31 field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22 field public static final int USAGE_RINGTONE = 33; // 0x21 @@ -35572,7 +35627,7 @@ package android.provider { field public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios"; field public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities"; field public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale"; - field public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer"; + field @Deprecated public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer"; field public static final String AUTO_TIME = "auto_time"; field public static final String AUTO_TIME_ZONE = "auto_time_zone"; field public static final String BLUETOOTH_ON = "bluetooth_on"; @@ -51130,6 +51185,7 @@ package android.view.accessibility { method public int getRecommendedTimeoutMillis(int, int); method public void interrupt(); method public static boolean isAccessibilityButtonSupported(); + method public boolean isAudioDescriptionRequested(); method public boolean isEnabled(); method public boolean isTouchExplorationEnabled(); method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer); @@ -51423,14 +51479,18 @@ package android.view.accessibility { public static final class AccessibilityNodeInfo.CollectionItemInfo { ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean); ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean); + ctor public AccessibilityNodeInfo.CollectionItemInfo(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean); method public int getColumnIndex(); method public int getColumnSpan(); + method @Nullable public String getColumnTitle(); method public int getRowIndex(); method public int getRowSpan(); + method @Nullable public String getRowTitle(); method @Deprecated public boolean isHeading(); method public boolean isSelected(); method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean); method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean); + method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean); } public static final class AccessibilityNodeInfo.ExtraRenderingInfo { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6d342db6cdf9..e52eeb3c281e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -215,6 +215,7 @@ package android { field public static final String POWER_SAVER = "android.permission.POWER_SAVER"; field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; + field public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY"; field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES"; field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION"; field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION"; @@ -256,7 +257,8 @@ package android { field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"; field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"; - field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; + field public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission.REQUEST_COMPANION_SELF_MANAGED"; + field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM"; @@ -267,10 +269,11 @@ package android { field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS"; field public static final String ROTATE_SURFACE_FLINGER = "android.permission.ROTATE_SURFACE_FLINGER"; field public static final String SCHEDULE_PRIORITIZED_ALARM = "android.permission.SCHEDULE_PRIORITIZED_ALARM"; - field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; + field @Deprecated public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS"; field public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission.SECURE_ELEMENT_PRIVILEGED_OPERATION"; field public static final String SEND_CATEGORY_CAR_NOTIFICATIONS = "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS"; field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY"; + field public static final String SEND_SAFETY_CENTER_UPDATE = "android.permission.SEND_SAFETY_CENTER_UPDATE"; field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"; field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION"; field public static final String SERIAL_PORT = "android.permission.SERIAL_PORT"; @@ -726,6 +729,10 @@ package android.app { field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED"; } + public final class GameManager { + method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int); + } + public abstract class InstantAppResolverService extends android.app.Service { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); @@ -964,8 +971,8 @@ package android.app.admin { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); @@ -2000,6 +2007,8 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setDiscoverableTimeout(@NonNull java.time.Duration); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setScanMode(int); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2 @@ -2016,6 +2025,13 @@ package android.bluetooth { method public void onOobData(int, @NonNull android.bluetooth.OobData); } + public final class BluetoothClass implements android.os.Parcelable { + field public static final int PROFILE_A2DP_SINK = 6; // 0x6 + field public static final int PROFILE_NAP = 5; // 0x5 + field public static final int PROFILE_OPP = 2; // 0x2 + field public static final int PROFILE_PANU = 4; // 0x4 + } + public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<java.lang.Integer> getAllGroupIds(@Nullable android.os.ParcelUuid); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); @@ -2343,15 +2359,34 @@ package android.bluetooth.le { package android.companion { + public final class AssociationInfo implements android.os.Parcelable { + method @NonNull public String getPackageName(); + method public boolean isSelfManaged(); + } + public final class AssociationRequest implements android.os.Parcelable { + method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isForceConfirmation(); + method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public boolean isSelfManaged(); field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING"; field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; } + public static final class AssociationRequest.Builder { + method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setForceConfirmation(boolean); + method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public android.companion.AssociationRequest.Builder setSelfManaged(boolean); + } + public final class CompanionDeviceManager { + method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void addOnAssociationsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener); method @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public void associate(@NonNull String, @NonNull android.net.MacAddress, @NonNull byte[]); method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle); + method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations(); method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle); + method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener); + } + + public static interface CompanionDeviceManager.OnAssociationsChangedListener { + method public void onAssociationsChanged(@NonNull java.util.List<android.companion.AssociationInfo>); } } @@ -2437,13 +2472,14 @@ package android.content { field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding"; field public static final String MUSIC_RECOGNITION_SERVICE = "music_recognition"; field public static final String NETD_SERVICE = "netd"; - field public static final String NETWORK_SCORE_SERVICE = "network_score"; + field @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; field public static final String REBOOT_READINESS_SERVICE = "reboot_readiness"; field public static final String ROLLBACK_SERVICE = "rollback"; + field public static final String SAFETY_CENTER_SERVICE = "safety_center"; field public static final String SEARCH_UI_SERVICE = "search_ui"; field public static final String SECURE_ELEMENT_SERVICE = "secure_element"; field public static final String SMARTSPACE_SERVICE = "smartspace"; @@ -6308,6 +6344,7 @@ package android.media.tv.tuner.filter { method public int getAudioStreamType(); method public int getVideoStreamType(); method public boolean isPassthrough(); + method public boolean useSecureMemory(); field public static final int AUDIO_STREAM_TYPE_AAC = 6; // 0x6 field public static final int AUDIO_STREAM_TYPE_AC3 = 7; // 0x7 field public static final int AUDIO_STREAM_TYPE_AC4 = 9; // 0x9 @@ -6343,6 +6380,7 @@ package android.media.tv.tuner.filter { method @NonNull public android.media.tv.tuner.filter.AvSettings build(); method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setAudioStreamType(int); method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setPassthrough(boolean); + method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setUseSecureMemory(boolean); method @NonNull public android.media.tv.tuner.filter.AvSettings.Builder setVideoStreamType(int); } @@ -6365,14 +6403,14 @@ package android.media.tv.tuner.filter { } public class Filter implements java.lang.AutoCloseable { + method @Nullable public String acquireSharedFilterToken(); method public void close(); method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration); - method @Nullable public String createSharedFilter(); method public int flush(); + method public void freeSharedFilterToken(@NonNull String); method @Deprecated public int getId(); method public long getIdLong(); method public int read(@NonNull byte[], long, long); - method public void releaseSharedFilter(@NonNull String); method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter); method public int setMonitorEventMask(int); method public int start(); @@ -6518,6 +6556,7 @@ package android.media.tv.tuner.filter { method public int getTsIndexMask(); field public static final int INDEX_TYPE_NONE = 0; // 0x0 field public static final int INDEX_TYPE_SC = 1; // 0x1 + field public static final int INDEX_TYPE_SC_AVC = 3; // 0x3 field public static final int INDEX_TYPE_SC_HEVC = 2; // 0x2 field public static final int MPT_INDEX_AUDIO = 262144; // 0x40000 field public static final int MPT_INDEX_MPT = 65536; // 0x10000 @@ -6610,6 +6649,7 @@ package android.media.tv.tuner.filter { method @NonNull public static android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder builder(int); method public int getTableId(); method public int getVersion(); + field public static final int INVALID_TABLE_INFO_VERSION = -1; // 0xffffffff } public static class SectionSettingsWithTableInfo.Builder extends android.media.tv.tuner.filter.SectionSettings.Builder<android.media.tv.tuner.filter.SectionSettingsWithTableInfo.Builder> { @@ -7668,15 +7708,15 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } - public class NetworkKey implements android.os.Parcelable { - ctor public NetworkKey(android.net.WifiKey); - method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR; - field public static final int TYPE_WIFI = 1; // 0x1 - field public final int type; - field public final android.net.WifiKey wifiKey; + @Deprecated public class NetworkKey implements android.os.Parcelable { + ctor @Deprecated public NetworkKey(android.net.WifiKey); + method @Deprecated @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkKey> CREATOR; + field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 + field @Deprecated public final int type; + field @Deprecated public final android.net.WifiKey wifiKey; } public abstract class NetworkRecommendationProvider { @@ -7685,31 +7725,31 @@ package android.net { method public abstract void onRequestScores(android.net.NetworkKey[]); } - public class NetworkScoreManager { - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage(); - method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; - method @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull java.util.Collection<android.net.NetworkKey>) throws java.lang.SecurityException; - method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String) throws java.lang.SecurityException; - method @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException; + @Deprecated public class NetworkScoreManager { + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage(); + method @Deprecated @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public void registerNetworkScoreCallback(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkScoreManager.NetworkScoreCallback) throws java.lang.SecurityException; + method @Deprecated @RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES) public boolean requestScores(@NonNull java.util.Collection<android.net.NetworkKey>) throws java.lang.SecurityException; + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String) throws java.lang.SecurityException; + method @Deprecated @RequiresPermission(android.Manifest.permission.SCORE_NETWORKS) public boolean updateScores(@NonNull android.net.ScoredNetwork[]) throws java.lang.SecurityException; field @Deprecated public static final String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE"; - field public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE"; - field public static final String ACTION_RECOMMEND_NETWORKS = "android.net.action.RECOMMEND_NETWORKS"; - field public static final String ACTION_SCORER_CHANGED = "android.net.scoring.SCORER_CHANGED"; + field @Deprecated public static final String ACTION_CUSTOM_ENABLE = "android.net.scoring.CUSTOM_ENABLE"; + field @Deprecated public static final String ACTION_RECOMMEND_NETWORKS = "android.net.action.RECOMMEND_NETWORKS"; + field @Deprecated public static final String ACTION_SCORER_CHANGED = "android.net.scoring.SCORER_CHANGED"; field @Deprecated public static final String ACTION_SCORE_NETWORKS = "android.net.scoring.SCORE_NETWORKS"; field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore"; - field public static final String EXTRA_NEW_SCORER = "newScorer"; + field @Deprecated public static final String EXTRA_NEW_SCORER = "newScorer"; field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName"; - field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1 - field public static final int SCORE_FILTER_NONE = 0; // 0x0 - field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 + field @Deprecated public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1 + field @Deprecated public static final int SCORE_FILTER_NONE = 0; // 0x0 + field @Deprecated public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2 } - public abstract static class NetworkScoreManager.NetworkScoreCallback { - ctor public NetworkScoreManager.NetworkScoreCallback(); - method public abstract void onScoresInvalidated(); - method public abstract void onScoresUpdated(@NonNull java.util.Collection<android.net.ScoredNetwork>); + @Deprecated public abstract static class NetworkScoreManager.NetworkScoreCallback { + ctor @Deprecated public NetworkScoreManager.NetworkScoreCallback(); + method @Deprecated public abstract void onScoresInvalidated(); + method @Deprecated public abstract void onScoresUpdated(@NonNull java.util.Collection<android.net.ScoredNetwork>); } public abstract class NetworkSpecifier { @@ -7748,35 +7788,35 @@ package android.net { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); } - public class RssiCurve implements android.os.Parcelable { - ctor public RssiCurve(int, int, byte[]); - ctor public RssiCurve(int, int, byte[], int); - method public int describeContents(); - method public byte lookupScore(int); - method public byte lookupScore(int, boolean); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.RssiCurve> CREATOR; - field public final int activeNetworkRssiBoost; - field public final int bucketWidth; - field public final byte[] rssiBuckets; - field public final int start; - } - - public class ScoredNetwork implements android.os.Parcelable { - ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve); - ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean); - ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, @Nullable android.os.Bundle); - method public int calculateBadge(int); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final String ATTRIBUTES_KEY_BADGING_CURVE = "android.net.attributes.key.BADGING_CURVE"; - field public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL"; - field public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET"; - field @NonNull public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR; - field @Nullable public final android.os.Bundle attributes; - field public final boolean meteredHint; - field public final android.net.NetworkKey networkKey; - field public final android.net.RssiCurve rssiCurve; + @Deprecated public class RssiCurve implements android.os.Parcelable { + ctor @Deprecated public RssiCurve(int, int, byte[]); + ctor @Deprecated public RssiCurve(int, int, byte[], int); + method @Deprecated public int describeContents(); + method @Deprecated public byte lookupScore(int); + method @Deprecated public byte lookupScore(int, boolean); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.RssiCurve> CREATOR; + field @Deprecated public final int activeNetworkRssiBoost; + field @Deprecated public final int bucketWidth; + field @Deprecated public final byte[] rssiBuckets; + field @Deprecated public final int start; + } + + @Deprecated public class ScoredNetwork implements android.os.Parcelable { + ctor @Deprecated public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve); + ctor @Deprecated public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean); + ctor @Deprecated public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, @Nullable android.os.Bundle); + method @Deprecated public int calculateBadge(int); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated public static final String ATTRIBUTES_KEY_BADGING_CURVE = "android.net.attributes.key.BADGING_CURVE"; + field @Deprecated public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL"; + field @Deprecated public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET"; + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR; + field @Deprecated @Nullable public final android.os.Bundle attributes; + field @Deprecated public final boolean meteredHint; + field @Deprecated public final android.net.NetworkKey networkKey; + field @Deprecated public final android.net.RssiCurve rssiCurve; } public class TrafficStats { @@ -7803,13 +7843,13 @@ package android.net { ctor public WebAddress(String) throws android.net.ParseException; } - public class WifiKey implements android.os.Parcelable { - ctor public WifiKey(String, String); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.WifiKey> CREATOR; - field public final String bssid; - field public final String ssid; + @Deprecated public class WifiKey implements android.os.Parcelable { + ctor @Deprecated public WifiKey(String, String); + method @Deprecated public int describeContents(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.WifiKey> CREATOR; + field @Deprecated public final String bssid; + field @Deprecated public final String ssid; } } @@ -12312,7 +12352,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst(); - method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping(); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping(); method public int getMaxNumberOfSimultaneouslyActiveSims(); method public static long getMaxNumberVerificationTimeoutMillis(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup(); @@ -12320,10 +12360,13 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState(); method public int getSimApplicationState(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int, int); method public int getSimCardState(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale(); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Collection<android.telephony.UiccSlotMapping> getSimSlotMapping(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telephony.RadioAccessSpecifier> getSystemSelectionChannels(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f83b3a42d843..c1ab0703b9d7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -267,10 +267,6 @@ package android.app { method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream(); } - public final class GameManager { - method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int); - } - public abstract class HomeVisibilityListener { ctor public HomeVisibilityListener(); method public abstract void onHomeVisibilityChanged(boolean); @@ -1458,6 +1454,7 @@ package android.media { method public boolean hasRegisteredDynamicPolicy(); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice(); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int); + method public void setRampingRingerEnabled(boolean); } public static final class AudioRecord.MetricsConstants { @@ -1834,11 +1831,6 @@ package android.os { method public int getAudioUsage(); } - public static final class VibrationAttributes.Builder { - ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes, @NonNull android.os.VibrationEffect); - ctor public VibrationAttributes.Builder(@NonNull android.os.VibrationAttributes, @NonNull android.os.VibrationEffect); - } - public abstract class VibrationEffect implements android.os.Parcelable { method public static android.os.VibrationEffect get(int); method public static android.os.VibrationEffect get(int, boolean); @@ -1855,13 +1847,9 @@ package android.os { } public static final class VibrationEffect.Composed extends android.os.VibrationEffect { - method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int); method public long getDuration(); method public int getRepeatIndex(); method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); - method @NonNull public android.os.VibrationEffect.Composed resolve(int); - method @NonNull public android.os.VibrationEffect.Composed scale(float); - method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR; } @@ -2009,72 +1997,47 @@ package android.os.strictmode { package android.os.vibrator { public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment { - method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int); method public int describeContents(); method public long getDuration(); method public int getEffectId(); method public int getEffectStrength(); - method public boolean hasNonZeroAmplitude(); - method @NonNull public android.os.vibrator.PrebakedSegment resolve(int); - method @NonNull public android.os.vibrator.PrebakedSegment scale(float); method public boolean shouldFallback(); - method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR; } public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment { - method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int); method public int describeContents(); method public int getDelay(); method public long getDuration(); method public int getPrimitiveId(); method public float getScale(); - method public boolean hasNonZeroAmplitude(); - method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int); - method @NonNull public android.os.vibrator.PrimitiveSegment scale(float); - method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR; } public final class RampSegment extends android.os.vibrator.VibrationEffectSegment { - method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int); method public int describeContents(); method public long getDuration(); method public float getEndAmplitude(); method public float getEndFrequency(); method public float getStartAmplitude(); method public float getStartFrequency(); - method public boolean hasNonZeroAmplitude(); - method @NonNull public android.os.vibrator.RampSegment resolve(int); - method @NonNull public android.os.vibrator.RampSegment scale(float); - method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR; } public final class StepSegment extends android.os.vibrator.VibrationEffectSegment { - method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int); method public int describeContents(); method public float getAmplitude(); method public long getDuration(); method public float getFrequency(); - method public boolean hasNonZeroAmplitude(); - method @NonNull public android.os.vibrator.StepSegment resolve(int); - method @NonNull public android.os.vibrator.StepSegment scale(float); - method public void validate(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR; } public abstract class VibrationEffectSegment implements android.os.Parcelable { - method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int); method public abstract long getDuration(); - method public abstract boolean hasNonZeroAmplitude(); - method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int); - method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float); - method public abstract void validate(); field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR; } @@ -2194,7 +2157,7 @@ package android.provider { field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate"; field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height"; field public static final String USER_PREFERRED_RESOLUTION_WIDTH = "user_preferred_resolution_width"; - field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; + field @Deprecated public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { @@ -3250,12 +3213,6 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR; } - public final class TaskFragmentAppearedInfo implements android.os.Parcelable { - method @NonNull public android.view.SurfaceControl getLeash(); - method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo(); - field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR; - } - public final class TaskFragmentCreationParams implements android.os.Parcelable { method @NonNull public android.os.IBinder getFragmentToken(); method @NonNull public android.graphics.Rect getInitialBounds(); @@ -3292,7 +3249,7 @@ package android.window { ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor); method @NonNull public java.util.concurrent.Executor getExecutor(); method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken(); - method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo); + method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable); method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo); method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 0f852b404440..09af72d6cc12 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -2071,7 +2071,7 @@ public abstract class AccessibilityService extends Service { try { connection.setServiceInfo(mInfo); mInfo = null; - AccessibilityInteractionClient.getInstance(this).clearCache(); + AccessibilityInteractionClient.getInstance(this).clearCache(mConnectionId); } catch (RemoteException re) { Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re); re.rethrowFromSystemServer(); @@ -2421,7 +2421,7 @@ public abstract class AccessibilityService extends Service { if (event != null) { // Send the event to AccessibilityCache via AccessibilityInteractionClient AccessibilityInteractionClient.getInstance(mContext).onAccessibilityEvent( - event); + event, mConnectionId); if (serviceWantsEvent && (mConnectionId != AccessibilityInteractionClient.NO_ID)) { // Send the event to AccessibilityService @@ -2451,7 +2451,7 @@ public abstract class AccessibilityService extends Service { args.recycle(); if (connection != null) { AccessibilityInteractionClient.getInstance(mContext).addConnection( - mConnectionId, connection); + mConnectionId, connection, /*initializeCache=*/true); if (mContext != null) { try { connection.setAttributionTag(mContext.getAttributionTag()); @@ -2466,7 +2466,8 @@ public abstract class AccessibilityService extends Service { AccessibilityInteractionClient.getInstance(mContext).removeConnection( mConnectionId); mConnectionId = AccessibilityInteractionClient.NO_ID; - AccessibilityInteractionClient.getInstance(mContext).clearCache(); + AccessibilityInteractionClient.getInstance(mContext) + .clearCache(mConnectionId); mCallback.init(AccessibilityInteractionClient.NO_ID, null); } return; @@ -2478,7 +2479,7 @@ public abstract class AccessibilityService extends Service { return; } case DO_CLEAR_ACCESSIBILITY_CACHE: { - AccessibilityInteractionClient.getInstance(mContext).clearCache(); + AccessibilityInteractionClient.getInstance(mContext).clearCache(mConnectionId); return; } case DO_ON_KEY_EVENT: { diff --git a/core/java/android/accessibilityservice/MagnificationConfig.aidl b/core/java/android/accessibilityservice/MagnificationConfig.aidl new file mode 100644 index 000000000000..fe415a864b80 --- /dev/null +++ b/core/java/android/accessibilityservice/MagnificationConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accessibilityservice; + +parcelable MagnificationConfig; diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java new file mode 100644 index 000000000000..8884508bb2b0 --- /dev/null +++ b/core/java/android/accessibilityservice/MagnificationConfig.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accessibilityservice; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class describes the magnification config for {@link AccessibilityService} to control the + * magnification. + * + * <p> + * When the magnification config uses {@link #DEFAULT_MODE}, + * {@link AccessibilityService} will be able to control the activated magnifier on the display. + * If there is no magnifier activated, it controls the last-activated magnification mode. + * If there is no magnifier activated before, it controls full-screen magnifier by default. + * </p> + * + * <p> + * When the magnification config uses {@link #FULLSCREEN_MODE}. {@link AccessibilityService} will + * be able to control full-screen magnifier on the display. + * </p> + * + * <p> + * When the magnification config uses {@link #WINDOW_MODE}. {@link AccessibilityService} will be + * able to control the activated window magnifier on the display. + * </p> + * + * <p> + * If the other magnification configs, scale centerX and centerY, are not set by the + * {@link Builder}, the configs should be current values or default values. And the center + * position ordinarily is the center of the screen. + * </p> + */ +public final class MagnificationConfig implements Parcelable { + + /** The controlling magnification mode. It controls the activated magnifier. */ + public static final int DEFAULT_MODE = 0; + /** The controlling magnification mode. It controls fullscreen magnifier. */ + public static final int FULLSCREEN_MODE = 1; + /** The controlling magnification mode. It controls window magnifier. */ + public static final int WINDOW_MODE = 2; + + @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = { + DEFAULT_MODE, + FULLSCREEN_MODE, + WINDOW_MODE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface MAGNIFICATION_MODE { + } + + private int mMode = DEFAULT_MODE; + private float mScale = Float.NaN; + private float mCenterX = Float.NaN; + private float mCenterY = Float.NaN; + + private MagnificationConfig() { + /* do nothing */ + } + + private MagnificationConfig(@NonNull Parcel parcel) { + mMode = parcel.readInt(); + mScale = parcel.readFloat(); + mCenterX = parcel.readFloat(); + mCenterY = parcel.readFloat(); + } + + /** + * Returns the magnification mode that is the current activated mode or the controlling mode of + * the config. + * + * @return The magnification mode + */ + public int getMode() { + return mMode; + } + + /** + * Returns the magnification scale of the controlling magnifier + * + * @return the scale If the controlling magnifier is not activated, it returns 1 by default + */ + public float getScale() { + return mScale; + } + + /** + * Returns the screen-relative X coordinate of the center of the magnification viewport. + * + * @return the X coordinate. If the controlling magnifier is {@link #WINDOW_MODE} but not + * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link + * #FULLSCREEN_MODE} but not enabled, it returns 0 + */ + public float getCenterX() { + return mCenterX; + } + + /** + * Returns the screen-relative Y coordinate of the center of the magnification viewport. + * + * @return the Y coordinate If the controlling magnifier is {@link #WINDOW_MODE} but not + * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link + * #FULLSCREEN_MODE} but not enabled, it returns 0 + */ + public float getCenterY() { + return mCenterY; + } + + @NonNull + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder("MagnificationConfig["); + stringBuilder.append("mode: ").append(getMode()); + stringBuilder.append(", "); + stringBuilder.append("scale: ").append(getScale()); + stringBuilder.append(", "); + stringBuilder.append("centerX: ").append(getCenterX()); + stringBuilder.append(", "); + stringBuilder.append("centerY: ").append(getCenterY()); + stringBuilder.append("] "); + return stringBuilder.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mMode); + parcel.writeFloat(mScale); + parcel.writeFloat(mCenterX); + parcel.writeFloat(mCenterY); + } + + /** + * Builder for creating {@link MagnificationConfig} objects. + */ + public static final class Builder { + + private int mMode = DEFAULT_MODE; + private float mScale = Float.NaN; + private float mCenterX = Float.NaN; + private float mCenterY = Float.NaN; + + /** + * Creates a new Builder. + */ + public Builder() { + } + + /** + * Sets the magnification mode. + * + * @param mode The magnification mode + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) { + mMode = mode; + return this; + } + + /** + * Sets the magnification scale. + * + * @param scale The magnification scale + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setScale(float scale) { + mScale = scale; + return this; + } + + /** + * Sets the X coordinate of the center of the magnification viewport. + * + * @param centerX the screen-relative X coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setCenterX(float centerX) { + mCenterX = centerX; + return this; + } + + /** + * Sets the Y coordinate of the center of the magnification viewport. + * + * @param centerY the screen-relative Y coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @return This builder + */ + @NonNull + public MagnificationConfig.Builder setCenterY(float centerY) { + mCenterY = centerY; + return this; + } + + /** + * Builds and returns a {@link MagnificationConfig} + */ + @NonNull + public MagnificationConfig build() { + MagnificationConfig magnificationConfig = new MagnificationConfig(); + magnificationConfig.mMode = mMode; + magnificationConfig.mScale = mScale; + magnificationConfig.mCenterX = mCenterX; + magnificationConfig.mCenterY = mCenterY; + return magnificationConfig; + } + } + + /** + * @see Parcelable.Creator + */ + public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR = + new Parcelable.Creator<MagnificationConfig>() { + public MagnificationConfig createFromParcel(Parcel parcel) { + return new MagnificationConfig(parcel); + } + + public MagnificationConfig[] newArray(int size) { + return new MagnificationConfig[size]; + } + }; +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index f9cc3234a9ee..9f8d24662c8d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4082,6 +4082,34 @@ public class ActivityManager { } /** + * Gets the message that is shown when a user is switched from. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingFromUserMessage() { + try { + return getService().getSwitchingFromUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the message that is shown when a user is switched to. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USERS) + public @Nullable String getSwitchingToUserMessage() { + try { + return getService().getSwitchingToUserMessage(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Uses the value defined by the platform. * * @hide diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c89e8b0f8d32..15f67d04115f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -29,6 +29,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE; import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS; import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets; import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; @@ -322,7 +323,7 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage private ContextImpl mSystemContext; - private ContextImpl mSystemUiContext; + private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>(); @UnsupportedAppUsage static volatile IPackageManager sPackageManager; @@ -1301,8 +1302,11 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void scheduleCrash(String msg, int typeId) { - sendMessage(H.SCHEDULE_CRASH, msg, typeId); + public void scheduleCrash(String msg, int typeId, @Nullable Bundle extras) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = msg; + args.arg2 = extras; + sendMessage(H.SCHEDULE_CRASH, args, typeId); } public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken, @@ -1932,11 +1936,11 @@ public final class ActivityThread extends ClientTransactionHandler } } - private void throwRemoteServiceException(String message, int typeId) { + private void throwRemoteServiceException(String message, int typeId, @Nullable Bundle extras) { // Use a switch to ensure all the type IDs are unique. switch (typeId) { case ForegroundServiceDidNotStartInTimeException.TYPE_ID: - throw new ForegroundServiceDidNotStartInTimeException(message); + throw generateForegroundServiceDidNotStartInTimeException(message, extras); case CannotDeliverBroadcastException.TYPE_ID: throw new CannotDeliverBroadcastException(message); @@ -1959,6 +1963,15 @@ public final class ActivityThread extends ClientTransactionHandler } } + private ForegroundServiceDidNotStartInTimeException + generateForegroundServiceDidNotStartInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStartInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStartInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage @@ -2176,9 +2189,14 @@ public final class ActivityThread extends ClientTransactionHandler handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; - case SCHEDULE_CRASH: - throwRemoteServiceException((String) msg.obj, msg.arg1); + case SCHEDULE_CRASH: { + SomeArgs args = (SomeArgs) msg.obj; + String message = (String) args.arg1; + Bundle extras = (Bundle) args.arg2; + args.recycle(); + throwRemoteServiceException(message, msg.arg1, extras); break; + } case DUMP_HEAP: handleDumpHeap((DumpHeapData) msg.obj); break; @@ -2641,22 +2659,26 @@ public final class ActivityThread extends ClientTransactionHandler } @Override + @NonNull public ContextImpl getSystemUiContext() { - synchronized (this) { - if (mSystemUiContext == null) { - mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext()); - } - return mSystemUiContext; - } + return getSystemUiContext(DEFAULT_DISPLAY); } /** - * Create the context instance base on system resources & display information which used for UI. + * Gets the context instance base on system resources & display information which used for UI. * @param displayId The ID of the display where the UI is shown. * @see ContextImpl#createSystemUiContext(ContextImpl, int) */ - public ContextImpl createSystemUiContext(int displayId) { - return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId); + @NonNull + public ContextImpl getSystemUiContext(int displayId) { + synchronized (this) { + ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId); + if (systemUiContext == null) { + systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId); + mDisplaySystemUiContexts.put(displayId, systemUiContext); + } + return systemUiContext; + } } public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { @@ -3775,7 +3797,7 @@ public final class ActivityThread extends ClientTransactionHandler if (pkgName != null && !pkgName.isEmpty() && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { - if (id != Display.DEFAULT_DISPLAY) { + if (id != DEFAULT_DISPLAY) { Display display = dm.getCompatibleDisplay(id, appContext.getResources()); appContext = (ContextImpl) appContext.createDisplayContext(display); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7a545f695aff..565f69090c6b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -9469,23 +9469,23 @@ public class AppOpsManager { e.rethrowFromSystemServer(); } - if (missedAsyncOps != null) { + // Copy pointer so callback can be dispatched out of lock + OnOpNotedCallback onOpNotedCallback = sOnOpNotedCallback; + if (onOpNotedCallback != null && missedAsyncOps != null) { int numMissedAsyncOps = missedAsyncOps.size(); for (int i = 0; i < numMissedAsyncOps; i++) { final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i); - if (sOnOpNotedCallback != null) { - sOnOpNotedCallback.getAsyncNotedExecutor().execute( - () -> sOnOpNotedCallback.onAsyncNoted(asyncNotedAppOp)); - } + onOpNotedCallback.getAsyncNotedExecutor().execute( + () -> onOpNotedCallback.onAsyncNoted(asyncNotedAppOp)); } } synchronized (this) { int numMissedSyncOps = sUnforwardedOps.size(); - for (int i = 0; i < numMissedSyncOps; i++) { - final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i); - if (sOnOpNotedCallback != null) { - sOnOpNotedCallback.getAsyncNotedExecutor().execute( - () -> sOnOpNotedCallback.onAsyncNoted(syncNotedAppOp)); + if (onOpNotedCallback != null) { + for (int i = 0; i < numMissedSyncOps; i++) { + final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i); + onOpNotedCallback.getAsyncNotedExecutor().execute( + () -> onOpNotedCallback.onAsyncNoted(syncNotedAppOp)); } } sUnforwardedOps.clear(); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 1943f9d7187f..d1b71459b0f0 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -920,16 +920,16 @@ public class ApplicationPackageManager extends PackageManager { Objects.requireNonNull(packageName); Objects.requireNonNull(onChecksumsReadyListener); Objects.requireNonNull(trustedInstallers); + if (trustedInstallers == TRUST_ALL) { + trustedInstallers = null; + } else if (trustedInstallers == TRUST_NONE) { + trustedInstallers = Collections.emptyList(); + } else if (trustedInstallers.isEmpty()) { + throw new IllegalArgumentException( + "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty " + + "list of certificates."); + } try { - if (trustedInstallers == TRUST_ALL) { - trustedInstallers = null; - } else if (trustedInstallers == TRUST_NONE) { - trustedInstallers = Collections.emptyList(); - } else if (trustedInstallers.isEmpty()) { - throw new IllegalArgumentException( - "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty " - + "list of certificates."); - } IOnChecksumsReadyListener onChecksumsReadyListenerDelegate = new IOnChecksumsReadyListener.Stub() { @Override @@ -938,7 +938,7 @@ public class ApplicationPackageManager extends PackageManager { onChecksumsReadyListener.onChecksumsReady(checksums); } }; - mPM.requestChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required, + mPM.requestPackageChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required, encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate, getUserId()); } catch (ParcelableException e) { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 756833a012a4..4a7361efe4cc 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1882,6 +1882,14 @@ class ContextImpl extends Context { "Not allowed to start service " + service + ": " + cn.getClassName()); } } + // If we started a foreground service in the same package, remember the stack trace. + if (cn != null && requireForeground) { + if (cn.getPackageName().equals(getOpPackageName())) { + Service.setStartForegroundServiceStackTrace(cn.getClassName(), + new StackTrace("Last startServiceCommon() call for this service was " + + "made here")); + } + } return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -2638,7 +2646,10 @@ class ContextImpl extends Context { overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(), mResources.getLoaders())); context.mDisplay = display; - context.mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT; + // Inherit context type if the container is from System or System UI context to bypass + // UI context check. + context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI + ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI : CONTEXT_TYPE_DISPLAY_CONTEXT; // Display contexts and any context derived from a display context should always override // the display that would otherwise be inherited from mToken (or the global configuration if // mToken is null). @@ -2691,7 +2702,8 @@ class ContextImpl extends Context { // Step 2. Create the base context of the window context, it will also create a Resources // associated with the WindowTokenClient and set the token to the base context. - final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display); + final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, + display.getDisplayId()); // Step 3. Create a WindowContext instance and set it as the outer context of the base // context to make the service obtained by #getSystemService(String) able to query @@ -2716,9 +2728,7 @@ class ContextImpl extends Context { if (display == null) { throw new IllegalArgumentException("Display must not be null"); } - final ContextImpl tokenContext = createWindowContextBase(token, display); - tokenContext.setResources(createWindowContextResources(tokenContext)); - return tokenContext; + return createWindowContextBase(token, display.getDisplayId()); } /** @@ -2726,13 +2736,13 @@ class ContextImpl extends Context { * window. * * @param token The token to associate with {@link Resources} - * @param display The {@link Display} to associate with. + * @param displayId The ID of {@link Display} to associate with. * * @see #createWindowContext(Display, int, Bundle) * @see #createTokenContext(IBinder, Display) */ @UiContext - ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { + ContextImpl createWindowContextBase(@NonNull IBinder token, int displayId) { ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams, mAttributionSource.getAttributionTag(), mAttributionSource.getNext(), @@ -2746,8 +2756,8 @@ class ContextImpl extends Context { baseContext.setResources(windowContextResources); // Associate the display with window context resources so that configuration update from // the server side will also apply to the display's metrics. - baseContext.mDisplay = ResourcesManager.getInstance() - .getAdjustedDisplay(display.getDisplayId(), windowContextResources); + baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId, + windowContextResources); return baseContext; } @@ -2983,6 +2993,18 @@ class ContextImpl extends Context { mContentCaptureOptions = options; } + @Override + protected void finalize() throws Throwable { + // If mToken is a WindowTokenClient, the Context is usually associated with a + // WindowContainer. We should detach from WindowContainer when the Context is finalized + // if this Context is not a WindowContext. WindowContext finalization is handled in + // WindowContext class. + if (mToken instanceof WindowTokenClient && mContextType != CONTEXT_TYPE_WINDOW_CONTEXT) { + ((WindowTokenClient) mToken).detachFromWindowContainerIfNeeded(); + } + super.finalize(); + } + @UnsupportedAppUsage static ContextImpl createSystemContext(ActivityThread mainThread) { LoadedApk packageInfo = new LoadedApk(mainThread); @@ -3003,22 +3025,13 @@ class ContextImpl extends Context { * @param displayId The ID of the display where the UI is shown. */ static ContextImpl createSystemUiContext(ContextImpl systemContext, int displayId) { - final LoadedApk packageInfo = systemContext.mPackageInfo; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, - ContextParams.EMPTY, null, null, null, null, null, 0, null, null); - context.setResources(createResources(null, packageInfo, null, displayId, null, - packageInfo.getCompatibilityInfo(), null)); - context.updateDisplay(displayId); + final WindowTokenClient token = new WindowTokenClient(); + final ContextImpl context = systemContext.createWindowContextBase(token, displayId); + token.attachContext(context); + token.attachToDisplayContent(displayId); context.mContextType = CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI; - return context; - } - /** - * The overloaded method of {@link #createSystemUiContext(ContextImpl, int)}. - * Uses {@Code Display.DEFAULT_DISPLAY} as the target display. - */ - static ContextImpl createSystemUiContext(ContextImpl systemContext) { - return createSystemUiContext(systemContext, Display.DEFAULT_DISPLAY); + return context; } @UnsupportedAppUsage @@ -3227,7 +3240,13 @@ class ContextImpl extends Context { @Override public IBinder getWindowContextToken() { - return mContextType == CONTEXT_TYPE_WINDOW_CONTEXT ? mToken : null; + switch (mContextType) { + case CONTEXT_TYPE_WINDOW_CONTEXT: + case CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI: + return mToken; + default: + return null; + } } private void checkMode(int mode) { diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index b324fb68ff59..78759db28539 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -21,8 +21,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.annotation.UserHandleAware; import android.content.Context; import android.os.Handler; @@ -125,7 +125,7 @@ public final class GameManager { * * @hide */ - @TestApi + @SystemApi @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String packageName, @GameMode int gameMode) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 8bb40590d069..8f904b57474a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -332,6 +332,8 @@ interface IActivityManager { boolean isTopActivityImmersive(); void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId, in String message, boolean force, int exceptionTypeId); + void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName, + int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras); /** @deprecated -- use getProviderMimeTypeAsync */ @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives = "Use {@link android.content.ContentResolver#getType} public API instead.") @@ -349,6 +351,8 @@ interface IActivityManager { void setPackageScreenCompatMode(in String packageName, int mode); @UnsupportedAppUsage boolean switchUser(int userid); + String getSwitchingFromUserMessage(); + String getSwitchingToUserMessage(); @UnsupportedAppUsage void setStopUserOnSwitch(int value); boolean removeTask(int taskId); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 0e42a791cc3a..1714229486e4 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -108,7 +108,7 @@ oneway interface IApplicationThread { void scheduleOnNewActivityOptions(IBinder token, in Bundle options); void scheduleSuicide(); void dispatchPackageBroadcast(int cmd, in String[] packages); - void scheduleCrash(in String msg, int typeId); + void scheduleCrash(in String msg, int typeId, in Bundle extras); void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path, in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index 1038530d92d3..e220627706f9 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -16,6 +16,10 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; import android.util.AndroidRuntimeException; /** @@ -33,6 +37,10 @@ public class RemoteServiceException extends AndroidRuntimeException { super(msg); } + public RemoteServiceException(String msg, Throwable cause) { + super(msg, cause); + } + /** * Exception used to crash an app process when it didn't call {@link Service#startForeground} * in time after the service was started with @@ -44,8 +52,21 @@ public class RemoteServiceException extends AndroidRuntimeException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ public static final int TYPE_ID = 1; - public ForegroundServiceDidNotStartInTimeException(String msg) { - super(msg); + private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname"; + + public ForegroundServiceDidNotStartInTimeException(String msg, Throwable cause) { + super(msg, cause); + } + + public static Bundle createExtrasForService(@NonNull ComponentName service) { + Bundle b = new Bundle(); + b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName()); + return b; + } + + @Nullable + public static String getServiceClassNameFromExtras(@Nullable Bundle extras) { + return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME); } } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 0145747e0c65..be6a31e7f145 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -33,9 +33,12 @@ import android.content.res.Configuration; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; import android.view.contentcapture.ContentCaptureManager; +import com.android.internal.annotations.GuardedBy; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -729,6 +732,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -782,6 +786,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, foregroundServiceType); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -956,4 +961,34 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private IActivityManager mActivityManager = null; @UnsupportedAppUsage private boolean mStartCompatibility = false; + + /** + * This keeps track of the stacktrace where Context.startForegroundService() was called + * for each service class. We use that when we crash the app for not calling + * {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}. + */ + @GuardedBy("sStartForegroundServiceStackTraces") + private static final ArrayMap<String, StackTrace> sStartForegroundServiceStackTraces = + new ArrayMap<>(); + + /** @hide */ + public static void setStartForegroundServiceStackTrace( + @NonNull String className, @NonNull StackTrace stacktrace) { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.put(className, stacktrace); + } + } + + private void clearStartForegroundServiceStackTrace() { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.remove(this.getClassName()); + } + } + + /** @hide */ + public static StackTrace getStartForegroundServiceStackTrace(@NonNull String className) { + synchronized (sStartForegroundServiceStackTraces) { + return sStartForegroundServiceStackTraces.get(className); + } + } } diff --git a/core/java/android/app/StackTrace.java b/core/java/android/app/StackTrace.java new file mode 100644 index 000000000000..ec058f88118b --- /dev/null +++ b/core/java/android/app/StackTrace.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** + * An Exception subclass that's used only for logging stacktraces. + * @hide + */ +public class StackTrace extends Exception { + public StackTrace(String message) { + super(message); + } +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 74e28581717d..089c2691a277 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -196,9 +196,12 @@ import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.print.IPrintManager; import android.print.PrintManager; +import android.safetycenter.SafetyCenterFrameworkInitializer; import android.scheduling.SchedulingFrameworkInitializer; import android.security.FileIntegrityManager; import android.security.IFileIntegrityService; +import android.security.attestationverification.AttestationVerificationManager; +import android.security.attestationverification.IAttestationVerificationManagerService; import android.service.oemlock.IOemLockService; import android.service.oemlock.OemLockManager; import android.service.persistentdata.IPersistentDataBlockService; @@ -1424,6 +1427,19 @@ public final class SystemServiceRegistry { return new FileIntegrityManager(ctx.getOuterContext(), IFileIntegrityService.Stub.asInterface(b)); }}); + + registerService(Context.ATTESTATION_VERIFICATION_SERVICE, + AttestationVerificationManager.class, + new CachedServiceFetcher<AttestationVerificationManager>() { + @Override + public AttestationVerificationManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.ATTESTATION_VERIFICATION_SERVICE); + return new AttestationVerificationManager(ctx.getOuterContext(), + IAttestationVerificationManagerService.Stub.asInterface(b)); + }}); + //CHECKSTYLE:ON IndentationCheck registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class, new CachedServiceFetcher<AppIntegrityManager>() { @@ -1530,6 +1546,7 @@ public final class SystemServiceRegistry { SchedulingFrameworkInitializer.registerServiceWrappers(); SupplementalProcessFrameworkInitializer.registerServiceWrappers(); UwbFrameworkInitializer.registerServiceWrappers(); + SafetyCenterFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index ddde27220b96..95b00c17352d 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -219,6 +219,24 @@ public class TaskInfo { public boolean isResizeable; /** + * Minimal width of the task when it's resizeable. + * @hide + */ + public int minWidth; + + /** + * Minimal height of the task when it's resizeable. + * @hide + */ + public int minHeight; + + /** + * The default minimal size of the task used when a minWidth or minHeight is not specified. + * @hide + */ + public int defaultMinSize; + + /** * Relative position of the task's top left corner in the parent container. * @hide */ @@ -419,6 +437,9 @@ public class TaskInfo { displayCutoutInsets = source.readTypedObject(Rect.CREATOR); topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); + minWidth = source.readInt(); + minHeight = source.readInt(); + defaultMinSize = source.readInt(); source.readBinderList(launchCookies); positionInParent = source.readTypedObject(Point.CREATOR); parentTaskId = source.readInt(); @@ -459,6 +480,9 @@ public class TaskInfo { dest.writeTypedObject(displayCutoutInsets, flags); dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); + dest.writeInt(minWidth); + dest.writeInt(minHeight); + dest.writeInt(defaultMinSize); dest.writeBinderList(launchCookies); dest.writeTypedObject(positionInParent, flags); dest.writeInt(parentTaskId); @@ -484,6 +508,9 @@ public class TaskInfo { + " supportsMultiWindow=" + supportsMultiWindow + " resizeMode=" + resizeMode + " isResizeable=" + isResizeable + + " minWidth=" + minWidth + + " minHeight=" + minHeight + + " defaultMinSize=" + defaultMinSize + " token=" + token + " topActivityType=" + topActivityType + " pictureInPictureParams=" + pictureInPictureParams diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 828b17156536..58ded716cf40 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -638,7 +638,7 @@ public final class UiAutomation { final IAccessibilityServiceConnection connection; synchronized (mLock) { throwIfNotConnectedLocked(); - AccessibilityInteractionClient.getInstance().clearCache(); + AccessibilityInteractionClient.getInstance().clearCache(mConnectionId); connection = AccessibilityInteractionClient.getInstance() .getConnection(mConnectionId); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 57b319634759..cf95ffe62b6b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3471,7 +3471,10 @@ public class DevicePolicyManager { * Setting custom, overly-complicated password requirements leads to passwords that are hard * for users to remember and may not provide any security benefits given as Android uses * hardware-backed throttling to thwart online and offline brute-forcing of the device's - * screen lock. + * screen lock. Company-owned devices (fully-managed and organization-owned managed profile + * devices) are able to continue using this method, though it is recommended that + * {@link #setRequiredPasswordComplexity(int)} should be used instead. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param quality The new desired quality. One of {@link #PASSWORD_QUALITY_UNSPECIFIED}, * {@link #PASSWORD_QUALITY_BIOMETRIC_WEAK}, @@ -7269,6 +7272,9 @@ public class DevicePolicyManager { * Returns the current runtime nearby notification streaming policy set by the device or profile * owner. */ + @RequiresPermission( + value = android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, + conditional = true) public @NearbyStreamingPolicy int getNearbyNotificationStreamingPolicy() { return getNearbyNotificationStreamingPolicy(myUserId()); } @@ -7309,6 +7315,9 @@ public class DevicePolicyManager { /** * Returns the current runtime nearby app streaming policy set by the device or profile owner. */ + @RequiresPermission( + value = android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, + conditional = true) public @NearbyStreamingPolicy int getNearbyAppStreamingPolicy() { return getNearbyAppStreamingPolicy(myUserId()); } @@ -9137,7 +9146,9 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_ADMIN_POLICY}) public @Nullable List<String> getPermittedAccessibilityServices(int userId) { throwIfParentInstance("getPermittedAccessibilityServices"); if (mService != null) { @@ -9274,12 +9285,14 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_ADMIN_POLICY}) public @Nullable List<String> getPermittedInputMethodsForCurrentUser() { throwIfParentInstance("getPermittedInputMethodsForCurrentUser"); if (mService != null) { try { - return mService.getPermittedInputMethodsForCurrentUser(); + return mService.getPermittedInputMethodsAsUser(UserHandle.myUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9288,6 +9301,34 @@ public class DevicePolicyManager { } /** + * Returns the list of input methods permitted. + * + * <p>When this method returns empty list means all input methods are allowed, if a non-empty + * list is returned it will contain the intersection of the permitted lists for any device or + * profile owners that apply to this user. It will also include any system input methods. + * + * @return List of input method package names. + * @hide + */ + @UserHandleAware + @RequiresPermission(allOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.MANAGE_USERS + }, conditional = true) + public @NonNull List<String> getPermittedInputMethods() { + throwIfParentInstance("getPermittedInputMethods"); + List<String> result = null; + if (mService != null) { + try { + result = mService.getPermittedInputMethodsAsUser(myUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return result != null ? result : Collections.emptyList(); + } + + /** * Called by a profile owner of a managed profile to set the packages that are allowed to use * a {@link android.service.notification.NotificationListenerService} in the primary user to * see notifications from the managed profile. By default all packages are permitted by this diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index cf48594d97cb..b9fcdf537806 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -243,7 +243,7 @@ interface IDevicePolicyManager { boolean setPermittedInputMethods(in ComponentName admin,in List<String> packageList, boolean parent); List<String> getPermittedInputMethods(in ComponentName admin, boolean parent); - List<String> getPermittedInputMethodsForCurrentUser(); + List<String> getPermittedInputMethodsAsUser(int userId); boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId, boolean parent); boolean setPermittedCrossProfileNotificationListeners(in ComponentName admin, in List<String> packageList); diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index a71cffe5afc0..ceab02f51062 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -96,6 +96,13 @@ public interface TimeZoneDetector { String SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE = "suggest_telephony_time_zone"; /** + * A shell command that enables telephony time zone fallback. See {@link + * com.android.server.timezonedetector.TimeZoneDetectorStrategy} for details. + * @hide + */ + String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback"; + + /** * A shared utility method to create a {@link ManualTimeZoneSuggestion}. * * @hide diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index cf00cbd584a4..20122fb3ca7a 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -51,7 +51,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Attributable; import android.content.AttributionSource; import android.content.Context; -import android.os.BatteryStats; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -59,7 +58,6 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; -import android.os.SynchronousResultReceiver; import android.os.SystemProperties; import android.util.Log; import android.util.Pair; @@ -69,6 +67,7 @@ import com.android.internal.annotations.GuardedBy; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -82,7 +81,6 @@ import java.util.Set; import java.util.UUID; import java.util.WeakHashMap; import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantReadWriteLock; /** @@ -402,6 +400,16 @@ public final class BluetoothAdapter { @Retention(RetentionPolicy.SOURCE) public @interface ScanMode {} + /** @hide */ + @IntDef(value = { + BluetoothStatusCodes.SUCCESS, + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ScanModeStatusCode {} + /** * Indicates that both inquiry scan and page scan are disabled on the local * Bluetooth adapter. Therefore this device is neither discoverable @@ -1618,7 +1626,7 @@ public final class BluetoothAdapter { return mService.getScanMode(mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } @@ -1626,143 +1634,110 @@ public final class BluetoothAdapter { } /** - * Set the Bluetooth scan mode of the local Bluetooth adapter. - * <p>The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - * <p>For privacy reasons, discoverable mode is automatically turned off - * after <code>durationMillis</code> milliseconds. For example, 120000 milliseconds should be - * enough for a remote device to initiate and complete its discovery process. - * <p>Valid scan mode values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. + * Set the local Bluetooth adapter connectablility and discoverability. + * <p>If the scan mode is set to {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, + * it will change to {@link #SCAN_MODE_CONNECTABLE} after the discoverable timeout. + * The discoverable timeout can be set with {@link #setDiscoverableTimeout} and + * checked with {@link #getDiscoverableTimeout}. By default, the timeout is usually + * 120 seconds on phones which is enough for a remote device to initiate and complete + * its discovery process. * <p>Applications cannot set the scan mode. They should use - * <code>startActivityForResult( - * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE}) - * </code>instead. - * - * @param mode valid scan mode - * @param durationMillis time in milliseconds to apply scan mode, only used for {@link - * #SCAN_MODE_CONNECTABLE_DISCOVERABLE} - * @return true if the scan mode was set, false otherwise + * {@link #ACTION_REQUEST_DISCOVERABLE} instead. + * + * @param mode represents the desired state of the local device scan mode + * + * @return status code indicating whether the scan mode was successfully set * @hide */ - @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which " - + "shows UI that confirms the user wants to go into discoverable mode.") - @RequiresLegacyBluetoothPermission + @SystemApi @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean setScanMode(@ScanMode int mode, long durationMillis) { + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @ScanModeStatusCode + public int setScanMode(@ScanMode int mode) { if (getState() != STATE_ON) { - return false; + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } try { mServiceLock.readLock().lock(); if (mService != null) { - int durationSeconds = Math.toIntExact(durationMillis / 1000); - return mService.setScanMode(mode, durationSeconds, mAttributionSource); + return mService.setScanMode(mode, mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); - } catch (ArithmeticException ex) { - Log.e(TAG, "setScanMode: Duration in seconds outside of the bounds of an int"); - throw new IllegalArgumentException("Duration not in bounds. In seconds, the " - + "durationMillis must be in the range of an int"); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } - return false; + return BluetoothStatusCodes.ERROR_UNKNOWN; } /** - * Set the Bluetooth scan mode of the local Bluetooth adapter. - * <p>The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - * <p>For privacy reasons, discoverable mode is automatically turned off - * after <code>duration</code> seconds. For example, 120 seconds should be - * enough for a remote device to initiate and complete its discovery - * process. - * <p>Valid scan mode values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * <p>Applications cannot set the scan mode. They should use - * <code>startActivityForResult( - * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE}) - * </code>instead. + * Get the timeout duration of the {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. * - * @param mode valid scan mode - * @return true if the scan mode was set, false otherwise - * @hide + * @return the duration of the discoverable timeout or null if an error has occurred */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission @RequiresBluetoothScanPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean setScanMode(@ScanMode int mode) { + public @Nullable Duration getDiscoverableTimeout() { if (getState() != STATE_ON) { - return false; + return null; } try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setScanMode(mode, getDiscoverableTimeout(), mAttributionSource); + long timeout = mService.getDiscoverableTimeout(mAttributionSource); + return (timeout == -1) ? null : Duration.ofSeconds(timeout); } } catch (RemoteException e) { - Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } - return false; + return null; } - /** @hide */ - @UnsupportedAppUsage + /** + * Set the total time the Bluetooth local adapter will stay discoverable when + * {@link #setScanMode} is called with {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE} mode. + * After this timeout, the scan mode will fallback to {@link #SCAN_MODE_CONNECTABLE}. + * <p>If <code>timeout</code> is set to 0, no timeout will occur and the scan mode will + * be persisted until a subsequent call to {@link #setScanMode}. + * + * @param timeout represents the total duration the local Bluetooth adapter will remain + * discoverable, or no timeout if set to 0 + * @return whether the timeout was successfully set + * @throws IllegalArgumentException if <code>timeout</code> duration in seconds is more + * than {@link Integer#MAX_VALUE} + * @hide + */ + @SystemApi @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public int getDiscoverableTimeout() { + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @ScanModeStatusCode + public int setDiscoverableTimeout(@NonNull Duration timeout) { if (getState() != STATE_ON) { - return -1; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getDiscoverableTimeout(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } - return -1; - } - - /** @hide */ - @UnsupportedAppUsage - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void setDiscoverableTimeout(int timeout) { - if (getState() != STATE_ON) { - return; + if (timeout.toSeconds() > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Timeout in seconds must be less or equal to " + + Integer.MAX_VALUE); } try { mServiceLock.readLock().lock(); if (mService != null) { - mService.setDiscoverableTimeout(timeout, mAttributionSource); + return mService.setDiscoverableTimeout(timeout.toSeconds(), mAttributionSource); } } catch (RemoteException e) { - Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } finally { mServiceLock.readLock().unlock(); } + return BluetoothStatusCodes.ERROR_UNKNOWN; } /** @@ -2424,38 +2399,6 @@ public final class BluetoothAdapter { } /** - * Return the record of {@link BluetoothActivityEnergyInfo} object that - * has the activity and energy info. This can be used to ascertain what - * the controller has been up to, since the last sample. - * - * @param updateType Type of info, cached vs refreshed. - * @return a record with {@link BluetoothActivityEnergyInfo} or null if report is unavailable or - * unsupported - * @hide - * @deprecated use the asynchronous {@link #requestControllerActivityEnergyInfo(ResultReceiver)} - * instead. - */ - @Deprecated - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) { - SynchronousResultReceiver receiver = new SynchronousResultReceiver(); - requestControllerActivityEnergyInfo(receiver); - try { - SynchronousResultReceiver.Result result = receiver.awaitResult(1000); - if (result.bundle != null) { - return result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY); - } - } catch (TimeoutException e) { - Log.e(TAG, "getControllerActivityEnergyInfo timed out"); - } - return null; - } - - /** * Request the record of {@link BluetoothActivityEnergyInfo} object that * has the activity and energy info. This can be used to ascertain what * the controller has been up to, since the last sample. diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index 7fe18a0704ba..69525b543478 100755 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -17,6 +17,7 @@ package android.bluetooth; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -327,21 +328,26 @@ public final class BluetoothClass implements Parcelable { return Arrays.copyOfRange(bytes, 1, bytes.length); } - /** @hide */ - @UnsupportedAppUsage public static final int PROFILE_HEADSET = 0; - /** @hide */ - @UnsupportedAppUsage + public static final int PROFILE_A2DP = 1; + /** @hide */ + @SystemApi public static final int PROFILE_OPP = 2; - /** @hide */ + public static final int PROFILE_HID = 3; + /** @hide */ + @SystemApi public static final int PROFILE_PANU = 4; + /** @hide */ + @SystemApi public static final int PROFILE_NAP = 5; + /** @hide */ + @SystemApi public static final int PROFILE_A2DP_SINK = 6; /** @@ -350,11 +356,9 @@ public final class BluetoothClass implements Parcelable { * given class bits might support specified profile. It is not accurate for all * devices. It tries to err on the side of false positives. * - * @param profile The profile to be checked - * @return True if this device might support specified profile. - * @hide + * @param profile the profile to be checked + * @return whether this device supports specified profile */ - @UnsupportedAppUsage public boolean doesClassMatch(int profile) { if (profile == PROFILE_A2DP) { if (hasService(Service.RENDER)) { diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 3e799defa5e9..08e0178403f1 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -16,6 +16,8 @@ package android.bluetooth; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; @@ -26,6 +28,8 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -709,33 +713,85 @@ public final class BluetoothGattServer implements BluetoothProfile { * notification * @return true, if the notification has been triggered successfully * @throws IllegalArgumentException + * + * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice, + * BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe. */ + @Deprecated @RequiresLegacyBluetoothPermission @RequiresBluetoothConnectPermission @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm) { + return notifyCharacteristicChanged(device, characteristic, confirm, + characteristic.getValue()) == BluetoothStatusCodes.SUCCESS; + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + BluetoothStatusCodes.SUCCESS, + BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, + BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION, + BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, + BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, + BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED, + BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY, + BluetoothStatusCodes.ERROR_UNKNOWN + }) + public @interface NotifyCharacteristicReturnValues{} + + /** + * Send a notification or indication that a local characteristic has been + * updated. + * + * <p>A notification or indication is sent to the remote device to signal + * that the characteristic has been updated. This function should be invoked + * for every client that requests notifications/indications by writing + * to the "Client Configuration" descriptor for the given characteristic. + * + * @param device the remote device to receive the notification/indication + * @param characteristic the local characteristic that has been updated + * @param confirm {@code true} to request confirmation from the client (indication) or + * {@code false} to send a notification + * @param value the characteristic value + * @return whether the notification has been triggered successfully + * @throws IllegalArgumentException if the characteristic value or service is null + */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @NotifyCharacteristicReturnValues + public int notifyCharacteristicChanged(@NonNull BluetoothDevice device, + @NonNull BluetoothGattCharacteristic characteristic, boolean confirm, + @NonNull byte[] value) { if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) return false; + if (mService == null || mServerIf == 0) { + return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; + } + if (characteristic == null) { + throw new IllegalArgumentException("characteristic must not be null"); + } + if (device == null) { + throw new IllegalArgumentException("device must not be null"); + } BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - if (characteristic.getValue() == null) { - throw new IllegalArgumentException("Chracteristic value is empty. Use " - + "BluetoothGattCharacteristic#setvalue to update"); + if (service == null) { + throw new IllegalArgumentException("Characteristic must have a non-null service"); + } + if (value == null) { + throw new IllegalArgumentException("Characteristic value must not be null"); } try { - mService.sendNotification(mServerIf, device.getAddress(), + return mService.sendNotification(mServerIf, device.getAddress(), characteristic.getInstanceId(), confirm, - characteristic.getValue(), mAttributionSource); + value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); - return false; + throw e.rethrowFromSystemServer(); } - - return true; } /** diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index 1655b62bbfec..db5b75148e88 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -18,7 +18,6 @@ package android.bluetooth; import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.compat.annotation.UnsupportedAppUsage; import android.net.LocalSocket; @@ -266,7 +265,7 @@ public final class BluetoothSocket implements Closeable { throw new IOException("bt socket acept failed"); } - as.mPfd = new ParcelFileDescriptor(fds[0]); + as.mPfd = ParcelFileDescriptor.dup(fds[0]); as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]); as.mSocketIS = as.mSocket.getInputStream(); as.mSocketOS = as.mSocket.getOutputStream(); diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java index ca01784efd88..5ba7bb110449 100644 --- a/core/java/android/bluetooth/BluetoothStatusCodes.java +++ b/core/java/android/bluetooth/BluetoothStatusCodes.java @@ -40,7 +40,7 @@ public final class BluetoothStatusCodes { /** * Error code indicating that the API call was initiated by neither the system nor the active - * Zuser + * user */ public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index ab1eb1f31fc9..3f02aa28ae05 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -15,29 +15,24 @@ */ package android.companion; -import static android.companion.DeviceId.TYPE_MAC_ADDRESS; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.UserIdInt; +import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; -import java.util.ArrayList; -import java.util.Collections; import java.util.Date; -import java.util.HashSet; -import java.util.List; import java.util.Objects; -import java.util.Set; /** - * A record indicating that a device with a given address was confirmed by the user to be - * associated to a given companion app - * - * @hide - * TODO(b/1979395): un-hide and rename to AssociationInfo when implementing public APIs that use - * this class. + * Details for a specific "association" that has been established between an app and companion + * device. + * <p> + * An association gives an app the ability to interact with a companion device without needing to + * acquire broader runtime permissions. An association only exists after the user has confirmed that + * an app should have access to a companion device. */ public final class AssociationInfo implements Parcelable { /** @@ -45,15 +40,16 @@ public final class AssociationInfo implements Parcelable { * Disclosed to the clients (ie. companion applications) for referring to this record (eg. in * {@code disassociate()} API call). */ - private final int mAssociationId; + private final int mId; private final @UserIdInt int mUserId; private final @NonNull String mPackageName; - private final @NonNull List<DeviceId> mDeviceIds; + private final @Nullable MacAddress mDeviceMacAddress; + private final @Nullable CharSequence mDisplayName; private final @Nullable String mDeviceProfile; - private final boolean mManagedByCompanionApp; + private final boolean mSelfManaged; private boolean mNotifyOnDeviceNearby; private final long mTimeApprovedMs; @@ -63,23 +59,28 @@ public final class AssociationInfo implements Parcelable { * * @hide */ - public AssociationInfo(int associationId, @UserIdInt int userId, @NonNull String packageName, - @NonNull List<DeviceId> deviceIds, @Nullable String deviceProfile, - boolean managedByCompanionApp, boolean notifyOnDeviceNearby, long timeApprovedMs) { - if (associationId <= 0) { + public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, + @Nullable MacAddress macAddress, @Nullable CharSequence displayName, + @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, + long timeApprovedMs) { + if (id <= 0) { throw new IllegalArgumentException("Association ID should be greater than 0"); } - validateDeviceIds(deviceIds); + if (macAddress == null && displayName == null) { + throw new IllegalArgumentException("MAC address and the Display Name must NOT be null " + + "at the same time"); + } - mAssociationId = associationId; + mId = id; mUserId = userId; mPackageName = packageName; + mDeviceMacAddress = macAddress; + mDisplayName = displayName; mDeviceProfile = deviceProfile; - mDeviceIds = new ArrayList<>(deviceIds); - mManagedByCompanionApp = managedByCompanionApp; + mSelfManaged = selfManaged; mNotifyOnDeviceNearby = notifyOnDeviceNearby; mTimeApprovedMs = timeApprovedMs; } @@ -87,55 +88,66 @@ public final class AssociationInfo implements Parcelable { /** * @return the unique ID of this association record. */ - public int getAssociationId() { - return mAssociationId; + public int getId() { + return mId; } - /** @hide */ - public int getUserId() { + /** + * @return the ID of the user who "owns" this association. + * @hide + */ + public @UserIdInt int getUserId() { return mUserId; } - /** @hide */ + /** + * @return the package name of the app which this association refers to. + * @hide + */ + @SystemApi public @NonNull String getPackageName() { return mPackageName; } /** - * @return list of the device's IDs. At any time a device has at least 1 ID. + * @return the MAC address of the device. */ - public @NonNull List<DeviceId> getDeviceIds() { - return Collections.unmodifiableList(mDeviceIds); + public @Nullable MacAddress getDeviceMacAddress() { + return mDeviceMacAddress; } - /** - * @param type type of the ID. - * @return ID of the type if the device has such ID, {@code null} otherwise. - */ - public @Nullable String getIdOfType(@NonNull String type) { - for (int i = mDeviceIds.size() - 1; i >= 0; i--) { - final DeviceId id = mDeviceIds.get(i); - if (Objects.equals(mDeviceIds.get(i).getType(), type)) return id.getValue(); - } - return null; + /** @hide */ + public @Nullable String getDeviceMacAddressAsString() { + return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null; } - /** @hide */ - public @NonNull String getDeviceMacAddress() { - return Objects.requireNonNull(getIdOfType(TYPE_MAC_ADDRESS), - "MAC address of this device is not specified."); + /** + * @return the display name of the companion device (optionally) provided by the companion + * application. + * + * @see AssociationRequest.Builder#setDisplayName(CharSequence) + */ + public @Nullable CharSequence getDisplayName() { + return mDisplayName; } /** - * @return the profile of the device. + * @return the companion device profile used when establishing this + * association, or {@code null} if no specific profile was used. + * @see AssociationRequest.Builder#setDeviceProfile(String) */ public @Nullable String getDeviceProfile() { return mDeviceProfile; } - /** @hide */ - public boolean isManagedByCompanionApp() { - return mManagedByCompanionApp; + /** + * @return whether the association is managed by the companion application it belongs to. + * @see AssociationRequest.Builder#setSelfManaged(boolean) + * @hide + */ + @SystemApi + public boolean isSelfManaged() { + return mSelfManaged; } /** @@ -161,15 +173,40 @@ public final class AssociationInfo implements Parcelable { return mUserId == userId && Objects.equals(mPackageName, packageName); } + /** + * Utility method for checking if the association represents a device with the given MAC + * address. + * + * @return {@code false} if the association is "self-managed". + * {@code false} if the {@code addr} is {@code null} or is not a valid MAC address. + * Otherwise - the result of {@link MacAddress#equals(Object)} + * + * @hide + */ + public boolean isLinkedTo(@Nullable String addr) { + if (mSelfManaged) return false; + + if (addr == null) return false; + + final MacAddress macAddress; + try { + macAddress = MacAddress.fromString(addr); + } catch (IllegalArgumentException e) { + return false; + } + return macAddress.equals(mDeviceMacAddress); + } + @Override public String toString() { return "Association{" - + "mAssociationId=" + mAssociationId + + "mId=" + mId + ", mUserId=" + mUserId + ", mPackageName='" + mPackageName + '\'' - + ", mDeviceIds=" + mDeviceIds + + ", mDeviceMacAddress=" + mDeviceMacAddress + + ", mDisplayName='" + mDisplayName + '\'' + ", mDeviceProfile='" + mDeviceProfile + '\'' - + ", mManagedByCompanionApp=" + mManagedByCompanionApp + + ", mSelfManaged=" + mSelfManaged + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) + '}'; @@ -180,20 +217,21 @@ public final class AssociationInfo implements Parcelable { if (this == o) return true; if (!(o instanceof AssociationInfo)) return false; final AssociationInfo that = (AssociationInfo) o; - return mAssociationId == that.mAssociationId + return mId == that.mId && mUserId == that.mUserId - && mManagedByCompanionApp == that.mManagedByCompanionApp + && mSelfManaged == that.mSelfManaged && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby && mTimeApprovedMs == that.mTimeApprovedMs && Objects.equals(mPackageName, that.mPackageName) - && Objects.equals(mDeviceProfile, that.mDeviceProfile) - && Objects.equals(mDeviceIds, that.mDeviceIds); + && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) + && Objects.equals(mDisplayName, that.mDisplayName) + && Objects.equals(mDeviceProfile, that.mDeviceProfile); } @Override public int hashCode() { - return Objects.hash(mAssociationId, mUserId, mPackageName, mDeviceIds, mDeviceProfile, - mManagedByCompanionApp, mNotifyOnDeviceNearby, mTimeApprovedMs); + return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, + mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs); } @Override @@ -203,33 +241,36 @@ public final class AssociationInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mAssociationId); + dest.writeInt(mId); dest.writeInt(mUserId); dest.writeString(mPackageName); - dest.writeParcelableList(mDeviceIds, 0); + dest.writeTypedObject(mDeviceMacAddress, 0); + dest.writeCharSequence(mDisplayName); dest.writeString(mDeviceProfile); - dest.writeBoolean(mManagedByCompanionApp); + dest.writeBoolean(mSelfManaged); dest.writeBoolean(mNotifyOnDeviceNearby); dest.writeLong(mTimeApprovedMs); } private AssociationInfo(@NonNull Parcel in) { - mAssociationId = in.readInt(); + mId = in.readInt(); mUserId = in.readInt(); mPackageName = in.readString(); - mDeviceIds = in.readParcelableList(new ArrayList<>(), DeviceId.class.getClassLoader()); + mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR); + mDisplayName = in.readCharSequence(); mDeviceProfile = in.readString(); - mManagedByCompanionApp = in.readBoolean(); + mSelfManaged = in.readBoolean(); mNotifyOnDeviceNearby = in.readBoolean(); mTimeApprovedMs = in.readLong(); } + @NonNull public static final Parcelable.Creator<AssociationInfo> CREATOR = new Parcelable.Creator<AssociationInfo>() { @Override @@ -242,19 +283,4 @@ public final class AssociationInfo implements Parcelable { return new AssociationInfo(in); } }; - - private static void validateDeviceIds(@NonNull List<DeviceId> ids) { - if (ids.isEmpty()) throw new IllegalArgumentException("Device must have at least 1 id."); - - // Make sure none of the IDs are null, and they all have different types. - final Set<String> types = new HashSet<>(ids.size()); - for (int i = ids.size() - 1; i >= 0; i--) { - final DeviceId deviceId = ids.get(i); - if (deviceId == null) throw new IllegalArgumentException("DeviceId must not be null"); - if (!types.add(deviceId.getType())) { - throw new IllegalArgumentException( - "DeviceId cannot have multiple IDs of the same type"); - } - } - } } diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 7d1aabc639d9..1dc161cb3c22 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -16,6 +16,8 @@ package android.companion; +import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; + import static com.android.internal.util.CollectionUtils.emptyIfNull; import android.Manifest; @@ -58,9 +60,6 @@ import java.util.Objects; genBuilder = false, genConstDefs = false) public final class AssociationRequest implements Parcelable { - - private static final String LOG_TAG = AssociationRequest.class.getSimpleName(); - /** * Device profile: watch. * @@ -116,7 +115,7 @@ public final class AssociationRequest implements Parcelable { /** * Whether only a single device should match the provided filter. * - * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac + * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac * address, bonded devices are also searched among. This allows to obtain the necessary app * privileges even if the device is already paired. */ @@ -134,6 +133,24 @@ public final class AssociationRequest implements Parcelable { private @Nullable @DeviceProfile String mDeviceProfile = null; /** + * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for + * "self-managed" association. + */ + private final @Nullable CharSequence mDisplayName; + + /** + * Whether the association is to be managed by the companion application. + */ + private final boolean mSelfManaged; + + /** + * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit + * confirmation from the user before creating an association, even if such confirmation is not + * required. + */ + private final boolean mForceConfirmation; + + /** * The app package making the request. * * Populated by the system. @@ -167,8 +184,30 @@ public final class AssociationRequest implements Parcelable { */ private boolean mSkipPrompt = false; - private void onConstructed() { - mCreationTime = System.currentTimeMillis(); + /** + * Whether the association is to be managed by the companion application. + * + * @see Builder#setSelfManaged(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) + public boolean isSelfManaged() { + return mSelfManaged; + } + + /** + * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit + * confirmation from the user before creating an association, even if such confirmation is not + * required. + * + * @see Builder#setForceConfirmation(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) + public boolean isForceConfirmation() { + return mForceConfirmation; } /** @hide */ @@ -199,20 +238,27 @@ public final class AssociationRequest implements Parcelable { return mDeviceFilters; } + private void onConstructed() { + mCreationTime = System.currentTimeMillis(); + } + /** * A builder for {@link AssociationRequest} */ public static final class Builder extends OneTimeUseBuilder<AssociationRequest> { private boolean mSingleDevice = false; - @Nullable private ArrayList<DeviceFilter<?>> mDeviceFilters = null; - private @Nullable String mDeviceProfile = null; + private @Nullable ArrayList<DeviceFilter<?>> mDeviceFilters = null; + private @Nullable String mDeviceProfile; + private @Nullable CharSequence mDisplayName; + private boolean mSelfManaged = false; + private boolean mForceConfirmation = false; public Builder() {} /** * Whether only a single device should match the provided filter. * - * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac + * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac * address, bonded devices are also searched among. This allows to obtain the necessary app * privileges even if the device is already paired. * @@ -249,14 +295,65 @@ public final class AssociationRequest implements Parcelable { return this; } + /** + * Adds a display name. + * Generally {@link AssociationRequest}s are not required to provide a display name, except + * for request for creating "self-managed" associations, which MUST provide a display name. + * + * @param displayName the display name of the device. + */ + @NonNull + public Builder setDisplayName(@NonNull CharSequence displayName) { + checkNotUsed(); + mDisplayName = Objects.requireNonNull(displayName); + return this; + } + + /** + * Indicate whether the association would be managed by the companion application. + * + * Requests for creating "self-managed" association MUST provide a Display name. + * + * @see #setDisplayName(CharSequence) + * @hide + */ + @SystemApi + @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) + @NonNull + public Builder setSelfManaged(boolean selfManaged) { + checkNotUsed(); + mSelfManaged = selfManaged; + return this; + } + + /** + * Indicates whether the application would prefer the CompanionDeviceManager to collect an + * explicit confirmation from the user before creating an association, even if such + * confirmation is not required. + * + * @hide + */ + @SystemApi + @RequiresPermission(REQUEST_COMPANION_SELF_MANAGED) + @NonNull + public Builder setForceConfirmation(boolean forceConfirmation) { + checkNotUsed(); + mForceConfirmation = forceConfirmation; + return this; + } + /** @inheritDoc */ @NonNull @Override public AssociationRequest build() { markUsed(); - return new AssociationRequest( - mSingleDevice, emptyIfNull(mDeviceFilters), - mDeviceProfile, null, null, -1L, false); + if (mSelfManaged && mDisplayName == null) { + throw new IllegalStateException("Request for a self-managed association MUST " + + "provide the display name of the device"); + } + return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters), + mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, + null, null, -1L, false); } } @@ -283,13 +380,22 @@ public final class AssociationRequest implements Parcelable { * @param singleDevice * Whether only a single device should match the provided filter. * - * When scanning for a single device with a specifc {@link BluetoothDeviceFilter} mac + * When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac * address, bonded devices are also searched among. This allows to obtain the necessary app * privileges even if the device is already paired. * @param deviceFilters * If set, only devices matching either of the given filters will be shown to the user * @param deviceProfile * If set, association will be requested as a corresponding kind of device + * @param displayName + * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for + * "self-managed" association. + * @param selfManaged + * Whether the association is to be managed by the companion application. + * @param forceConfirmation + * Indicates that the application would prefer the CompanionDeviceManager to collect an explicit + * confirmation from the user before creating an association, even if such confirmation is not + * required. * @param callingPackage * The app package making the request. * @@ -311,6 +417,9 @@ public final class AssociationRequest implements Parcelable { boolean singleDevice, @NonNull List<DeviceFilter<?>> deviceFilters, @Nullable @DeviceProfile String deviceProfile, + @Nullable CharSequence displayName, + boolean selfManaged, + boolean forceConfirmation, @Nullable String callingPackage, @Nullable String deviceProfilePrivilegesDescription, long creationTime, @@ -322,6 +431,9 @@ public final class AssociationRequest implements Parcelable { this.mDeviceProfile = deviceProfile; com.android.internal.util.AnnotationValidations.validate( DeviceProfile.class, null, mDeviceProfile); + this.mDisplayName = displayName; + this.mSelfManaged = selfManaged; + this.mForceConfirmation = forceConfirmation; this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; @@ -341,6 +453,17 @@ public final class AssociationRequest implements Parcelable { } /** + * The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for + * "self-managed" association. + * + * @hide + */ + @DataClass.Generated.Member + public @Nullable CharSequence getDisplayName() { + return mDisplayName; + } + + /** * The app package making the request. * * Populated by the system. @@ -396,6 +519,9 @@ public final class AssociationRequest implements Parcelable { "singleDevice = " + mSingleDevice + ", " + "deviceFilters = " + mDeviceFilters + ", " + "deviceProfile = " + mDeviceProfile + ", " + + "displayName = " + mDisplayName + ", " + + "selfManaged = " + mSelfManaged + ", " + + "forceConfirmation = " + mForceConfirmation + ", " + "callingPackage = " + mCallingPackage + ", " + "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " + "creationTime = " + mCreationTime + ", " + @@ -419,6 +545,9 @@ public final class AssociationRequest implements Parcelable { && mSingleDevice == that.mSingleDevice && Objects.equals(mDeviceFilters, that.mDeviceFilters) && Objects.equals(mDeviceProfile, that.mDeviceProfile) + && Objects.equals(mDisplayName, that.mDisplayName) + && mSelfManaged == that.mSelfManaged + && mForceConfirmation == that.mForceConfirmation && Objects.equals(mCallingPackage, that.mCallingPackage) && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription) && mCreationTime == that.mCreationTime @@ -435,6 +564,9 @@ public final class AssociationRequest implements Parcelable { _hash = 31 * _hash + Boolean.hashCode(mSingleDevice); _hash = 31 * _hash + Objects.hashCode(mDeviceFilters); _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); + _hash = 31 * _hash + Objects.hashCode(mDisplayName); + _hash = 31 * _hash + Boolean.hashCode(mSelfManaged); + _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation); _hash = 31 * _hash + Objects.hashCode(mCallingPackage); _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription); _hash = 31 * _hash + Long.hashCode(mCreationTime); @@ -448,15 +580,19 @@ public final class AssociationRequest implements Parcelable { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - byte flg = 0; + int flg = 0; if (mSingleDevice) flg |= 0x1; - if (mSkipPrompt) flg |= 0x40; + if (mSelfManaged) flg |= 0x10; + if (mForceConfirmation) flg |= 0x20; + if (mSkipPrompt) flg |= 0x200; if (mDeviceProfile != null) flg |= 0x4; - if (mCallingPackage != null) flg |= 0x8; - if (mDeviceProfilePrivilegesDescription != null) flg |= 0x10; - dest.writeByte(flg); + if (mDisplayName != null) flg |= 0x8; + if (mCallingPackage != null) flg |= 0x40; + if (mDeviceProfilePrivilegesDescription != null) flg |= 0x80; + dest.writeInt(flg); dest.writeParcelableList(mDeviceFilters, flags); if (mDeviceProfile != null) dest.writeString(mDeviceProfile); + if (mDisplayName != null) dest.writeCharSequence(mDisplayName); if (mCallingPackage != null) dest.writeString(mCallingPackage); if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription); dest.writeLong(mCreationTime); @@ -473,14 +609,17 @@ public final class AssociationRequest implements Parcelable { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - byte flg = in.readByte(); + int flg = in.readInt(); boolean singleDevice = (flg & 0x1) != 0; - boolean skipPrompt = (flg & 0x40) != 0; + boolean selfManaged = (flg & 0x10) != 0; + boolean forceConfirmation = (flg & 0x20) != 0; + boolean skipPrompt = (flg & 0x200) != 0; List<DeviceFilter<?>> deviceFilters = new ArrayList<>(); in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader()); String deviceProfile = (flg & 0x4) == 0 ? null : in.readString(); - String callingPackage = (flg & 0x8) == 0 ? null : in.readString(); - String deviceProfilePrivilegesDescription = (flg & 0x10) == 0 ? null : in.readString(); + CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence(); + String callingPackage = (flg & 0x40) == 0 ? null : in.readString(); + String deviceProfilePrivilegesDescription = (flg & 0x80) == 0 ? null : in.readString(); long creationTime = in.readLong(); this.mSingleDevice = singleDevice; @@ -490,6 +629,9 @@ public final class AssociationRequest implements Parcelable { this.mDeviceProfile = deviceProfile; com.android.internal.util.AnnotationValidations.validate( DeviceProfile.class, null, mDeviceProfile); + this.mDisplayName = displayName; + this.mSelfManaged = selfManaged; + this.mForceConfirmation = forceConfirmation; this.mCallingPackage = callingPackage; this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription; this.mCreationTime = creationTime; @@ -513,10 +655,10 @@ public final class AssociationRequest implements Parcelable { }; @DataClass.Generated( - time = 1635190605212L, + time = 1637228802427L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", - inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)") + inputSignatures = "public static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final boolean mSelfManaged\nprivate final boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate boolean mSelfManaged\nprivate boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 6719a6934a3a..2b12f12a8ec0 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -16,23 +16,26 @@ package android.companion; -import android.Manifest; +import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING; +import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION; +import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.app.Activity; -import android.app.Application; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.net.MacAddress; -import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -40,10 +43,16 @@ import android.service.notification.NotificationListenerService; import android.util.ExceptionUtils; import android.util.Log; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.CollectionUtils; + +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.function.BiConsumer; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * System level service for managing companion devices @@ -75,10 +84,21 @@ public final class CompanionDeviceManager { * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> * </ul> + * + * @deprecated use {@link #EXTRA_ASSOCIATION} instead. */ + @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; /** + * Extra field name for the {@link AssociationInfo} object, included into + * {@link android.content.Intent} which application receive in + * {@link Activity#onActivityResult(int, int, Intent)} after the application's + * {@link AssociationRequest} was successfully processed and an association was created. + */ + public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"; + + /** * The package name of the companion device discovery component. * * @hide @@ -87,30 +107,121 @@ public final class CompanionDeviceManager { "com.android.companiondevicemanager"; /** - * A callback to receive once at least one suitable device is found, or the search failed - * (e.g. timed out) + * Callback for applications to receive updates about and the outcome of + * {@link AssociationRequest} issued via {@code associate()} call. + * + * <p> + * The {@link Callback#onAssociationPending(IntentSender)} is invoked after the + * {@link AssociationRequest} has been checked by the Companion Device Manager Service and is + * pending user's approval. + * + * The {@link IntentSender} received as an argument to + * {@link Callback#onAssociationPending(IntentSender)} "encapsulates" an {@link Activity} + * that has UI for the user to: + * <ul> + * <li> + * choose the device to associate the application with (if multiple eligible devices are + * available) + * </li> + * <li>confirm the association</li> + * <li> + * approve the privileges the application will be granted if the association is to be created + * </li> + * </ul> + * + * If the Companion Device Manager Service needs to scan for the devices, the {@link Activity} + * will also display the status and the progress of the scan. + * + * Note that Companion Device Manager Service will only start the scanning after the + * {@link Activity} was launched and became visible. + * + * Applications are expected to launch the UI using the received {@link IntentSender} via + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}. + * </p> + * + * <p> + * Upon receiving user's confirmation Companion Device Manager Service will create an + * association and will send an {@link AssociationInfo} object that represents the created + * association back to the application both via + * {@link Callback#onAssociationCreated(AssociationInfo)} and + * via {@link Activity#setResult(int, Intent)}. + * In the latter the {@code resultCode} will be set to {@link Activity#RESULT_OK} and the + * {@code data} {@link Intent} will contain {@link AssociationInfo} extra named + * {@link #EXTRA_ASSOCIATION}. + * <pre> + * <code> + * if (resultCode == Activity.RESULT_OK) { + * AssociationInfo associationInfo = data.getParcelableExtra(EXTRA_ASSOCIATION); + * } + * </code> + * </pre> + * </p> + * + * <p> + * If the Companion Device Manager Service is not able to create an association, it will + * invoke {@link Callback#onFailure(CharSequence)}. + * + * If this happened after the application has launched the UI (eg. the user chose to reject + * the association), the outcome will also be delivered to the applications via + * {@link Activity#setResult(int)} with the {@link Activity#RESULT_CANCELED} + * {@code resultCode}. + * </p> + * + * <p> + * Note that in some cases the Companion Device Manager Service may not need to collect + * user's approval for creating an association. In such cases, this method will not be + * invoked, and {@link #onAssociationCreated(AssociationInfo)} may be invoked right away. + * </p> + * + * @see #associate(AssociationRequest, Executor, Callback) + * @see #associate(AssociationRequest, Callback, Handler) + * @see #EXTRA_ASSOCIATION */ public abstract static class Callback { + /** + * @deprecated method was renamed to onAssociationPending() to provide better clarity; both + * methods are functionally equivalent and only one needs to be overridden. + * + * @see #onAssociationPending(IntentSender) + */ + @Deprecated + public void onDeviceFound(@NonNull IntentSender intentSender) {} /** - * Called once at least one suitable device is found + * Invoked when the association needs to approved by the user. + * + * Applications should launch the {@link Activity} "encapsulated" in {@code intentSender} + * {@link IntentSender} object by calling + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}. * - * @param chooserLauncher a {@link IntentSender} to launch the UI for user to select a - * device + * @param intentSender an {@link IntentSender} which applications should use to launch + * the UI for the user to confirm the association. */ - public abstract void onDeviceFound(IntentSender chooserLauncher); + public void onAssociationPending(@NonNull IntentSender intentSender) { + onDeviceFound(intentSender); + } /** - * Called if there was an error looking for device(s) + * Invoked when the association is created. * - * @param error the cause of the error + * @param associationInfo contains details of the newly-established association. */ - public abstract void onFailure(CharSequence error); + public void onAssociationCreated(@NonNull AssociationInfo associationInfo) {} + + /** + * Invoked if the association could not be created. + * + * @param error error message. + */ + public abstract void onFailure(@Nullable CharSequence error); } private final ICompanionDeviceManager mService; private Context mContext; + @GuardedBy("mListeners") + private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>(); + /** @hide */ public CompanionDeviceManager( @Nullable ICompanionDeviceManager service, @NonNull Context context) { @@ -119,59 +230,109 @@ public final class CompanionDeviceManager { } /** - * Associate this app with a companion device, selected by user - * - * <p>Once at least one appropriate device is found, {@code callback} will be called with a - * {@link PendingIntent} that can be used to show the list of available devices for the user - * to select. - * It should be started for result (i.e. using - * {@link android.app.Activity#startIntentSenderForResult}), as the resulting - * {@link android.content.Intent} will contain extra {@link #EXTRA_DEVICE}, with the selected - * device. (e.g. {@link android.bluetooth.BluetoothDevice})</p> - * - * <p>If your app needs to be excluded from battery optimizations (run in the background) - * or to have unrestricted data access (use data in the background) you can declare that - * you use the {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and {@link - * android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} respectively. Note that these - * special capabilities have a negative effect on the device's battery and user's data - * usage, therefore you should request them when absolutely necessary.</p> - * - * <p>You can call {@link #getAssociations} to get the list of currently associated - * devices, and {@link #disassociate} to remove an association. Consider doing so when the - * association is no longer relevant to avoid unnecessary battery and/or data drain resulting - * from special privileges that the association provides</p> + * Request to associate this app with a companion device. * - * <p>Calling this API requires a uses-feature - * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> + * <p>Note that before creating establishing association the system may need to show UI to + * collect user confirmation.</p> * - * <p>When using {@link AssociationRequest#DEVICE_PROFILE_WATCH watch} - * {@link AssociationRequest.Builder#setDeviceProfile profile}, caller must also hold - * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH}</p> + * <p>If the app needs to be excluded from battery optimizations (run in the background) + * or to have unrestricted data access (use data in the background) it should declare use of + * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and + * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its + * AndroidManifest.xml respectively. + * Note that these special capabilities have a negative effect on the device's battery and + * user's data usage, therefore you should request them when absolutely necessary.</p> * - * @param request specific details about this request - * @param callback will be called once there's at least one device found for user to choose from - * @param handler A handler to control which thread the callback will be delivered on, or null, - * to deliver it on main thread + * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently + * {@link AssociationInfo} objects, that represent their existing associations. + * Applications can also use {@link #disassociate(int)} to remove an association, and are + * recommended to do when an association is no longer relevant to avoid unnecessary battery + * and/or data drain resulting from special privileges that the association provides</p> * - * @see AssociationRequest + * <p>Calling this API requires a uses-feature + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> + ** + * @param request A request object that describes details of the request. + * @param callback The callback used to notify application when the association is created. + * @param handler The handler which will be used to invoke the callback. + * + * @see AssociationRequest.Builder + * @see #getMyAssociations() + * @see #disassociate(int) + * @see #associate(AssociationRequest, Executor, Callback) */ - @RequiresPermission( - value = Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, - conditional = true) + @UserHandleAware + @RequiresPermission(anyOf = { + REQUEST_COMPANION_PROFILE_WATCH, + REQUEST_COMPANION_PROFILE_APP_STREAMING, + REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION, + }, conditional = true) public void associate( @NonNull AssociationRequest request, @NonNull Callback callback, @Nullable Handler handler) { - if (!checkFeaturePresent()) { - return; + if (!checkFeaturePresent()) return; + Objects.requireNonNull(request, "Request cannot be null"); + Objects.requireNonNull(callback, "Callback cannot be null"); + handler = Handler.mainIfNull(handler); + + try { + mService.associate(request, new AssociationRequestCallbackProxy(handler, callback), + mContext.getOpPackageName(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } + } + + /** + * Request to associate this app with a companion device. + * + * <p>Note that before creating establishing association the system may need to show UI to + * collect user confirmation.</p> + * + * <p>If the app needs to be excluded from battery optimizations (run in the background) + * or to have unrestricted data access (use data in the background) it should declare use of + * {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and + * {@link android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} in its + * AndroidManifest.xml respectively. + * Note that these special capabilities have a negative effect on the device's battery and + * user's data usage, therefore you should request them when absolutely necessary.</p> + * + * <p>Application can use {@link #getMyAssociations()} for retrieving the list of currently + * {@link AssociationInfo} objects, that represent their existing associations. + * Applications can also use {@link #disassociate(int)} to remove an association, and are + * recommended to do when an association is no longer relevant to avoid unnecessary battery + * and/or data drain resulting from special privileges that the association provides</p> + * + * <p>Calling this API requires a uses-feature + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> + ** + * @param request A request object that describes details of the request. + * @param executor The executor which will be used to invoke the callback. + * @param callback The callback used to notify application when the association is created. + * + * @see AssociationRequest.Builder + * @see #getMyAssociations() + * @see #disassociate(int) + */ + @UserHandleAware + @RequiresPermission(anyOf = { + REQUEST_COMPANION_PROFILE_WATCH, + REQUEST_COMPANION_PROFILE_APP_STREAMING, + REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION + }, conditional = true) + public void associate( + @NonNull AssociationRequest request, + @NonNull Executor executor, + @NonNull Callback callback) { + if (!checkFeaturePresent()) return; Objects.requireNonNull(request, "Request cannot be null"); + Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(callback, "Callback cannot be null"); + try { - mService.associate( - request, - new CallbackProxy(request, callback, Handler.mainIfNull(handler)), - getCallingPackage()); + mService.associate(request, new AssociationRequestCallbackProxy(executor, callback), + mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -182,15 +343,32 @@ public final class CompanionDeviceManager { * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> * * @return a list of MAC addresses of devices that have been previously associated with the - * current app. You can use these with {@link #disassociate} + * current app are managed by CompanionDeviceManager (ie. does not include devices managed by + * application itself even if they have a MAC address). + * + * @deprecated use {@link #getMyAssociations()} */ + @Deprecated + @UserHandleAware @NonNull public List<String> getAssociations() { - if (!checkFeaturePresent()) { - return Collections.emptyList(); - } + return CollectionUtils.mapNotNull(getMyAssociations(), + a -> a.isSelfManaged() ? null : a.getDeviceMacAddressAsString()); + } + + /** + * <p>Calling this API requires a uses-feature + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> + * + * @return a list of associations that have been previously associated with the current app. + */ + @UserHandleAware + @NonNull + public List<AssociationInfo> getMyAssociations() { + if (!checkFeaturePresent()) return Collections.emptyList(); + try { - return mService.getAssociations(getCallingPackage(), mContext.getUserId()); + return mService.getAssociations(mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -209,13 +387,41 @@ public final class CompanionDeviceManager { * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> * * @param deviceMacAddress the MAC address of device to disassociate from this app + * + * @deprecated use {@link #disassociate(int)} */ + @UserHandleAware + @Deprecated public void disassociate(@NonNull String deviceMacAddress) { - if (!checkFeaturePresent()) { - return; + if (!checkFeaturePresent()) return; + + try { + mService.legacyDisassociate(deviceMacAddress, mContext.getOpPackageName(), + mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } + } + + /** + * Remove an association. + * + * <p>Any privileges provided via being associated with a given device will be revoked</p> + * + * <p>Calling this API requires a uses-feature + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> + * + * @param associationId id of the association to be removed. + * + * @see #associate(AssociationRequest, Executor, Callback) + * @see AssociationInfo#getId() + */ + @UserHandleAware + public void disassociate(int associationId) { + if (!checkFeaturePresent()) return; + try { - mService.disassociate(deviceMacAddress, getCallingPackage()); + mService.disassociate(associationId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -234,12 +440,14 @@ public final class CompanionDeviceManager { * <p>Calling this API requires a uses-feature * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p> */ + @UserHandleAware public void requestNotificationAccess(ComponentName component) { if (!checkFeaturePresent()) { return; } try { - IntentSender intentSender = mService.requestNotificationAccess(component) + IntentSender intentSender = mService + .requestNotificationAccess(component, mContext.getUserId()) .getIntentSender(); mContext.startIntentSender(intentSender, null, 0, 0, 0); } catch (RemoteException e) { @@ -304,9 +512,7 @@ public final class CompanionDeviceManager { @NonNull String packageName, @NonNull MacAddress macAddress, @NonNull UserHandle user) { - if (!checkFeaturePresent()) { - return false; - } + if (!checkFeaturePresent()) return false; Objects.requireNonNull(packageName, "package name cannot be null"); Objects.requireNonNull(macAddress, "mac address cannot be null"); Objects.requireNonNull(user, "user cannot be null"); @@ -322,21 +528,91 @@ public final class CompanionDeviceManager { * Gets all package-device {@link AssociationInfo}s for the current user. * * @return the associations list + * @see #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener) + * @see #removeOnAssociationsChangedListener(OnAssociationsChangedListener) * @hide */ + @SystemApi + @UserHandleAware @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public @NonNull List<AssociationInfo> getAllAssociations() { - if (!checkFeaturePresent()) { - return Collections.emptyList(); - } + if (!checkFeaturePresent()) return Collections.emptyList(); try { - return mService.getAssociationsForUser(mContext.getUser().getIdentifier()); + return mService.getAllAssociationsForUser(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Listener for any changes to {@link AssociationInfo}. + * + * @hide + */ + @SystemApi + public interface OnAssociationsChangedListener { + /** + * Invoked when a change occurs to any of the associations for the user (including adding + * new associations and removing existing associations). + * + * @param associations all existing associations for the user (after the change). + */ + void onAssociationsChanged(@NonNull List<AssociationInfo> associations); + } + + /** + * Register listener for any changes to {@link AssociationInfo}. + * + * @see #getAllAssociations() + * @hide + */ + @SystemApi + @UserHandleAware + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public void addOnAssociationsChangedListener( + @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) { + if (!checkFeaturePresent()) return; + synchronized (mListeners) { + final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy( + executor, listener); + try { + mService.addOnAssociationsChangedListener(proxy, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mListeners.add(proxy); + } + } + + /** + * Unregister listener for any changes to {@link AssociationInfo}. + * + * @see #getAllAssociations() + * @hide + */ + @SystemApi + @UserHandleAware + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public void removeOnAssociationsChangedListener( + @NonNull OnAssociationsChangedListener listener) { + if (!checkFeaturePresent()) return; + synchronized (mListeners) { + final Iterator<OnAssociationsChangedListenerProxy> iterator = mListeners.iterator(); + while (iterator.hasNext()) { + final OnAssociationsChangedListenerProxy proxy = iterator.next(); + if (proxy.mListener == listener) { + try { + mService.removeOnAssociationsChangedListener(proxy, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + iterator.remove(); + } + } + } + } + + /** * Checks whether the bluetooth device represented by the mac address was recently associated * with the companion app. This allows these devices to skip the Bluetooth pairing dialog if * their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}. @@ -404,8 +680,8 @@ public final class CompanionDeviceManager { } Objects.requireNonNull(deviceAddress, "address cannot be null"); try { - mService.registerDevicePresenceListenerService( - mContext.getPackageName(), deviceAddress); + mService.registerDevicePresenceListenerService(deviceAddress, + mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); throw e.rethrowFromSystemServer(); @@ -437,8 +713,8 @@ public final class CompanionDeviceManager { } Objects.requireNonNull(deviceAddress, "address cannot be null"); try { - mService.unregisterDevicePresenceListenerService( - mContext.getPackageName(), deviceAddress); + mService.unregisterDevicePresenceListenerService(deviceAddress, + mContext.getPackageName(), mContext.getUserId()); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); } @@ -509,78 +785,63 @@ public final class CompanionDeviceManager { return featurePresent; } - private Activity getActivity() { - return (Activity) mContext; - } - - private String getCallingPackage() { - return mContext.getPackageName(); - } - - private class CallbackProxy extends IFindDeviceCallback.Stub - implements Application.ActivityLifecycleCallbacks { - - private Callback mCallback; - private Handler mHandler; - private AssociationRequest mRequest; - - final Object mLock = new Object(); + private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub { + private final Handler mHandler; + private final Callback mCallback; + private final Executor mExecutor; - private CallbackProxy(AssociationRequest request, Callback callback, Handler handler) { + private AssociationRequestCallbackProxy( + @NonNull Executor executor, @NonNull Callback callback) { + mExecutor = executor; + mHandler = null; mCallback = callback; + } + + private AssociationRequestCallbackProxy( + @NonNull Handler handler, @NonNull Callback callback) { mHandler = handler; - mRequest = request; - getActivity().getApplication().registerActivityLifecycleCallbacks(this); + mExecutor = null; + mCallback = callback; } @Override - public void onSuccess(PendingIntent launcher) { - lockAndPost(Callback::onDeviceFound, launcher.getIntentSender()); + public void onAssociationPending(@NonNull PendingIntent pi) { + execute(mCallback::onAssociationPending, pi.getIntentSender()); } @Override - public void onFailure(CharSequence reason) { - lockAndPost(Callback::onFailure, reason); - } - - <T> void lockAndPost(BiConsumer<Callback, T> action, T payload) { - synchronized (mLock) { - if (mHandler != null) { - mHandler.post(() -> { - Callback callback = null; - synchronized (mLock) { - callback = mCallback; - } - if (callback != null) { - action.accept(callback, payload); - } - }); - } - } + public void onAssociationCreated(@NonNull AssociationInfo association) { + execute(mCallback::onAssociationCreated, association); } @Override - public void onActivityDestroyed(Activity activity) { - synchronized (mLock) { - if (activity != getActivity()) return; - try { - mService.stopScan(mRequest, this, getCallingPackage()); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - getActivity().getApplication().unregisterActivityLifecycleCallbacks(this); - mCallback = null; - mHandler = null; - mRequest = null; - mContext = null; + public void onFailure(CharSequence error) throws RemoteException { + execute(mCallback::onFailure, error); + } + + private <T> void execute(Consumer<T> callback, T arg) { + if (mExecutor != null) { + mExecutor.execute(() -> callback.accept(arg)); + } else { + mHandler.post(() -> callback.accept(arg)); } } + } + + private static class OnAssociationsChangedListenerProxy + extends IOnAssociationsChangedListener.Stub { + private final Executor mExecutor; + private final OnAssociationsChangedListener mListener; + + private OnAssociationsChangedListenerProxy(Executor executor, + OnAssociationsChangedListener listener) { + mExecutor = executor; + mListener = listener; + } - @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} - @Override public void onActivityStarted(Activity activity) {} - @Override public void onActivityResumed(Activity activity) {} - @Override public void onActivityPaused(Activity activity) {} - @Override public void onActivityStopped(Activity activity) {} - @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + @Override + public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) { + mExecutor.execute(() -> mListener.onAssociationsChanged(associations)); + } } } diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java deleted file mode 100644 index 5deed1ad4ff9..000000000000 --- a/core/java/android/companion/DeviceId.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.companion; - -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * The class represents free-form ID of a companion device. - * - * Since companion devices may have multiple IDs of different type at the same time - * (eg. a MAC address and a Serial Number), this class not only stores the ID itself, it also stores - * the type of the ID. - * Both the type of the ID and its actual value are represented as {@link String}-s. - * - * Examples of device IDs: - * - "mac_address: f0:18:98:b3:fd:2e" - * - "ip_address: 128.121.35.200" - * - "imei: 352932100034923 / 44" - * - "serial_number: 96141FFAZ000B7" - * - "meid_hex: 35293210003492" - * - "meid_dic: 08918 92240 0001 3548" - * - * @hide - * TODO(b/1979395): un-hide when implementing public APIs that use this class. - */ -public final class DeviceId implements Parcelable { - public static final String TYPE_MAC_ADDRESS = "mac_address"; - - private final @NonNull String mType; - private final @NonNull String mValue; - - /** - * @param type type of the ID. Non-empty. Max length - 16 characters. - * @param value the ID. Non-empty. Max length - 48 characters. - * @throws IllegalArgumentException if either {@param type} or {@param value} is empty or - * exceeds its max allowed length. - */ - public DeviceId(@NonNull String type, @NonNull String value) { - if (type.isEmpty() || value.isEmpty()) { - throw new IllegalArgumentException("'type' and 'value' should not be empty"); - } - this.mType = type; - this.mValue = value; - } - - /** - * @return the type of the ID. - */ - public @NonNull String getType() { - return mType; - } - - /** - * @return the ID. - */ - public @NonNull String getValue() { - return mValue; - } - - @Override - public String toString() { - return "DeviceId{" - + "type='" + mType + '\'' - + ", value='" + mValue + '\'' - + '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof DeviceId)) return false; - DeviceId deviceId = (DeviceId) o; - return Objects.equals(mType, deviceId.mType) && Objects.equals(mValue, - deviceId.mValue); - } - - @Override - public int hashCode() { - return Objects.hash(mType, mValue); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mType); - dest.writeString(mValue); - } - - private DeviceId(@NonNull Parcel in) { - mType = in.readString(); - mValue = in.readString(); - } - - public static final @NonNull Creator<DeviceId> CREATOR = new Creator<DeviceId>() { - @Override - public DeviceId createFromParcel(@NonNull Parcel in) { - return new DeviceId(in); - } - - @Override - public DeviceId[] newArray(int size) { - return new DeviceId[size]; - } - }; -} diff --git a/core/java/android/companion/IAssociationRequestCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl new file mode 100644 index 000000000000..8cc2a7194e08 --- /dev/null +++ b/core/java/android/companion/IAssociationRequestCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing per missions and + * limitations under the License. + */ + +package android.companion; + +import android.app.PendingIntent; +import android.companion.AssociationInfo; + +/** @hide */ +interface IAssociationRequestCallback { + oneway void onAssociationPending(in PendingIntent pendingIntent); + + oneway void onAssociationCreated(in AssociationInfo associationInfo); + + oneway void onFailure(in CharSequence error); +}
\ No newline at end of file diff --git a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl index 71e5b246e8ec..702e8db79b0f 100644 --- a/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl +++ b/core/java/android/companion/ICompanionDeviceDiscoveryService.aidl @@ -17,7 +17,7 @@ package android.companion; import android.companion.AssociationRequest; -import android.companion.IFindDeviceCallback; +import android.companion.IAssociationRequestCallback; import com.android.internal.infra.AndroidFuture; @@ -26,7 +26,7 @@ oneway interface ICompanionDeviceDiscoveryService { void startDiscovery( in AssociationRequest request, in String callingPackage, - in IFindDeviceCallback findCallback, + in IAssociationRequestCallback applicationCallback, in AndroidFuture<String> serviceCallback); void onAssociationCreated(); diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 101f9489f223..1558db22a003 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -17,7 +17,8 @@ package android.companion; import android.app.PendingIntent; -import android.companion.IFindDeviceCallback; +import android.companion.IAssociationRequestCallback; +import android.companion.IOnAssociationsChangedListener; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.content.ComponentName; @@ -28,32 +29,41 @@ import android.content.ComponentName; * @hide */ interface ICompanionDeviceManager { - void associate(in AssociationRequest request, - in IFindDeviceCallback callback, - in String callingPackage); - void stopScan(in AssociationRequest request, - in IFindDeviceCallback callback, - in String callingPackage); + void associate(in AssociationRequest request, in IAssociationRequestCallback callback, + in String callingPackage, int userId); - List<String> getAssociations(String callingPackage, int userId); - List<AssociationInfo> getAssociationsForUser(int userId); + List<AssociationInfo> getAssociations(String callingPackage, int userId); + List<AssociationInfo> getAllAssociationsForUser(int userId); - void disassociate(String deviceMacAddress, String callingPackage); + /** @deprecated */ + void legacyDisassociate(String deviceMacAddress, String callingPackage, int userId); + void disassociate(int associationId); + + /** @deprecated */ boolean hasNotificationAccess(in ComponentName component); - PendingIntent requestNotificationAccess(in ComponentName component); + PendingIntent requestNotificationAccess(in ComponentName component, int userId); + + /** @deprecated */ boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress, int userId); - void registerDevicePresenceListenerService(in String packageName, in String deviceAddress); + void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage, + int userId); - void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress); + void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage, + int userId); + /** @deprecated */ boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId); + /** @deprecated */ void createAssociation(in String packageName, in String macAddress, int userId, in byte[] certificate); void dispatchMessage(in int messageId, in int associationId, in byte[] message); + + void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); + void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId); } diff --git a/core/java/android/companion/IFindDeviceCallback.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl index a3a47a9fc857..e6794b741e46 100644 --- a/core/java/android/companion/IFindDeviceCallback.aidl +++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,9 @@ package android.companion; -import android.app.PendingIntent; +import android.companion.AssociationInfo; /** @hide */ -interface IFindDeviceCallback { - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - oneway void onSuccess(in PendingIntent launcher); - oneway void onFailure(in CharSequence reason); -} +interface IOnAssociationsChangedListener { + oneway void onAssociationsChanged(in List<AssociationInfo> associations); +}
\ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 25d1d53752a7..2b75022e27c8 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3842,6 +3842,8 @@ public abstract class Context { UWB_SERVICE, MEDIA_METRICS_SERVICE, SUPPLEMENTAL_PROCESS_SERVICE, + //@hide: ATTESTATION_VERIFICATION_SERVICE, + //@hide: SAFETY_CENTER_SERVICE, }) @Retention(RetentionPolicy.SOURCE) public @interface ServiceName {} @@ -5354,9 +5356,12 @@ public abstract class Context { * {@link android.net.NetworkScoreManager} for managing network scoring. * @see #getSystemService(String) * @see android.net.NetworkScoreManager + * @deprecated see https://developer.android.com/guide/topics/connectivity/wifi-suggest for + * alternative API to propose WiFi networks. * @hide */ @SystemApi + @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score"; /** @@ -5739,6 +5744,15 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.security.attestationverification.AttestationVerificationManager}. + * @see #getSystemService(String) + * @see android.security.attestationverification.AttestationVerificationManager + * @hide + */ + public static final String ATTESTATION_VERIFICATION_SERVICE = "attestation_verification"; + + /** + * Use with {@link #getSystemService(String)} to retrieve an * {@link android.security.FileIntegrityManager}. * @see #getSystemService(String) * @see android.security.FileIntegrityManager @@ -5871,6 +5885,27 @@ public abstract class Context { public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process"; /** + * Use with {@link #getSystemService(String)} to retrieve a {@link + * android.safetycenter.SafetyCenterManager} instance for interacting with the safety center. + * + * @see #getSystemService(String) + * @see android.safetycenter.SafetyCenterManager + * @hide + */ + @SystemApi + public static final String SAFETY_CENTER_SERVICE = "safety_center"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.nearby.NearbyManager} to discover nearby devices. + * + * @see #getSystemService(String) + * @see android.nearby.NearbyManager + * @hide + */ + public static final String NEARBY_SERVICE = "nearby"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index a3efbd771fad..a36d532c9919 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2378,6 +2378,14 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES"; + /** + * Activity action: Launch UI to open the Safety Center, which highlights the user's security + * and privacy status. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SAFETY_CENTER = + "android.intent.action.SAFETY_CENTER"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -5522,8 +5530,8 @@ public class Intent implements Parcelable, Cloneable { /** * A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD}, - * that specifies whether the system displayed attribution information in the - * permission usage system UI for the chosen entry. + * that specifies whether the permission usage system UI is showing attribution information + * for the chosen entry. * * <p> The extra can only be true if application has specified attributionsAreUserVisible * in its manifest. </p> @@ -6916,7 +6924,6 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_OFFLOAD = 0x80000000; /** - /** * If set, when sending a broadcast the recipient will run on the system dedicated queue. * * @hide diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index f72288c670d9..18e205f2e79e 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -18,6 +18,7 @@ package android.content.pm; import android.content.pm.Checksum; import android.content.pm.DataLoaderParamsParcel; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageInstallObserver2; import android.content.IntentSender; import android.os.ParcelFileDescriptor; @@ -36,6 +37,7 @@ interface IPackageInstallerSession { void stageViaHardLink(String target); void setChecksums(String name, in Checksum[] checksums, in byte[] signature); + void requestChecksums(in String name, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener); void removeSplit(String splitName); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 46f179797e4a..1c82b38c5007 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -69,41 +69,34 @@ interface IPackageManager { void checkPackageStartable(String packageName, int userId); @UnsupportedAppUsage(trackingBug = 171933273) boolean isPackageAvailable(String packageName, int userId); - @UnsupportedAppUsage - PackageInfo getPackageInfo(String packageName, int flags, int userId); + PackageInfo getPackageInfo(String packageName, long flags, int userId); PackageInfo getPackageInfoVersioned(in VersionedPackage versionedPackage, - int flags, int userId); - @UnsupportedAppUsage - int getPackageUid(String packageName, int flags, int userId); - int[] getPackageGids(String packageName, int flags, int userId); + long flags, int userId); + int getPackageUid(String packageName, long flags, int userId); + int[] getPackageGids(String packageName, long flags, int userId); @UnsupportedAppUsage String[] currentToCanonicalPackageNames(in String[] names); @UnsupportedAppUsage String[] canonicalToCurrentPackageNames(in String[] names); - @UnsupportedAppUsage - ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId); + ApplicationInfo getApplicationInfo(String packageName, long flags, int userId); /** * @return the target SDK for the given package name, or -1 if it cannot be retrieved */ int getTargetSdkVersion(String packageName); - @UnsupportedAppUsage - ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId); + ActivityInfo getActivityInfo(in ComponentName className, long flags, int userId); boolean activitySupportsIntent(in ComponentName className, in Intent intent, String resolvedType); - @UnsupportedAppUsage - ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId); + ActivityInfo getReceiverInfo(in ComponentName className, long flags, int userId); - @UnsupportedAppUsage - ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId); + ServiceInfo getServiceInfo(in ComponentName className, long flags, int userId); - @UnsupportedAppUsage - ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId); + ProviderInfo getProviderInfo(in ComponentName className, long flags, int userId); boolean isProtectedBroadcast(String actionName); @@ -133,33 +126,31 @@ interface IPackageManager { @UnsupportedAppUsage boolean isUidPrivileged(int uid); - @UnsupportedAppUsage - ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); + ResolveInfo resolveIntent(in Intent intent, String resolvedType, long flags, int userId); ResolveInfo findPersistentPreferredActivity(in Intent intent, int userId); boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId); - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) ParceledListSlice queryIntentActivities(in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); ParceledListSlice queryIntentActivityOptions( in ComponentName caller, in Intent[] specifics, in String[] specificTypes, in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); ParceledListSlice queryIntentReceivers(in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); ResolveInfo resolveService(in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); ParceledListSlice queryIntentServices(in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); ParceledListSlice queryIntentContentProviders(in Intent intent, - String resolvedType, int flags, int userId); + String resolvedType, long flags, int userId); /** * This implements getInstalledPackages via a "last returned row" @@ -167,8 +158,7 @@ interface IPackageManager { * limit that kicks in when flags are included that bloat up the data * returned. */ - @UnsupportedAppUsage - ParceledListSlice getInstalledPackages(int flags, in int userId); + ParceledListSlice getInstalledPackages(long flags, in int userId); /** * This implements getPackagesHoldingPermissions via a "last returned row" @@ -177,7 +167,7 @@ interface IPackageManager { * returned. */ ParceledListSlice getPackagesHoldingPermissions(in String[] permissions, - int flags, int userId); + long flags, int userId); /** * This implements getInstalledApplications via a "last returned row" @@ -185,18 +175,17 @@ interface IPackageManager { * limit that kicks in when flags are included that bloat up the data * returned. */ - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - ParceledListSlice getInstalledApplications(int flags, int userId); + ParceledListSlice getInstalledApplications(long flags, int userId); /** * Retrieve all applications that are marked as persistent. * - * @return A List<applicationInfo> containing one entry for each persistent + * @return A List<ApplicationInfo> containing one entry for each persistent * application. */ ParceledListSlice getPersistentApplications(int flags); - ProviderInfo resolveContentProvider(String name, int flags, int userId); + ProviderInfo resolveContentProvider(String name, long flags, int userId); /** * Retrieve sync information for all content providers. @@ -211,7 +200,7 @@ interface IPackageManager { inout List<ProviderInfo> outInfo); ParceledListSlice queryContentProviders( - String processName, int uid, int flags, String metaDataKey); + String processName, int uid, long flags, String metaDataKey); @UnsupportedAppUsage InstrumentationInfo getInstrumentationInfo( @@ -690,9 +679,9 @@ interface IPackageManager { int getInstallReason(String packageName, int userId); - ParceledListSlice getSharedLibraries(in String packageName, int flags, int userId); + ParceledListSlice getSharedLibraries(in String packageName, long flags, int userId); - ParceledListSlice getDeclaredSharedLibraries(in String packageName, int flags, int userId); + ParceledListSlice getDeclaredSharedLibraries(in String packageName, long flags, int userId); boolean canRequestPackageInstalls(String packageName, int userId); @@ -750,7 +739,7 @@ interface IPackageManager { void notifyPackagesReplacedReceived(in String[] packages); - void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId); + void requestPackageChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId); IntentSender getLaunchIntentSenderForPackage(String packageName, String callingPackage, String featureId, int userId); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 158601351d8b..80584d161226 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -19,6 +19,13 @@ package android.content.pm; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.MODE_IGNORED; +import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256; +import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512; +import static android.content.pm.Checksum.TYPE_WHOLE_MD5; +import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256; +import static android.content.pm.Checksum.TYPE_WHOLE_SHA1; +import static android.content.pm.Checksum.TYPE_WHOLE_SHA256; +import static android.content.pm.Checksum.TYPE_WHOLE_SHA512; import android.Manifest; import android.annotation.CurrentTimeMillisLong; @@ -62,12 +69,16 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import java.io.Closeable; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.MessageDigest; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -406,6 +417,13 @@ public class PackageInstaller { @Retention(RetentionPolicy.SOURCE) public @interface FileLocation{} + /** Default set of checksums - includes all available checksums. + * @see Session#requestChecksums */ + private static final int DEFAULT_CHECKSUMS = + TYPE_WHOLE_MERKLE_ROOT_4K_SHA256 | TYPE_WHOLE_MD5 | TYPE_WHOLE_SHA1 | TYPE_WHOLE_SHA256 + | TYPE_WHOLE_SHA512 | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256 + | TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512; + private final IPackageInstaller mInstaller; private final int mUserId; private final String mInstallerPackageName; @@ -1256,7 +1274,7 @@ public class PackageInstaller { * @param name previously written as part of this session. * {@link #openWrite} * @param checksums installer intends to make available via - * {@link PackageManager#requestChecksums}. + * {@link PackageManager#requestChecksums} or {@link #requestChecksums}. * @param signature DER PKCS#7 detached signature bytes over binary serialized checksums * to enable integrity checking for the checksums or null for no integrity * checking. {@link PackageManager#requestChecksums} will return @@ -1293,6 +1311,89 @@ public class PackageInstaller { } } + private static List<byte[]> encodeCertificates(List<Certificate> certs) throws + CertificateEncodingException { + if (certs == null) { + return null; + } + List<byte[]> result = new ArrayList<>(certs.size()); + for (Certificate cert : certs) { + if (!(cert instanceof X509Certificate)) { + throw new CertificateEncodingException("Only X509 certificates supported."); + } + result.add(cert.getEncoded()); + } + return result; + } + + /** + * Requests checksums for the APK file in session. + * <p> + * A possible use case is replying to {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION} + * broadcast. + * The checksums will be returned asynchronously via onChecksumsReadyListener. + * <p> + * By default returns all readily available checksums: + * <ul> + * <li>enforced by platform, + * <li>enforced by the installer. + * </ul> + * If the caller needs a specific checksum type, they can specify it as required. + * <p> + * <b>Caution: Android can not verify installer-provided checksums. Make sure you specify + * trusted installers.</b> + * <p> + * @param name previously written as part of this session. + * {@link #openWrite} + * @param required to explicitly request the checksum types. Will incur significant + * CPU/memory/disk usage. + * @param trustedInstallers for checksums enforced by installer, which installers are to be + * trusted. + * {@link PackageManager#TRUST_ALL} will return checksums from any + * installer, + * {@link PackageManager#TRUST_NONE} disables optimized + * installer-enforced checksums, otherwise the list has to be + * a non-empty list of certificates. + * @param onChecksumsReadyListener called once when the results are available. + * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers. + * @throws FileNotFoundException if the file does not exist. + * @throws IllegalArgumentException if the list of trusted installer certificates is empty. + */ + public void requestChecksums(@NonNull String name, @Checksum.TypeMask int required, + @NonNull List<Certificate> trustedInstallers, + @NonNull PackageManager.OnChecksumsReadyListener onChecksumsReadyListener) + throws CertificateEncodingException, FileNotFoundException { + Objects.requireNonNull(name); + Objects.requireNonNull(onChecksumsReadyListener); + Objects.requireNonNull(trustedInstallers); + if (trustedInstallers == PackageManager.TRUST_ALL) { + trustedInstallers = null; + } else if (trustedInstallers == PackageManager.TRUST_NONE) { + trustedInstallers = Collections.emptyList(); + } else if (trustedInstallers.isEmpty()) { + throw new IllegalArgumentException( + "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty " + + "list of certificates."); + } + try { + IOnChecksumsReadyListener onChecksumsReadyListenerDelegate = + new IOnChecksumsReadyListener.Stub() { + @Override + public void onChecksumsReady(List<ApkChecksum> checksums) + throws RemoteException { + onChecksumsReadyListener.onChecksumsReady(checksums); + } + }; + mSession.requestChecksums(name, DEFAULT_CHECKSUMS, required, + encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate); + } catch (ParcelableException e) { + e.maybeRethrow(FileNotFoundException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Attempt to commit everything staged in this session. This may require * user intervention, and so it may not happen immediately. The final diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 1b51314e00d9..c777bf542706 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -9044,7 +9044,7 @@ public abstract class PackageManager { } /** - * Requesting the checksums for APKs within a package. + * Requests the checksums for APKs within a package. * The checksums will be returned asynchronously via onChecksumsReadyListener. * * By default returns all readily available checksums: diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index d1577684aac6..ff80e614be58 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -19,8 +19,11 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Build; +import android.os.Build.Partition; import android.os.Environment; import android.os.FileUtils; +import android.os.SystemProperties; import com.android.internal.annotations.VisibleForTesting; @@ -64,20 +67,34 @@ public class PackagePartitions { */ private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS = new ArrayList<>(Arrays.asList( - new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM, + new SystemPartition(Environment.getRootDirectory(), + PARTITION_SYSTEM, Partition.PARTITION_NAME_SYSTEM, true /* containsPrivApp */, false /* containsOverlay */), - new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR, + new SystemPartition(Environment.getVendorDirectory(), + PARTITION_VENDOR, Partition.PARTITION_NAME_VENDOR, true /* containsPrivApp */, true /* containsOverlay */), - new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM, + new SystemPartition(Environment.getOdmDirectory(), + PARTITION_ODM, Partition.PARTITION_NAME_ODM, true /* containsPrivApp */, true /* containsOverlay */), - new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM, + new SystemPartition(Environment.getOemDirectory(), + PARTITION_OEM, Partition.PARTITION_NAME_OEM, false /* containsPrivApp */, true /* containsOverlay */), - new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT, + new SystemPartition(Environment.getProductDirectory(), + PARTITION_PRODUCT, Partition.PARTITION_NAME_PRODUCT, true /* containsPrivApp */, true /* containsOverlay */), - new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT, + new SystemPartition(Environment.getSystemExtDirectory(), + PARTITION_SYSTEM_EXT, Partition.PARTITION_NAME_SYSTEM_EXT, true /* containsPrivApp */, true /* containsOverlay */))); /** + * A string to represent the fingerprint of this build and all package partitions. Using it to + * determine whether the system update has occurred. Different from {@link Build#FINGERPRINT}, + * this string is digested from the fingerprints of the build and all package partitions to + * help detect the partition update. + */ + public static final String FINGERPRINT = getFingerprint(); + + /** * Returns a list in which the elements are products of the specified function applied to the * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order. */ @@ -101,6 +118,23 @@ public class PackagePartitions { } } + /** + * Returns a fingerprint string for this build and all package partitions. The string is + * digested from the fingerprints of the build and all package partitions. + * + * @return A string to represent the fingerprint of this build and all package partitions. + */ + @NonNull + private static String getFingerprint() { + final String[] digestProperties = new String[SYSTEM_PARTITIONS.size() + 1]; + for (int i = 0; i < SYSTEM_PARTITIONS.size(); i++) { + final String partitionName = SYSTEM_PARTITIONS.get(i).getName(); + digestProperties[i] = "ro." + partitionName + ".build.fingerprint"; + } + digestProperties[SYSTEM_PARTITIONS.size()] = "ro.build.fingerprint"; // build fingerprint + return SystemProperties.digestOf(digestProperties); + } + /** Represents a partition that contains application packages. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public static class SystemPartition { @@ -108,6 +142,9 @@ public class PackagePartitions { public final int type; @NonNull + private final String mName; + + @NonNull private final DeferredCanonicalFile mFolder; @Nullable @@ -122,9 +159,10 @@ public class PackagePartitions { @NonNull private final File mNonConicalFolder; - private SystemPartition(@NonNull File folder, @PartitionType int type, + private SystemPartition(@NonNull File folder, @PartitionType int type, String name, boolean containsPrivApp, boolean containsOverlay) { this.type = type; + this.mName = name; this.mFolder = new DeferredCanonicalFile(folder); this.mAppFolder = new DeferredCanonicalFile(folder, "app"); this.mPrivAppFolder = containsPrivApp ? new DeferredCanonicalFile(folder, "priv-app") @@ -136,6 +174,7 @@ public class PackagePartitions { public SystemPartition(@NonNull SystemPartition original) { this.type = original.type; + this.mName = original.mName; this.mFolder = new DeferredCanonicalFile(original.mFolder.getFile()); this.mAppFolder = original.mAppFolder; this.mPrivAppFolder = original.mPrivAppFolder; @@ -148,10 +187,19 @@ public class PackagePartitions { * different root folder. */ public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) { - this(rootFolder, partition.type, partition.mPrivAppFolder != null, + this(rootFolder, partition.type, partition.mName, partition.mPrivAppFolder != null, partition.mOverlayFolder != null); } + /** + * Returns the name identifying the partition. + * @see Partition + */ + @NonNull + public String getName() { + return mName; + } + /** Returns the canonical folder of the partition. */ @NonNull public File getFolder() { diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index cc4782ad4a79..aa5780695d75 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -10,6 +10,9 @@ "path": "frameworks/base/services/tests/PackageManagerComponentOverrideTests" }, { + "path": "frameworks/base/services/tests/servicestests/src/com/android/server/pm" + }, + { "path": "cts/tests/tests/packageinstaller" }, { diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java index ef124c7e3b27..b11b38a6d788 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java @@ -76,7 +76,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generate(ParsingPackageRead pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, int userId) { return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId, null); @@ -90,7 +90,7 @@ public class PackageInfoWithoutStateUtils { @Nullable private static PackageInfo generateWithComponents(ParsingPackageRead pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, int userId, @Nullable ApexInfo apexInfo) { ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId); @@ -190,7 +190,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generateWithoutComponents(ParsingPackageRead pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { if (!checkUseInstalled(pkg, state, flags)) { @@ -210,9 +210,9 @@ public class PackageInfoWithoutStateUtils { */ @NonNull public static PackageInfo generateWithoutComponentsUnchecked(ParsingPackageRead pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, - Set<String> grantedPermissions, FrameworkPackageUserState state, int userId, - @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, + long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { PackageInfo pi = new PackageInfo(); pi.packageName = pkg.getPackageName(); pi.splitNames = pkg.getSplitNames(); @@ -365,7 +365,8 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateApplicationInfo(ParsingPackageRead pkg, - @PackageManager.ApplicationInfoFlags int flags, FrameworkPackageUserState state, int userId) { + @PackageManager.ApplicationInfoFlags long flags, FrameworkPackageUserState state, + int userId) { if (pkg == null) { return null; } @@ -392,8 +393,8 @@ public class PackageInfoWithoutStateUtils { */ @NonNull public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg, - @PackageManager.ApplicationInfoFlags int flags, @NonNull FrameworkPackageUserState state, - int userId, boolean assignUserFields) { + @PackageManager.ApplicationInfoFlags long flags, + @NonNull FrameworkPackageUserState state, int userId, boolean assignUserFields) { // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = ((ParsingPackageHidden) pkg).toAppInfoWithoutState(); @@ -406,7 +407,7 @@ public class PackageInfoWithoutStateUtils { return ai; } - private static void updateApplicationInfo(ApplicationInfo ai, int flags, + private static void updateApplicationInfo(ApplicationInfo ai, long flags, FrameworkPackageUserState state) { if ((flags & PackageManager.GET_META_DATA) == 0) { ai.metaData = null; @@ -452,8 +453,8 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateDelegateApplicationInfo(@Nullable ApplicationInfo ai, - @PackageManager.ApplicationInfoFlags int flags, @NonNull FrameworkPackageUserState state, - int userId) { + @PackageManager.ApplicationInfoFlags long flags, + @NonNull FrameworkPackageUserState state, int userId) { if (ai == null || !checkUseInstalledOrHidden(flags, state, ai)) { return null; } @@ -469,7 +470,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateDelegateActivityInfo(@Nullable ActivityInfo a, - @PackageManager.ComponentInfoFlags int flags, @NonNull FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlags long flags, @NonNull FrameworkPackageUserState state, int userId) { if (a == null || !checkUseInstalledOrHidden(flags, state, a.applicationInfo)) { return null; @@ -484,7 +485,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (a == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -504,12 +505,12 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, int, + * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, long, * FrameworkPackageUserState, ApplicationInfo, int)}. */ @NonNull public static ActivityInfo generateActivityInfoUnchecked(@NonNull ParsedActivity a, - @PackageManager.ComponentInfoFlags int flags, + @PackageManager.ComponentInfoFlags long flags, @NonNull ApplicationInfo applicationInfo) { // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(); @@ -550,13 +551,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, int userId) { + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, + int userId) { return generateActivityInfo(pkg, a, flags, state, null, userId); } @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (s == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -576,12 +578,12 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, int, FrameworkPackageUserState, - * ApplicationInfo, int)}. + * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, long, + * FrameworkPackageUserState, ApplicationInfo, int)}. */ @NonNull public static ServiceInfo generateServiceInfoUnchecked(@NonNull ParsedService s, - @PackageManager.ComponentInfoFlags int flags, + @PackageManager.ComponentInfoFlags long flags, @NonNull ApplicationInfo applicationInfo) { // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(); @@ -600,13 +602,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, int userId) { + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, + int userId) { return generateServiceInfo(pkg, s, flags, state, null, userId); } @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (p == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -626,12 +629,12 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, int, + * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, long, * FrameworkPackageUserState, ApplicationInfo, int)}. */ @NonNull public static ProviderInfo generateProviderInfoUnchecked(@NonNull ParsedProvider p, - @PackageManager.ComponentInfoFlags int flags, + @PackageManager.ComponentInfoFlags long flags, @NonNull ApplicationInfo applicationInfo) { // Make shallow copies so we can store the metadata safely ProviderInfo pi = new ProviderInfo(); @@ -661,17 +664,18 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlags int flags, FrameworkPackageUserState state, int userId) { + @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state, + int userId) { return generateProviderInfo(pkg, p, flags, state, null, userId); } /** - * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, int, - * FrameworkPackageUserState, int, boolean)} + * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, + * long, FrameworkPackageUserState, int, boolean)} */ @Nullable public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i, - ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags int flags, int userId, + ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags long flags, int userId, boolean assignUserFields) { if (i == null) return null; @@ -702,7 +706,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PermissionInfo generatePermissionInfo(ParsedPermission p, - @PackageManager.ComponentInfoFlags int flags) { + @PackageManager.ComponentInfoFlags long flags) { if (p == null) return null; PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission()); @@ -725,7 +729,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PermissionGroupInfo generatePermissionGroupInfo(ParsedPermissionGroup pg, - @PackageManager.ComponentInfoFlags int flags) { + @PackageManager.ComponentInfoFlags long flags) { if (pg == null) return null; PermissionGroupInfo pgi = new PermissionGroupInfo( @@ -753,8 +757,8 @@ public class PackageInfoWithoutStateUtils { return new Attribution(pa.getTag(), pa.getLabel()); } - private static boolean checkUseInstalledOrHidden(int flags, @NonNull FrameworkPackageUserState state, - @Nullable ApplicationInfo appInfo) { + private static boolean checkUseInstalledOrHidden(long flags, + @NonNull FrameworkPackageUserState state, @Nullable ApplicationInfo appInfo) { // Returns false if the package is hidden system app until installed. if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0 && !state.isInstalled() @@ -882,8 +886,8 @@ public class PackageInfoWithoutStateUtils { return privateFlagsExt; } - private static boolean checkUseInstalled(ParsingPackageRead pkg, FrameworkPackageUserState state, - @PackageManager.PackageInfoFlags int flags) { + private static boolean checkUseInstalled(ParsingPackageRead pkg, + FrameworkPackageUserState state, @PackageManager.PackageInfoFlags long flags) { // If available for the target user return PackageUserStateUtils.isAvailable(state, flags); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f07f3827c1c9..d5957a2b6924 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -334,7 +334,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, @DataClass.ParcelWith(ForInternedString.class) private String backupAgentName; private int banner; - private int category; + private int category = ApplicationInfo.CATEGORY_UNDEFINED; @Nullable @DataClass.ParcelWith(ForInternedString.class) private String classLoaderName; diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java index 1ac9739cc460..0334601a0a06 100644 --- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java +++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java @@ -170,13 +170,13 @@ public class ComponentParseUtils { } public static boolean isMatch(FrameworkPackageUserState state, boolean isSystem, - boolean isPackageEnabled, ParsedMainComponent component, int flags) { + boolean isPackageEnabled, ParsedMainComponent component, long flags) { return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); } public static boolean isEnabled(FrameworkPackageUserState state, boolean isPackageEnabled, - ParsedMainComponent parsedComponent, int flags) { + ParsedMainComponent parsedComponent, long flags) { return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); } diff --git a/core/java/android/content/pm/pkg/PackageUserStateUtils.java b/core/java/android/content/pm/pkg/PackageUserStateUtils.java index 9a800b0990fd..468bff1b53ec 100644 --- a/core/java/android/content/pm/pkg/PackageUserStateUtils.java +++ b/core/java/android/content/pm/pkg/PackageUserStateUtils.java @@ -34,15 +34,15 @@ public class PackageUserStateUtils { private static final boolean DEBUG = false; private static final String TAG = "PackageUserStateUtils"; - public static boolean isMatch(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, - int flags) { + public static boolean isMatch(@NonNull FrameworkPackageUserState state, + ComponentInfo componentInfo, long flags) { return isMatch(state, componentInfo.applicationInfo.isSystemApp(), componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.directBootAware, componentInfo.name, flags); } public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, - boolean isPackageEnabled, ParsedMainComponent component, int flags) { + boolean isPackageEnabled, ParsedMainComponent component, long flags) { return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); } @@ -58,7 +58,7 @@ public class PackageUserStateUtils { */ public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, boolean isPackageEnabled, boolean isComponentEnabled, - boolean isComponentDirectBootAware, String componentName, int flags) { + boolean isComponentDirectBootAware, String componentName, long flags) { final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; if (!isAvailable(state, flags) && !(isSystem && matchUninstalled)) { return reportIfDebug(false, flags); @@ -81,7 +81,7 @@ public class PackageUserStateUtils { return reportIfDebug(matchesUnaware || matchesAware, flags); } - public static boolean isAvailable(@NonNull FrameworkPackageUserState state, int flags) { + public static boolean isAvailable(@NonNull FrameworkPackageUserState state, long flags) { // True if it is installed for this user and it is not hidden. If it is hidden, // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0; @@ -91,7 +91,7 @@ public class PackageUserStateUtils { && (!state.isHidden() || matchUninstalled)); } - public static boolean reportIfDebug(boolean result, int flags) { + public static boolean reportIfDebug(boolean result, long flags) { if (DEBUG && !result) { Slog.i(TAG, "No match!; flags: " + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " " @@ -101,13 +101,13 @@ public class PackageUserStateUtils { } public static boolean isEnabled(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, - int flags) { + long flags) { return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.name, flags); } public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, - ParsedMainComponent parsedComponent, int flags) { + ParsedMainComponent parsedComponent, long flags) { return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); } @@ -115,8 +115,9 @@ public class PackageUserStateUtils { /** * Test if the given component is considered enabled. */ - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, - boolean isComponentEnabled, String componentName, int flags) { + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, + boolean isPackageEnabled, boolean isComponentEnabled, String componentName, + long flags) { if ((flags & MATCH_DISABLED_COMPONENTS) != 0) { return true; } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8ebb8ecd4c06..01bf49eb03a6 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -2432,27 +2432,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration break; } - switch (config.uiMode & Configuration.UI_MODE_TYPE_MASK) { - case Configuration.UI_MODE_TYPE_APPLIANCE: - parts.add("appliance"); - break; - case Configuration.UI_MODE_TYPE_DESK: - parts.add("desk"); - break; - case Configuration.UI_MODE_TYPE_TELEVISION: - parts.add("television"); - break; - case Configuration.UI_MODE_TYPE_CAR: - parts.add("car"); - break; - case Configuration.UI_MODE_TYPE_WATCH: - parts.add("watch"); - break; - case Configuration.UI_MODE_TYPE_VR_HEADSET: - parts.add("vrheadset"); - break; - default: - break; + final String uiModeTypeString = + getUiModeTypeString(config.uiMode & Configuration.UI_MODE_TYPE_MASK); + if (uiModeTypeString != null) { + parts.add(uiModeTypeString); } switch (config.uiMode & Configuration.UI_MODE_NIGHT_MASK) { @@ -2587,6 +2570,28 @@ public final class Configuration implements Parcelable, Comparable<Configuration } /** + * @hide + */ + public static String getUiModeTypeString(int uiModeType) { + switch (uiModeType) { + case Configuration.UI_MODE_TYPE_APPLIANCE: + return "appliance"; + case Configuration.UI_MODE_TYPE_DESK: + return "desk"; + case Configuration.UI_MODE_TYPE_TELEVISION: + return "television"; + case Configuration.UI_MODE_TYPE_CAR: + return "car"; + case Configuration.UI_MODE_TYPE_WATCH: + return "watch"; + case Configuration.UI_MODE_TYPE_VR_HEADSET: + return "vrheadset"; + default: + return null; + } + } + + /** * Generate a delta Configuration between <code>base</code> and <code>change</code>. The * resulting delta can be used with {@link #updateFrom(Configuration)}. * <p /> diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 96a18dcac92a..3c8b6e9101b3 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1323,7 +1323,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Maximum flashlight brightness level.</p> * <p>If this value is greater than 1, then the device supports controlling the * flashlight brightness level via - * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}. + * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}. * If this value is equal to 1, flashlight brightness control is not supported. * The value for this key will be null for devices with no flash unit.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> @@ -1335,7 +1335,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Default flashlight brightness level to be set via - * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p> + * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p> * <p>If flash unit is available this will be greater than or equal to 1 and less * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p> * <p>Setting flashlight brightness above the default level diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 88649392c23c..9b19fc4d3ef2 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -110,6 +110,11 @@ public class CameraDeviceImpl extends CameraDevice private int mRepeatingRequestId = REQUEST_ID_NONE; // Latest repeating request list's types private int[] mRepeatingRequestTypes; + + // Cache failed requests to process later in case of a repeating error callback + private int mFailedRepeatingRequestId = REQUEST_ID_NONE; + private int[] mFailedRepeatingRequestTypes; + // Map stream IDs to input/output configurations private SimpleEntry<Integer, InputConfiguration> mConfiguredInput = new SimpleEntry<>(REQUEST_ID_NONE, null); @@ -1326,16 +1331,25 @@ public class CameraDeviceImpl extends CameraDevice int requestId = mRepeatingRequestId; mRepeatingRequestId = REQUEST_ID_NONE; + mFailedRepeatingRequestId = REQUEST_ID_NONE; int[] requestTypes = mRepeatingRequestTypes; mRepeatingRequestTypes = null; + mFailedRepeatingRequestTypes = null; long lastFrameNumber; try { lastFrameNumber = mRemoteDevice.cancelRequest(requestId); } catch (IllegalArgumentException e) { if (DEBUG) { - Log.v(TAG, "Repeating request was already stopped for request " + requestId); + Log.v(TAG, "Repeating request was already stopped for request " + + requestId); } + // Cache request id and request types in case of a race with + // "onRepeatingRequestError" which may no yet be scheduled on another thread + // or blocked by us. + mFailedRepeatingRequestId = requestId; + mFailedRepeatingRequestTypes = requestTypes; + // Repeating request was already stopped. Nothing more to do. return; } @@ -1965,7 +1979,17 @@ public class CameraDeviceImpl extends CameraDevice synchronized(mInterfaceLock) { // Camera is already closed or no repeating request is present. if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) { - return; // Camera already closed + if ((mFailedRepeatingRequestId == repeatingRequestId) && + (mFailedRepeatingRequestTypes != null) && (mRemoteDevice != null)) { + Log.v(TAG, "Resuming stop of failed repeating request with id: " + + mFailedRepeatingRequestId); + + checkEarlyTriggerSequenceCompleteLocked(mFailedRepeatingRequestId, + lastFrameNumber, mFailedRepeatingRequestTypes); + mFailedRepeatingRequestId = REQUEST_ID_NONE; + mFailedRepeatingRequestTypes = null; + } + return; } // Redirect device callback to the offline session in case we are in the middle diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java index 7d719846f343..0b1ed65ef937 100644 --- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java +++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java @@ -109,7 +109,8 @@ public class AmbientDisplayConfiguration { /** {@hide} */ public boolean quickPickupSensorEnabled(int user) { - return !TextUtils.isEmpty(quickPickupSensorType()) + return boolSettingDefaultOn(Settings.Secure.DOZE_QUICK_PICKUP_GESTURE, user) + && !TextUtils.isEmpty(quickPickupSensorType()) && pickupGestureEnabled(user) && !alwaysOnEnabled(user); } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index fd34fa4c9c7f..2985c754ac45 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Point; import android.hardware.SensorManager; +import android.media.projection.IMediaProjection; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; @@ -30,6 +31,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.window.DisplayWindowPolicyController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -379,6 +381,39 @@ public abstract class DisplayManagerInternal { public abstract void onEarlyInteractivityChange(boolean interactive); /** + * A special API for creates a virtual display with a DisplayPolicyController in system_server. + * <p> + * If this method is called without original calling uid, the caller must enforce the + * corresponding permissions according to the flags. + * {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT} + * {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT} + * {@link android.Manifest.permission#ADD_TRUSTED_DISPLAY} + * {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} + * </p> + * + * @param virtualDisplayConfig The arguments for the virtual display configuration. See + * {@link VirtualDisplayConfig} for using it. + * @param callback Callback to call when the virtual display's state changes, or null if none. + * @param projection MediaProjection token. + * @param packageName The package name of the app. + * @param controller The DisplayWindowPolicyControl that can control what contents are + * allowed to be displayed. + * @return The newly created virtual display id , or {@link Display#INVALID_DISPLAY} if the + * virtual display cannot be created. + */ + public abstract int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, + DisplayWindowPolicyController controller); + + /** + * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId} + * + * @param displayId The id of the display. + * @return The associated {@link DisplayWindowPolicyController}. + */ + public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId); + + /** * Describes the requested power state of the display. * * This object is intended to describe the general characteristics of the diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index e5d86204077b..6f0c944b76ff 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1016,9 +1016,8 @@ public final class InputManager { } /** - * Queries the framework about whether any physical keys exist on the - * any keyboard attached to the device that are capable of producing the given - * array of key codes. + * Queries the framework about whether any physical keys exist on any currently attached input + * devices that are capable of producing the given array of key codes. * * @param keyCodes The array of key codes to query. * @return A new array of the same size as the key codes array whose elements @@ -1032,11 +1031,10 @@ public final class InputManager { } /** - * Queries the framework about whether any physical keys exist on the - * any keyboard attached to the device that are capable of producing the given - * array of key codes. + * Queries the framework about whether any physical keys exist on the specified input device + * that are capable of producing the given array of key codes. * - * @param id The id of the device to query. + * @param id The id of the input device to query or -1 to consult all devices. * @param keyCodes The array of key codes to query. * @return A new array of the same size as the key codes array whose elements are set to true * if the given device could produce the corresponding key code at the same index in the key diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java index 5cd4eb576aad..5e0e1b7b7450 100644 --- a/core/java/android/net/NetworkKey.java +++ b/core/java/android/net/NetworkKey.java @@ -35,8 +35,10 @@ import java.util.Objects; /** * Information which identifies a specific network. * + * @deprecated as part of the {@link NetworkScoreManager} deprecation. * @hide */ +@Deprecated @SystemApi // NOTE: Ideally, we would abstract away the details of what identifies a network of a specific // type, so that all networks appear the same and can be scored without concern to the network type diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index 0ba266345a60..7b8b5c0db335 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -51,9 +51,13 @@ import java.util.concurrent.Executor; * permission. * </ul> * + * @deprecated No longer functional on {@link android.os.Build.VERSION_CODES#TIRAMISU} and above. + * See https://developer.android.com/guide/topics/connectivity/wifi-suggest for + * alternative APIs to suggest/configure Wi-Fi networks. * @hide */ @SystemApi +@Deprecated @SystemService(Context.NETWORK_SCORE_SERVICE) public class NetworkScoreManager { private static final String TAG = "NetworkScoreManager"; @@ -245,7 +249,7 @@ public class NetworkScoreManager { * or {@link permission#REQUEST_NETWORK_SCORES} permissions. */ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS, - android.Manifest.permission.REQUEST_NETWORK_SCORES}) + android.Manifest.permission.REQUEST_NETWORK_SCORES}) public String getActiveScorerPackage() { try { return mService.getActiveScorerPackage(); @@ -322,7 +326,7 @@ public class NetworkScoreManager { * hold the {@link permission#REQUEST_NETWORK_SCORES} permission. */ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS, - android.Manifest.permission.REQUEST_NETWORK_SCORES}) + android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws SecurityException { try { return mService.clearScores(); @@ -344,7 +348,7 @@ public class NetworkScoreManager { */ @SystemApi @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS, - android.Manifest.permission.REQUEST_NETWORK_SCORES}) + android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean setActiveScorer(String packageName) throws SecurityException { try { return mService.setActiveScorer(packageName); @@ -362,7 +366,7 @@ public class NetworkScoreManager { * hold the {@link permission#REQUEST_NETWORK_SCORES} permission. */ @RequiresPermission(anyOf = {android.Manifest.permission.SCORE_NETWORKS, - android.Manifest.permission.REQUEST_NETWORK_SCORES}) + android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws SecurityException { try { mService.disableScoring(); diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index ee24084e63c6..c906a13bf41b 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -70,9 +70,17 @@ public class NetworkTemplate implements Parcelable { private static final String TAG = "NetworkTemplate"; /** + * Initial Version of the backup serializer. + */ + public static final int BACKUP_VERSION_1_INIT = 1; + /** + * Version of the backup serializer that added carrier template support. + */ + public static final int BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2; + /** * Current Version of the Backup Serializer. */ - private static final int BACKUP_VERSION = 1; + private static final int BACKUP_VERSION = BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE; public static final int MATCH_MOBILE = 1; public static final int MATCH_WIFI = 4; @@ -285,6 +293,10 @@ public class NetworkTemplate implements Parcelable { private final int mRoaming; private final int mDefaultNetwork; private final int mSubType; + /** + * The subscriber Id match rule defines how the template should match networks with + * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail. + */ private final int mSubscriberIdMatchRule; // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}. @@ -348,7 +360,7 @@ public class NetworkTemplate implements Parcelable { mSubscriberIdMatchRule = subscriberIdMatchRule; checkValidSubscriberIdMatchRule(); if (!isKnownMatchRule(matchRule)) { - Log.e(TAG, "Unknown network template rule " + matchRule + throw new IllegalArgumentException("Unknown network template rule " + matchRule + " will not match any identity."); } } @@ -842,11 +854,17 @@ public class NetworkTemplate implements Parcelable { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); + if (!isPersistable()) { + Log.wtf(TAG, "Trying to backup non-persistable template: " + this); + } + out.writeInt(BACKUP_VERSION); out.writeInt(mMatchRule); BackupUtils.writeString(out, mSubscriberId); BackupUtils.writeString(out, mNetworkId); + out.writeInt(mMetered); + out.writeInt(mSubscriberIdMatchRule); return baos.toByteArray(); } @@ -854,7 +872,7 @@ public class NetworkTemplate implements Parcelable { public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in) throws IOException, BackupUtils.BadVersionException { int version = in.readInt(); - if (version < 1 || version > BACKUP_VERSION) { + if (version < BACKUP_VERSION_1_INIT || version > BACKUP_VERSION) { throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version"); } @@ -862,11 +880,27 @@ public class NetworkTemplate implements Parcelable { String subscriberId = BackupUtils.readString(in); String networkId = BackupUtils.readString(in); - if (!isKnownMatchRule(matchRule)) { + final int metered; + final int subscriberIdMatchRule; + if (version >= BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) { + metered = in.readInt(); + subscriberIdMatchRule = in.readInt(); + } else { + // For backward compatibility, fill the missing filters from match rules. + metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD + || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL; + subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT; + } + + try { + return new NetworkTemplate(matchRule, + subscriberId, new String[] { subscriberId }, + networkId, metered, NetworkStats.ROAMING_ALL, + NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL, + NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule); + } catch (IllegalArgumentException e) { throw new BackupUtils.BadVersionException( - "Restored network template contains unknown match rule " + matchRule); + "Restored network template contains unknown match rule " + matchRule, e); } - - return new NetworkTemplate(matchRule, subscriberId, networkId); } } diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java index 668e96618f34..02cafb5b601e 100644 --- a/core/java/android/net/RssiCurve.java +++ b/core/java/android/net/RssiCurve.java @@ -50,8 +50,10 @@ import java.util.Objects; * the system. * * @see ScoredNetwork + * @deprecated as part of the {@link NetworkScoreManager} deprecation. * @hide */ +@Deprecated @SystemApi public class RssiCurve implements Parcelable { private static final int DEFAULT_ACTIVE_NETWORK_RSSI_BOOST = 25; diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java index 64b3bf1e0aa0..a46bdd9ac37e 100644 --- a/core/java/android/net/ScoredNetwork.java +++ b/core/java/android/net/ScoredNetwork.java @@ -29,8 +29,10 @@ import java.util.Set; /** * A network identifier along with a score for the quality of that network. * + * @deprecated as part of the {@link NetworkScoreManager} deprecation. * @hide */ +@Deprecated @SystemApi public class ScoredNetwork implements Parcelable { diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java index bc9d8c54090a..2e4ea89d4257 100644 --- a/core/java/android/net/WifiKey.java +++ b/core/java/android/net/WifiKey.java @@ -29,8 +29,10 @@ import java.util.regex.Pattern; * Information identifying a Wi-Fi network. * @see NetworkKey * + * @deprecated as part of the {@link NetworkScore} deprecation. * @hide */ +@Deprecated @SystemApi public class WifiKey implements Parcelable { diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java index 80e8579dd73f..557e41a2b103 100644 --- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java +++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java @@ -25,7 +25,6 @@ import android.content.pm.PackageManager; import android.nfc.INfcFCardEmulation; import android.nfc.NfcAdapter; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import java.util.HashMap; diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java index c2b33dd51b0c..f8f7dfe034b5 100644 --- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java +++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java @@ -237,6 +237,7 @@ public final class NfcFServiceInfo implements Parcelable { public String toString() { StringBuilder out = new StringBuilder("NfcFService: "); out.append(getComponent()); + out.append(", UID: " + mUid); out.append(", description: " + mDescription); out.append(", System Code: " + mSystemCode); if (mDynamicSystemCode != null) { @@ -257,6 +258,7 @@ public final class NfcFServiceInfo implements Parcelable { NfcFServiceInfo thatService = (NfcFServiceInfo) o; if (!thatService.getComponent().equals(this.getComponent())) return false; + if (thatService.getUid() != this.getUid()) return false; if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false; if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false; if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false; @@ -321,8 +323,9 @@ public final class NfcFServiceInfo implements Parcelable { }; public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println(" " + getComponent() + - " (Description: " + getDescription() + ")"); + pw.println(" " + getComponent() + + " (Description: " + getDescription() + ")" + + " (UID: " + getUid() + ")"); pw.println(" System Code: " + getSystemCode()); pw.println(" NFCID2: " + getNfcid2()); pw.println(" T3tPmm: " + getT3tPmm()); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a4a76a8831a1..53484d2a0618 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -7354,9 +7354,11 @@ public abstract class BatteryStats implements Parcelable { pw.print(getHistoryTagPoolUid(i)); pw.print(",\""); String str = getHistoryTagPoolString(i); - str = str.replace("\\", "\\\\"); - str = str.replace("\"", "\\\""); - pw.print(str); + if (str != null) { + str = str.replace("\\", "\\\\"); + str = str.replace("\"", "\\\""); + pw.print(str); + } pw.print("\""); pw.println(); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 743468a6dd08..35b9ccc83e0b 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1294,6 +1294,18 @@ public class Build { public static class Partition { /** The name identifying the system partition. */ public static final String PARTITION_NAME_SYSTEM = "system"; + /** @hide */ + public static final String PARTITION_NAME_BOOTIMAGE = "bootimage"; + /** @hide */ + public static final String PARTITION_NAME_ODM = "odm"; + /** @hide */ + public static final String PARTITION_NAME_OEM = "oem"; + /** @hide */ + public static final String PARTITION_NAME_PRODUCT = "product"; + /** @hide */ + public static final String PARTITION_NAME_SYSTEM_EXT = "system_ext"; + /** @hide */ + public static final String PARTITION_NAME_VENDOR = "vendor"; private final String mName; private final String mFingerprint; @@ -1350,8 +1362,12 @@ public class Build { ArrayList<Partition> partitions = new ArrayList(); String[] names = new String[] { - "bootimage", "odm", "product", "system_ext", Partition.PARTITION_NAME_SYSTEM, - "vendor" + Partition.PARTITION_NAME_BOOTIMAGE, + Partition.PARTITION_NAME_ODM, + Partition.PARTITION_NAME_PRODUCT, + Partition.PARTITION_NAME_SYSTEM_EXT, + Partition.PARTITION_NAME_SYSTEM, + Partition.PARTITION_NAME_VENDOR }; for (String name : names) { String fingerprint = SystemProperties.get("ro." + name + ".build.fingerprint"); diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java index aff55aff39f7..5f2c11313940 100644 --- a/core/java/android/os/CombinedVibration.java +++ b/core/java/android/os/CombinedVibration.java @@ -110,6 +110,20 @@ public abstract class CombinedVibration implements Parcelable { @TestApi public abstract long getDuration(); + /** + * Returns true if this effect could represent a touch haptic feedback. + * + * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified + * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN, + * then this method will be used to classify the most common use case and make sure they are + * covered by the user settings for "Touch feedback". + * + * @hide + */ + public boolean isHapticFeedbackCandidate() { + return false; + } + /** @hide */ public abstract void validate(); @@ -314,6 +328,12 @@ public abstract class CombinedVibration implements Parcelable { /** @hide */ @Override + public boolean isHapticFeedbackCandidate() { + return mEffect.isHapticFeedbackCandidate(); + } + + /** @hide */ + @Override public void validate() { mEffect.validate(); } @@ -431,6 +451,17 @@ public abstract class CombinedVibration implements Parcelable { /** @hide */ @Override + public boolean isHapticFeedbackCandidate() { + for (int i = 0; i < mEffects.size(); i++) { + if (!mEffects.valueAt(i).isHapticFeedbackCandidate()) { + return false; + } + } + return true; + } + + /** @hide */ + @Override public void validate() { Preconditions.checkArgument(mEffects.size() > 0, "There should be at least one effect set for a combined effect"); @@ -513,6 +544,9 @@ public abstract class CombinedVibration implements Parcelable { */ @TestApi public static final class Sequential extends CombinedVibration { + // If a vibration is playing more than 3 effects, it's probably not haptic feedback + private static final long MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE = 3; + private final List<CombinedVibration> mEffects; private final List<Integer> mDelays; @@ -575,6 +609,21 @@ public abstract class CombinedVibration implements Parcelable { /** @hide */ @Override + public boolean isHapticFeedbackCandidate() { + final int effectCount = mEffects.size(); + if (effectCount > MAX_HAPTIC_FEEDBACK_SEQUENCE_SIZE) { + return false; + } + for (int i = 0; i < effectCount; i++) { + if (!mEffects.get(i).isHapticFeedbackCandidate()) { + return false; + } + } + return true; + } + + /** @hide */ + @Override public void validate() { Preconditions.checkArgument(mEffects.size() > 0, "There should be at least one effect set for a combined effect"); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 50ca9ff3576f..3f4216406b54 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -60,6 +60,8 @@ interface IUserManager { List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated); List<UserInfo> getProfiles(int userId, boolean enabledOnly); int[] getProfileIds(int userId, boolean enabledOnly); + boolean isUserTypeEnabled(in String userType); + boolean canAddMoreUsersOfType(in String userType); boolean canAddMoreProfilesToUser(in String userType, int userId, boolean allowedToRemoveOne); boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne); UserInfo getProfileParent(int userId); diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 92861fba40a3..1e424d1194ae 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -1,18 +1,6 @@ # Haptics -per-file CombinedVibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file CombinedVibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file ExternalVibration.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file ExternalVibration.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file IExternalVibrationController.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file IExternalVibratorService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file IVibratorManagerService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file NullVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file SystemVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file SystemVibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file VibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file VibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file Vibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS -per-file VibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS +per-file *Vibration* = file:/services/core/java/com/android/server/vibrator/OWNERS +per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS # PowerManager per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index a82826889908..d0d6cb76280a 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -196,9 +196,6 @@ public class SystemVibrator extends Vibrator { return; } CombinedVibration combinedEffect = CombinedVibration.createParallel(effect); - // TODO(b/185351540): move this into VibratorManagerService once the touch vibration - // heuristics is fixed and works for CombinedVibration. Make sure it's always applied. - attributes = new VibrationAttributes.Builder(attributes, effect).build(); mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes); } diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index e5622a35b138..c690df2e3d31 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -223,9 +223,6 @@ public class SystemVibratorManager extends VibratorManager { CombinedVibration combined = CombinedVibration.startParallel() .addVibrator(mVibratorInfo.getId(), vibe) .combine(); - // TODO(b/185351540): move this into VibratorManagerService once the touch vibration - // heuristics is fixed and works for CombinedVibration. Make sure it's always applied. - attributes = new VibrationAttributes.Builder(attributes, vibe).build(); SystemVibratorManager.this.vibrate(uid, opPkg, combined, reason, attributes); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index cf4ce9b43cf2..bc6dbd8c098f 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -308,6 +308,25 @@ public class UserManager { public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering"; /** + * Specifies if users are disallowed from sharing Wi-Fi for admin configured networks. + * + * <p>Device owner and profile owner can set this restriction. + * When it is set by any of these owners, it prevents all users from + * sharing Wi-Fi for networks configured by these owners. + * Other networks not configured by these owners are not affected. + * + * <p>The default value is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = + "no_sharing_admin_configured_wifi"; + + /** * Specifies if a user is disallowed from changing the device * language. The default value is <code>false</code>. * @@ -1478,6 +1497,9 @@ public class UserManager { DISALLOW_CAMERA_TOGGLE, KEY_RESTRICTIONS_PENDING, DISALLOW_BIOMETRIC, + DISALLOW_CHANGE_WIFI_STATE, + DISALLOW_WIFI_TETHERING, + DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} @@ -3854,13 +3876,19 @@ public class UserManager { } /** - * Checks whether it's possible to add more users. Caller must hold the MANAGE_USERS - * permission. + * Checks whether it's possible to add more users. * * @return true if more users can be added, false if limit has been reached. + * + * @deprecated use {@link #canAddMoreUsers(String)} instead. + * * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @Deprecated + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public boolean canAddMoreUsers() { // TODO(b/142482943): UMS has different logic, excluding Demo and Profile from counting. Why // not here? The logic is inconsistent. See UMS.canAddMoreManagedProfiles @@ -3877,6 +3905,25 @@ public class UserManager { } /** + * Checks whether it is possible to add more users of the given user type. + * + * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}. + * @return true if more users of the given type can be added, otherwise false. + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + public boolean canAddMoreUsers(@NonNull String userType) { + try { + return canAddMoreUsers() && mService.canAddMoreUsersOfType(userType); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Checks whether it's possible to add more managed profiles. Caller must hold the MANAGE_USERS * permission. * if allowedToRemoveOne is true and if the user already has a managed profile, then return if @@ -3911,6 +3958,25 @@ public class UserManager { } /** + * Checks whether this device supports users of the given user type. + * + * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_SECONDARY}. + * @return true if the creation of users of the given user type is enabled on this device. + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + public boolean isUserTypeEnabled(@NonNull String userType) { + try { + return mService.isUserTypeEnabled(userType); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Returns list of the profiles of userId including userId itself. * Note that this returns both enabled and not enabled profiles. See * {@link #getEnabledProfiles(int)} if you need only the enabled ones. diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index e986036e411a..9612ca6addcd 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -21,9 +21,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.media.AudioAttributes; -import android.os.vibrator.PrebakedSegment; -import android.os.vibrator.VibrationEffectSegment; -import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -81,6 +78,11 @@ public final class VibrationAttributes implements Parcelable { * actions, such as emulation of physical effects, and texting feedback vibration. */ public static final int USAGE_CLASS_FEEDBACK = 0x2; + /** + * Vibration usage class value to use when the vibration is part of media, such as music, movie, + * soundtrack, game or animations. + */ + public static final int USAGE_CLASS_MEDIA = 0x3; /** * Mask for vibration usage class value. @@ -121,6 +123,15 @@ public final class VibrationAttributes implements Parcelable { * such as a fingerprint sensor. */ public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK; + /** + * Usage value to use for accessibility vibrations, such as with a screen reader. + */ + public static final int USAGE_ACCESSIBILITY = 0x40 | USAGE_CLASS_FEEDBACK; + /** + * Usage value to use for media vibrations, such as music, movie, soundtrack, animations, games, + * or any interactive media that isn't for touch feedback specifically. + */ + public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA; /** * @hide @@ -142,9 +153,6 @@ public final class VibrationAttributes implements Parcelable { */ public static final int FLAG_ALL_SUPPORTED = FLAG_BYPASS_INTERRUPTION_POLICY; - // If a vibration is playing for longer than 5s, it's probably not haptic feedback - private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000; - /** Creates a new {@link VibrationAttributes} instance with given usage. */ public static @NonNull VibrationAttributes createForUsage(int usage) { return new VibrationAttributes.Builder().setUsage(usage).build(); @@ -208,13 +216,17 @@ public final class VibrationAttributes implements Parcelable { case USAGE_NOTIFICATION: return AudioAttributes.USAGE_NOTIFICATION; case USAGE_COMMUNICATION_REQUEST: - return AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST; + return AudioAttributes.USAGE_VOICE_COMMUNICATION; case USAGE_RINGTONE: return AudioAttributes.USAGE_NOTIFICATION_RINGTONE; case USAGE_TOUCH: return AudioAttributes.USAGE_ASSISTANCE_SONIFICATION; case USAGE_ALARM: return AudioAttributes.USAGE_ALARM; + case USAGE_ACCESSIBILITY: + return AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY; + case USAGE_MEDIA: + return AudioAttributes.USAGE_MEDIA; default: return AudioAttributes.USAGE_UNKNOWN; } @@ -286,12 +298,16 @@ public final class VibrationAttributes implements Parcelable { return "UNKNOWN"; case USAGE_ALARM: return "ALARM"; + case USAGE_ACCESSIBILITY: + return "ACCESSIBILITY"; case USAGE_RINGTONE: return "RINGTONE"; case USAGE_NOTIFICATION: return "NOTIFICATION"; case USAGE_COMMUNICATION_REQUEST: return "COMMUNICATION_REQUEST"; + case USAGE_MEDIA: + return "MEDIA"; case USAGE_TOUCH: return "TOUCH"; case USAGE_PHYSICAL_EMULATION: @@ -337,67 +353,6 @@ public final class VibrationAttributes implements Parcelable { setFlags(audio); } - /** - * Constructs a new Builder from AudioAttributes and a VibrationEffect to infer usage. - * @hide - */ - @TestApi - public Builder(@NonNull AudioAttributes audio, @NonNull VibrationEffect effect) { - this(audio); - applyHapticFeedbackHeuristics(effect); - } - - /** - * Constructs a new Builder from VibrationAttributes and a VibrationEffect to infer usage. - * @hide - */ - @TestApi - public Builder(@NonNull VibrationAttributes vib, @NonNull VibrationEffect effect) { - this(vib); - applyHapticFeedbackHeuristics(effect); - } - - private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) { - if (effect != null) { - PrebakedSegment prebaked = extractPrebakedSegment(effect); - if (mUsage == USAGE_UNKNOWN && prebaked != null) { - switch (prebaked.getEffectId()) { - case VibrationEffect.EFFECT_CLICK: - case VibrationEffect.EFFECT_DOUBLE_CLICK: - case VibrationEffect.EFFECT_HEAVY_CLICK: - case VibrationEffect.EFFECT_TEXTURE_TICK: - case VibrationEffect.EFFECT_TICK: - case VibrationEffect.EFFECT_POP: - case VibrationEffect.EFFECT_THUD: - mUsage = USAGE_TOUCH; - break; - default: - Slog.w(TAG, "Unknown prebaked vibration effect, assuming it isn't " - + "haptic feedback"); - } - } - final long duration = effect.getDuration(); - if (mUsage == USAGE_UNKNOWN && duration >= 0 - && duration < MAX_HAPTIC_FEEDBACK_DURATION) { - mUsage = USAGE_TOUCH; - } - } - } - - @Nullable - private PrebakedSegment extractPrebakedSegment(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Composed) { - VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; - if (composed.getSegments().size() == 1) { - VibrationEffectSegment segment = composed.getSegments().get(0); - if (segment instanceof PrebakedSegment) { - return (PrebakedSegment) segment; - } - } - } - return null; - } - private void setUsage(@NonNull AudioAttributes audio) { mOriginalAudioUsage = audio.getUsage(); switch (audio.getUsage()) { @@ -405,21 +360,31 @@ public final class VibrationAttributes implements Parcelable { case AudioAttributes.USAGE_NOTIFICATION_EVENT: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED: case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT: + case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: mUsage = USAGE_NOTIFICATION; break; - case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST: - case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY: + case AudioAttributes.USAGE_VOICE_COMMUNICATION: + case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING: + case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: + case AudioAttributes.USAGE_ASSISTANT: mUsage = USAGE_COMMUNICATION_REQUEST; break; case AudioAttributes.USAGE_NOTIFICATION_RINGTONE: mUsage = USAGE_RINGTONE; break; + case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY: + mUsage = USAGE_ACCESSIBILITY; + break; case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION: mUsage = USAGE_TOUCH; break; case AudioAttributes.USAGE_ALARM: mUsage = USAGE_ALARM; break; + case AudioAttributes.USAGE_MEDIA: + case AudioAttributes.USAGE_GAME: + mUsage = USAGE_MEDIA; + break; default: mUsage = USAGE_UNKNOWN; } @@ -450,6 +415,8 @@ public final class VibrationAttributes implements Parcelable { * {@link VibrationAttributes#USAGE_TOUCH}, * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION}, * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}. + * {@link VibrationAttributes#USAGE_ACCESSIBILITY}. + * {@link VibrationAttributes#USAGE_MEDIA}. * @return the same Builder instance. */ public @NonNull Builder setUsage(int usage) { diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index a0cbbfe3327b..5758a4edec31 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -53,7 +53,10 @@ import java.util.Objects; public abstract class VibrationEffect implements Parcelable { // Stevens' coefficient to scale the perceived vibration intensity. private static final float SCALE_GAMMA = 0.65f; - + // If a vibration is playing for longer than 1s, it's probably not haptic feedback + private static final long MAX_HAPTIC_FEEDBACK_DURATION = 1000; + // If a vibration is playing more than 3 constants, it's probably not haptic feedback + private static final long MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE = 3; /** * The default vibration strength of the device. @@ -439,6 +442,20 @@ public abstract class VibrationEffect implements Parcelable { public abstract long getDuration(); /** + * Returns true if this effect could represent a touch haptic feedback. + * + * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified + * for each vibration, with the correct usage. When a vibration is played with usage UNKNOWN, + * then this method will be used to classify the most common use case and make sure they are + * covered by the user settings for "Touch feedback". + * + * @hide + */ + public boolean isHapticFeedbackCandidate() { + return false; + } + + /** * Resolve default values into integer amplitude numbers. * * @param defaultAmplitude the default amplitude to apply, must be between 0 and @@ -582,6 +599,7 @@ public abstract class VibrationEffect implements Parcelable { return mRepeatIndex; } + /** @hide */ @Override public void validate() { int segmentCount = mSegments.size(); @@ -620,6 +638,37 @@ public abstract class VibrationEffect implements Parcelable { return totalDuration; } + /** @hide */ + @Override + public boolean isHapticFeedbackCandidate() { + long totalDuration = getDuration(); + if (totalDuration > MAX_HAPTIC_FEEDBACK_DURATION) { + // Vibration duration is known and is longer than the max duration used to classify + // haptic feedbacks (or repeating indefinitely with duration == Long.MAX_VALUE). + return false; + } + int segmentCount = mSegments.size(); + if (segmentCount > MAX_HAPTIC_FEEDBACK_COMPOSITION_SIZE) { + // Vibration has some prebaked or primitive constants, it should be limited to the + // max composition size used to classify haptic feedbacks. + return false; + } + totalDuration = 0; + for (int i = 0; i < segmentCount; i++) { + if (!mSegments.get(i).isHapticFeedbackCandidate()) { + // There is at least one segment that is not a candidate for a haptic feedback. + return false; + } + long segmentDuration = mSegments.get(i).getDuration(); + if (segmentDuration > 0) { + totalDuration += segmentDuration; + } + } + // Vibration might still have some ramp or step segments, check the known duration. + return totalDuration <= MAX_HAPTIC_FEEDBACK_DURATION; + } + + /** @hide */ @NonNull @Override public Composed resolve(int defaultAmplitude) { @@ -636,6 +685,7 @@ public abstract class VibrationEffect implements Parcelable { return resolved; } + /** @hide */ @NonNull @Override public Composed scale(float scaleFactor) { @@ -652,6 +702,7 @@ public abstract class VibrationEffect implements Parcelable { return scaled; } + /** @hide */ @NonNull @Override public Composed applyEffectStrength(int effectStrength) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 75234db0ea03..c67c82e37cd2 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -493,7 +493,7 @@ public abstract class Vibrator { vibrate(vibe, attributes == null ? new VibrationAttributes.Builder().build() - : new VibrationAttributes.Builder(attributes, vibe).build()); + : new VibrationAttributes.Builder(attributes).build()); } /** diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index f57d1574d399..39a2e1353237 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -56,11 +56,12 @@ import java.util.UUID; * <ul> * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external * storage, historically found at {@code /sdcard}. - * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party - * apps for direct filesystem access. The system should send out relevant - * storage broadcasts and index any media on visible volumes. Visible volumes - * are considered a more stable part of the device, which is why we take the - * time to index them. In particular, transient volumes like USB OTG devices + * <li>{@link #MOUNT_FLAG_VISIBLE_FOR_READ} and + * {@link #MOUNT_FLAG_VISIBLE_FOR_WRITE} mean the volume is visible to + * third-party apps for direct filesystem access. The system should send out + * relevant storage broadcasts and index any media on visible volumes. Visible + * volumes are considered a more stable part of the device, which is why we take + * the time to index them. In particular, transient volumes like USB OTG devices * <em>should not</em> be marked as visible; their contents should be surfaced * to apps through the Storage Access Framework. * </ul> @@ -100,7 +101,8 @@ public class VolumeInfo implements Parcelable { public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL; public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY; - public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE; + public static final int MOUNT_FLAG_VISIBLE_FOR_READ = IVold.MOUNT_FLAG_VISIBLE_FOR_READ; + public static final int MOUNT_FLAG_VISIBLE_FOR_WRITE = IVold.MOUNT_FLAG_VISIBLE_FOR_WRITE; private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); @@ -312,17 +314,31 @@ public class VolumeInfo implements Parcelable { return isPrimary() && (getType() == TYPE_PUBLIC); } + private boolean isVisibleForRead() { + return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_READ) != 0; + } + + private boolean isVisibleForWrite() { + return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_WRITE) != 0; + } + @UnsupportedAppUsage public boolean isVisible() { - return (mountFlags & MOUNT_FLAG_VISIBLE) != 0; + return isVisibleForRead() || isVisibleForWrite(); } - public boolean isVisibleForUser(int userId) { - if ((type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED) - && mountUserId == userId) { - return isVisible(); + private boolean isVolumeSupportedForUser(int userId) { + if (mountUserId != userId) { + return false; } - return false; + return type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED; + } + + /** + * Returns {@code true} if this volume is visible for {@code userId}, {@code false} otherwise. + */ + public boolean isVisibleForUser(int userId) { + return isVolumeSupportedForUser(userId) && isVisible(); } /** @@ -335,12 +351,12 @@ public class VolumeInfo implements Parcelable { } public boolean isVisibleForRead(int userId) { - return isVisibleForUser(userId); + return isVolumeSupportedForUser(userId) && isVisibleForRead(); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isVisibleForWrite(int userId) { - return isVisibleForUser(userId); + return isVolumeSupportedForUser(userId) && isVisibleForWrite(); } @UnsupportedAppUsage diff --git a/core/java/android/os/vibrator/OWNERS b/core/java/android/os/vibrator/OWNERS new file mode 100644 index 000000000000..b54d6bf07818 --- /dev/null +++ b/core/java/android/os/vibrator/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
\ No newline at end of file diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java index 78b43468d663..30f5a5ca86c5 100644 --- a/core/java/android/os/vibrator/PrebakedSegment.java +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -67,17 +67,38 @@ public final class PrebakedSegment extends VibrationEffectSegment { return -1; } + /** @hide */ + @Override + public boolean isHapticFeedbackCandidate() { + switch (mEffectId) { + case VibrationEffect.EFFECT_CLICK: + case VibrationEffect.EFFECT_DOUBLE_CLICK: + case VibrationEffect.EFFECT_HEAVY_CLICK: + case VibrationEffect.EFFECT_POP: + case VibrationEffect.EFFECT_TEXTURE_TICK: + case VibrationEffect.EFFECT_THUD: + case VibrationEffect.EFFECT_TICK: + return true; + default: + // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback + return false; + } + } + + /** @hide */ @Override public boolean hasNonZeroAmplitude() { return true; } + /** @hide */ @NonNull @Override public PrebakedSegment resolve(int defaultAmplitude) { return this; } + /** @hide */ @NonNull @Override public PrebakedSegment scale(float scaleFactor) { @@ -85,6 +106,7 @@ public final class PrebakedSegment extends VibrationEffectSegment { return this; } + /** @hide */ @NonNull @Override public PrebakedSegment applyEffectStrength(int effectStrength) { @@ -105,16 +127,17 @@ public final class PrebakedSegment extends VibrationEffectSegment { } } + /** @hide */ @Override public void validate() { switch (mEffectId) { case VibrationEffect.EFFECT_CLICK: case VibrationEffect.EFFECT_DOUBLE_CLICK: - case VibrationEffect.EFFECT_TICK: + case VibrationEffect.EFFECT_HEAVY_CLICK: + case VibrationEffect.EFFECT_POP: case VibrationEffect.EFFECT_TEXTURE_TICK: case VibrationEffect.EFFECT_THUD: - case VibrationEffect.EFFECT_POP: - case VibrationEffect.EFFECT_HEAVY_CLICK: + case VibrationEffect.EFFECT_TICK: break; default: int[] ringtones = VibrationEffect.RINGTONES; diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java index 2ef29cb26ebc..58ca97860737 100644 --- a/core/java/android/os/vibrator/PrimitiveSegment.java +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -67,18 +67,27 @@ public final class PrimitiveSegment extends VibrationEffectSegment { return -1; } + /** @hide */ + @Override + public boolean isHapticFeedbackCandidate() { + return true; + } + + /** @hide */ @Override public boolean hasNonZeroAmplitude() { // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0. return true; } + /** @hide */ @NonNull @Override public PrimitiveSegment resolve(int defaultAmplitude) { return this; } + /** @hide */ @NonNull @Override public PrimitiveSegment scale(float scaleFactor) { @@ -86,12 +95,14 @@ public final class PrimitiveSegment extends VibrationEffectSegment { mDelay); } + /** @hide */ @NonNull @Override public PrimitiveSegment applyEffectStrength(int effectStrength) { return this; } + /** @hide */ @Override public void validate() { Preconditions.checkArgumentInRange(mPrimitiveId, VibrationEffect.Composition.PRIMITIVE_NOOP, diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java index aad87c5cd4e7..3ec56366d921 100644 --- a/core/java/android/os/vibrator/RampSegment.java +++ b/core/java/android/os/vibrator/RampSegment.java @@ -87,11 +87,19 @@ public final class RampSegment extends VibrationEffectSegment { return mDuration; } + /** @hide */ + @Override + public boolean isHapticFeedbackCandidate() { + return true; + } + + /** @hide */ @Override public boolean hasNonZeroAmplitude() { return mStartAmplitude > 0 || mEndAmplitude > 0; } + /** @hide */ @Override public void validate() { Preconditions.checkArgumentNonnegative(mDuration, @@ -100,7 +108,7 @@ public final class RampSegment extends VibrationEffectSegment { Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude"); } - + /** @hide */ @NonNull @Override public RampSegment resolve(int defaultAmplitude) { @@ -108,6 +116,7 @@ public final class RampSegment extends VibrationEffectSegment { return this; } + /** @hide */ @NonNull @Override public RampSegment scale(float scaleFactor) { @@ -121,6 +130,7 @@ public final class RampSegment extends VibrationEffectSegment { mDuration); } + /** @hide */ @NonNull @Override public RampSegment applyEffectStrength(int effectStrength) { diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java index 11209e0ee425..69a381f5c558 100644 --- a/core/java/android/os/vibrator/StepSegment.java +++ b/core/java/android/os/vibrator/StepSegment.java @@ -73,12 +73,20 @@ public final class StepSegment extends VibrationEffectSegment { return mDuration; } + /** @hide */ + @Override + public boolean isHapticFeedbackCandidate() { + return true; + } + + /** @hide */ @Override public boolean hasNonZeroAmplitude() { // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later. return Float.compare(mAmplitude, 0) != 0; } + /** @hide */ @Override public void validate() { Preconditions.checkArgumentNonnegative(mDuration, @@ -88,6 +96,7 @@ public final class StepSegment extends VibrationEffectSegment { } } + /** @hide */ @NonNull @Override public StepSegment resolve(int defaultAmplitude) { @@ -103,6 +112,7 @@ public final class StepSegment extends VibrationEffectSegment { mDuration); } + /** @hide */ @NonNull @Override public StepSegment scale(float scaleFactor) { @@ -113,6 +123,7 @@ public final class StepSegment extends VibrationEffectSegment { mDuration); } + /** @hide */ @NonNull @Override public StepSegment applyEffectStrength(int effectStrength) { diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java index 5b42845cfa43..979c4472eb9b 100644 --- a/core/java/android/os/vibrator/VibrationEffectSegment.java +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -57,10 +57,26 @@ public abstract class VibrationEffectSegment implements Parcelable { */ public abstract long getDuration(); - /** Returns true if this segment plays at a non-zero amplitude at some point. */ + /** + * Returns true if this segment could be a haptic feedback effect candidate. + * + * @see VibrationEffect#isHapticFeedbackCandidate() + * @hide + */ + public abstract boolean isHapticFeedbackCandidate(); + + /** + * Returns true if this segment plays at a non-zero amplitude at some point. + * + * @hide + */ public abstract boolean hasNonZeroAmplitude(); - /** Validates the segment, throwing exceptions if any parameter is invalid. */ + /** + * Validates the segment, throwing exceptions if any parameter is invalid. + * + * @hide + */ public abstract void validate(); /** @@ -68,6 +84,8 @@ public abstract class VibrationEffectSegment implements Parcelable { * * <p>This might fail with {@link IllegalArgumentException} if value is non-positive or larger * than {@link VibrationEffect#MAX_AMPLITUDE}. + * + * @hide */ @NonNull public abstract <T extends VibrationEffectSegment> T resolve(int defaultAmplitude); @@ -77,6 +95,8 @@ public abstract class VibrationEffectSegment implements Parcelable { * * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will * scale down the intensity, values larger than 1 will scale up + * + * @hide */ @NonNull public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor); @@ -86,6 +106,8 @@ public abstract class VibrationEffectSegment implements Parcelable { * * @param effectStrength new effect strength to be applied, one of * VibrationEffect.EFFECT_STRENGTH_*. + * + * @hide */ @NonNull public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ae09b45a8f99..7979256acc67 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -54,6 +54,7 @@ import android.database.Cursor; import android.database.SQLException; import android.location.ILocationManager; import android.location.LocationManager; +import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.Uri; @@ -269,6 +270,16 @@ public final class Settings { public static final String EXTRA_NETWORK_TEMPLATE = "network_template"; /** + * Activity Action: Show One-handed mode Settings page. + * <p> + * Input: Nothing + * <p> + * Output: Nothing + * @hide + */ + public static final String ACTION_ONE_HANDED_SETTINGS = + "android.settings.action.ONE_HANDED_SETTINGS"; + /** * The return values for {@link Settings.Config#set} * @hide */ @@ -3622,6 +3633,12 @@ public final class Settings { private static boolean putStringForUser(ContentResolver resolver, String name, String value, int userHandle, boolean overrideableByRestore) { + return putStringForUser(resolver, name, value, /* tag= */ null, + /* makeDefault= */ false, userHandle, overrideableByRestore); + } + + private static boolean putStringForUser(ContentResolver resolver, String name, String value, + String tag, boolean makeDefault, int userHandle, boolean overrideableByRestore) { if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); @@ -3632,8 +3649,8 @@ public final class Settings { + " to android.provider.Settings.Global, value is unchanged."); return false; } - return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle, - overrideableByRestore); + return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault, + userHandle, overrideableByRestore); } /** @@ -4469,6 +4486,15 @@ public final class Settings { public static final String VIBRATE_ON = "vibrate_on"; /** + * Whether applying ramping ringer on incoming phone call ringtone. + * <p>1 = apply ramping ringer + * <p>0 = do not apply ramping ringer + * @hide + */ + @Readable + public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer"; + + /** * If 1, redirects the system vibrator to all currently attached input devices * that support vibration. If there are no such input devices, then the system * vibrator is used instead. @@ -4537,6 +4563,25 @@ public final class Settings { "haptic_feedback_intensity"; /** + * The intensity of haptic feedback vibrations for interaction with hardware components from + * the device, like buttons and sensors, if configurable. + * + * Not all devices are capable of changing their feedback intensity; on these devices + * there will likely be no difference between the various vibration intensities except for + * intensity 0 (off) and the rest. + * + * <b>Values:</b><br/> + * 0 - Vibration is disabled<br/> + * 1 - Weak vibrations<br/> + * 2 - Medium vibrations<br/> + * 3 - Strong vibrations + * @hide + */ + @Readable + public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY = + "hardware_haptic_feedback_intensity"; + + /** * Ringer volume. This is used internally, changing this value will not * change the volume. See AudioManager. * @@ -5319,6 +5364,7 @@ public final class Settings { PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED); PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS); PUBLIC_SETTINGS.add(VIBRATE_WHEN_RINGING); + PUBLIC_SETTINGS.add(APPLY_RAMPING_RINGER); } /** @@ -5856,6 +5902,10 @@ public final class Settings { } /** @hide */ + public static void getMovedToSystemSettings(Set<String> outKeySet) { + } + + /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); @@ -10453,7 +10503,9 @@ public final class Settings { * Whether applying ramping ringer on incoming phone call ringtone. * <p>1 = apply ramping ringer * <p>0 = do not apply ramping ringer + * @deprecated Use {@link AudioManager#isRampingRingerEnabled()} instead */ + @Deprecated @Readable public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer"; @@ -11962,8 +12014,10 @@ public final class Settings { * Value to specify whether network quality scores and badging should be shown in the UI. * * Type: int (0 for false, 1 for true) + * @deprecated {@code NetworkScoreManager} is deprecated. * @hide */ + @Deprecated @Readable public static final String NETWORK_SCORING_UI_ENABLED = "network_scoring_ui_enabled"; @@ -11972,8 +12026,10 @@ public final class Settings { * when generating SSID only bases score curves. * * Type: long + * @deprecated {@code NetworkScoreManager} is deprecated. * @hide */ + @Deprecated @Readable public static final String SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS = "speed_label_cache_eviction_age_millis"; @@ -12006,8 +12062,10 @@ public final class Settings { * {@link NetworkScoreManager#setActiveScorer(String)} to write it. * * Type: string - package name + * @deprecated {@code NetworkScoreManager} is deprecated. * @hide */ + @Deprecated @Readable public static final String NETWORK_RECOMMENDATIONS_PACKAGE = "network_recommendations_package"; @@ -12017,8 +12075,10 @@ public final class Settings { * networks automatically. * * Type: string package name or null if the feature is either not provided or disabled. + * @deprecated {@code NetworkScoreManager} is deprecated. * @hide */ + @Deprecated @TestApi @Readable public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; @@ -12028,8 +12088,10 @@ public final class Settings { * {@link com.android.server.wifi.RecommendedNetworkEvaluator}. * * Type: long + * @deprecated {@code NetworkScoreManager} is deprecated. * @hide */ + @Deprecated @Readable public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS = "recommended_network_evaluator_cache_expiry_ms"; @@ -15232,12 +15294,24 @@ public final class Settings { MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES); } + // Certain settings have been moved from global to the per-user system namespace + private static final HashSet<String> MOVED_TO_SYSTEM; + static { + MOVED_TO_SYSTEM = new HashSet<>(1); + MOVED_TO_SYSTEM.add(Global.APPLY_RAMPING_RINGER); + } + /** @hide */ public static void getMovedToSecureSettings(Set<String> outKeySet) { outKeySet.addAll(MOVED_TO_SECURE); } /** @hide */ + public static void getMovedToSystemSettings(Set<String> outKeySet) { + outKeySet.addAll(MOVED_TO_SYSTEM); + } + + /** @hide */ public static void clearProviderForTest() { sProviderHolder.clearProviderForTest(); sNameValueCache.clearGenerationTrackerForTest(); @@ -15269,6 +15343,11 @@ public final class Settings { + " to android.provider.Settings.Secure, returning read-only value."); return Secure.getStringForUser(resolver, name, userHandle); } + if (MOVED_TO_SYSTEM.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + + " to android.provider.Settings.System, returning read-only value."); + return System.getStringForUser(resolver, name, userHandle); + } return sNameValueCache.getStringForUser(resolver, name, userHandle); } @@ -15433,6 +15512,13 @@ public final class Settings { return Secure.putStringForUser(resolver, name, value, tag, makeDefault, userHandle, overrideableByRestore); } + // Global and System have the same access policy so we can forward writes + if (MOVED_TO_SYSTEM.contains(name)) { + Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global" + + " to android.provider.Settings.System, value is unchanged."); + return System.putStringForUser(resolver, name, value, tag, + makeDefault, userHandle, overrideableByRestore); + } return sNameValueCache.putStringForUser(resolver, name, value, tag, makeDefault, userHandle, overrideableByRestore); } diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 1b38f590edd6..5d84af051382 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -3584,6 +3584,23 @@ public final class Telephony { "content://telephony/carriers/enforce_managed"); /** + * The {@code content://} style URL for the perferred APN used for internet. + * + * @hide + */ + public static final Uri PREFERRED_APN_URI = Uri.parse( + "content://telephony/carriers/preferapn/subId/"); + + /** + * The {@code content://} style URL for the perferred APN set id. + * + * @hide + */ + public static final Uri PREFERRED_APN_SET_URI = Uri.parse( + "content://telephony/carriers/preferapnset/subId/"); + + + /** * The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced. * @hide */ diff --git a/core/java/android/window/TaskFragmentAppearedInfo.aidl b/core/java/android/security/attestationverification/AttestationProfile.aidl index 3729c09168a6..51696a94cd88 100644 --- a/core/java/android/window/TaskFragmentAppearedInfo.aidl +++ b/core/java/android/security/attestationverification/AttestationProfile.aidl @@ -14,10 +14,9 @@ * limitations under the License. */ -package android.window; +package android.security.attestationverification; /** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide + * {@hide} */ -parcelable TaskFragmentAppearedInfo; +parcelable AttestationProfile; diff --git a/core/java/android/security/attestationverification/AttestationProfile.java b/core/java/android/security/attestationverification/AttestationProfile.java new file mode 100644 index 000000000000..7a43dac87fc5 --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationProfile.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_APP_DEFINED; +import static android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; +import android.security.attestationverification.AttestationVerificationManager.AttestationProfileId; +import android.util.Log; + +import com.android.internal.util.DataClass; + + +/** + * An attestation profile defining the security requirements for verifying the attestation of a + * remote compute environment. + * + * <p>This class is immutable and thread-safe. When checking this profile against an expected + * profile, it is recommended to construct the expected profile and compare them with {@code + * equals()}. + * + * @hide + * @see AttestationVerificationManager + */ +@DataClass( + genConstructor = false, + genEqualsHashCode = true +) +public final class AttestationProfile implements Parcelable { + + private static final String TAG = "AVF"; + + /** + * The ID of a system-defined attestation profile. + * + * See constants in {@link AttestationVerificationManager} prefixed with {@code PROFILE_}. If + * this has the value of {@link AttestationVerificationManager#PROFILE_APP_DEFINED}, then the + * packageName and profileName are non-null. + */ + @AttestationProfileId + private final int mAttestationProfileId; + + /** + * The package name of a app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @Nullable + private final String mPackageName; + + + /** + * The name of an app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @Nullable + private final String mProfileName; + + private AttestationProfile( + @AttestationProfileId int attestationProfileId, + @Nullable String packageName, + @Nullable String profileName) { + mAttestationProfileId = attestationProfileId; + mPackageName = packageName; + mProfileName = profileName; + } + + /** + * Create a profile with the given id. + * + * <p>This constructor is for specifying a profile which is defined by the system. These are + * available as constants in the {@link AttestationVerificationManager} class prefixed with + * {@code PROFILE_}. + * + * @param attestationProfileId the ID of the system-defined profile + * @throws IllegalArgumentException when called with + * {@link AttestationVerificationManager#PROFILE_APP_DEFINED} + * (use {@link #AttestationProfile(String, String)}) + */ + public AttestationProfile(@AttestationProfileId int attestationProfileId) { + this(attestationProfileId, null, null); + if (attestationProfileId == PROFILE_APP_DEFINED) { + throw new IllegalArgumentException("App-defined profiles must be specified with the " + + "constructor AttestationProfile#constructor(String, String)"); + } + } + + /** + * Create a profile with the given package name and profile name. + * + * <p>This constructor is for specifying a profile defined by an app. The packageName must + * match the package name of the app that defines the profile (as specified in the {@code + * package} attribute of the {@code + * <manifest>} tag in the app's manifest. The profile name matches the {@code name} attribute + * of the {@code <attestation-profile>} tag. + * + * <p>Apps must declare profiles in their manifest as an {@code <attestation-profile>} element. + * However, this constructor does not verify that such a profile exists. If the profile does not + * exist, verifications will fail. + * + * @param packageName the package name of the app defining the profile + * @param profileName the name of the profile + */ + public AttestationProfile(@NonNull String packageName, @NonNull String profileName) { + this(PROFILE_APP_DEFINED, packageName, profileName); + if (packageName == null || profileName == null) { + throw new IllegalArgumentException("Both packageName and profileName must be non-null"); + } + } + + @Override + public String toString() { + if (mAttestationProfileId == PROFILE_APP_DEFINED) { + return "AttestationProfile(package=" + mPackageName + ", name=" + mProfileName + ")"; + } else { + String humanReadableProfileId; + switch (mAttestationProfileId) { + case PROFILE_UNKNOWN: + humanReadableProfileId = "PROFILE_UNKNOWN"; + break; + default: + Log.e(TAG, "ERROR: Missing case in AttestationProfile#toString"); + humanReadableProfileId = "ERROR"; + } + return "AttestationProfile(" + humanReadableProfileId + "/" + mAttestationProfileId + + ")"; + } + } + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/security + // /attestationverification/AttestationProfile.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * The ID of a system-defined attestation profile. + * + * See constants in {@link AttestationVerificationManager} prefixed with {@code PROFILE_}. If + * this has the value of {@link AttestationVerificationManager#PROFILE_APP_DEFINED}, then the + * packageName and profileName are non-null. + */ + @DataClass.Generated.Member + public @AttestationProfileId int getAttestationProfileId() { + return mAttestationProfileId; + } + + /** + * The package name of a app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @DataClass.Generated.Member + public @Nullable String getPackageName() { + return mPackageName; + } + + /** + * The name of an app-defined attestation profile. + * + * This value will be null unless the value of attestationProfileId is {@link + * AttestationVerificationManager#PROFILE_APP_DEFINED}. + */ + @DataClass.Generated.Member + public @Nullable String getProfileName() { + return mProfileName; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(AttestationProfile other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + AttestationProfile that = (AttestationProfile) o; + //noinspection PointlessBooleanExpression + return true + && mAttestationProfileId == that.mAttestationProfileId + && java.util.Objects.equals(mPackageName, that.mPackageName) + && java.util.Objects.equals(mProfileName, that.mProfileName); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + mAttestationProfileId; + _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); + _hash = 31 * _hash + java.util.Objects.hashCode(mProfileName); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mPackageName != null) flg |= 0x2; + if (mProfileName != null) flg |= 0x4; + dest.writeByte(flg); + dest.writeInt(mAttestationProfileId); + if (mPackageName != null) dest.writeString(mPackageName); + if (mProfileName != null) dest.writeString(mProfileName); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AttestationProfile(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + int attestationProfileId = in.readInt(); + String packageName = (flg & 0x2) == 0 ? null : in.readString(); + String profileName = (flg & 0x4) == 0 ? null : in.readString(); + + this.mAttestationProfileId = attestationProfileId; + com.android.internal.util.AnnotationValidations.validate( + AttestationProfileId.class, null, mAttestationProfileId); + this.mPackageName = packageName; + this.mProfileName = profileName; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<AttestationProfile> CREATOR + = new Parcelable.Creator<AttestationProfile>() { + @Override + public AttestationProfile[] newArray(int size) { + return new AttestationProfile[size]; + } + + @Override + public AttestationProfile createFromParcel(@NonNull android.os.Parcel in) { + return new AttestationProfile(in); + } + }; + + @DataClass.Generated( + time = 1633629498403L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/security/attestationverification/AttestationProfile.java", + inputSignatures = "private static final java.lang.String TAG\nprivate final @android.security.attestationverification.AttestationVerificationManager.AttestationProfileId int mAttestationProfileId\nprivate final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mProfileName\npublic @java.lang.Override java.lang.String toString()\nclass AttestationProfile extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java new file mode 100644 index 000000000000..db783ceabcdb --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.content.Context; +import android.os.Bundle; +import android.os.ParcelDuration; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.infra.AndroidFuture; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +/** + * Provides methods for verifying that attestations from remote compute environments meet minimum + * security requirements specified by attestation profiles. + * + * @hide + */ +@SystemService(Context.ATTESTATION_VERIFICATION_SERVICE) +public class AttestationVerificationManager { + + private static final String TAG = "AVF"; + private static final Duration MAX_TOKEN_AGE = Duration.ofHours(1); + + private final Context mContext; + private final IAttestationVerificationManagerService mService; + + /** + * Verifies that {@code attestation} describes a computing environment that meets the + * requirements of {@code profile}, {@code localBindingType}, and {@code requirements}. + * + * <p>This method verifies that at least one system-registered {@linkplain + * AttestationVerificationService attestation verifier} associated with {@code profile} and + * {@code localBindingType} has verified that {@code attestation} attests that the remote + * environment matching the local binding data (determined by {@code localBindingType}) in + * {@code requirements} meets the requirements of the profile. + * + * <p>For successful verification, the {@code requirements} bundle must contain locally-known + * data which must match {@code attestation}. The required data in the bundle is defined by the + * {@code localBindingType} (see documentation for the type). Verifiers will fail to verify the + * attestation if the bundle contains unsupported data. + * + * <p>The {@code localBindingType} specifies how {@code attestation} is bound to a local + * secure channel endpoint or similar connection with the target remote environment described by + * the attestation. The binding is expected to be related to a cryptographic protocol, and each + * binding type requires specific arguments to be present in the {@code requirements} bundle. It + * is this binding to something known locally that ensures an attestation is not only valid, but + * is also associated with a particular connection. + * + * <p>The {@code callback} is called with a result and {@link VerificationToken} (which may be + * null). The result is an integer (see constants in this class with the prefix {@code RESULT_}. + * The result is {@link #RESULT_SUCCESS} when at least one verifier has passed its checks. The + * token may be used in calls to other parts of the system. + * + * <p>It's expected that a verifier will be able to decode and understand the passed values, + * otherwise fail to verify. {@code attestation} should contain some type data to prevent parse + * errors. + * + * <p>The values put into the {@code requirements} Bundle depend on the {@code + * localBindingType} used. + * + * @param profile the attestation profile which defines the security requirements which + * must be met by the environment described by {@code attestation} + * @param localBindingType the type of the local binding data; see constants in this class with + * the prefix {@code TYPE_} + * @param requirements a {@link Bundle} containing locally-known data which must match + * {@code attestation} + * @param attestation attestation data which describes a remote computing environment + * @param executor {@code callback} will be executed on this executor + * @param callback will be called with the results of the verification + * @see AttestationVerificationService + */ + @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE) + public void verifyAttestation( + @NonNull AttestationProfile profile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @NonNull byte[] attestation, + @NonNull @CallbackExecutor Executor executor, + @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback) { + try { + AndroidFuture<IVerificationResult> resultCallback = new AndroidFuture<>(); + resultCallback.thenAccept(result -> { + Log.d(TAG, "verifyAttestation result: " + result.resultCode + " / " + result.token); + executor.execute(() -> { + callback.accept(result.resultCode, result.token); + }); + }); + + mService.verifyAttestation(profile, localBindingType, requirements, attestation, + resultCallback); + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Verifies that {@code token} is a valid token, returning the result contained in valid + * tokens. + * + * <p>This verifies that the token was issued by the platform and thus the system verified + * attestation data against the specified {@code profile}, {@code localBindingType}, and {@code + * requirements}. The value returned by this method is the same as the one originally returned + * when the token was generated. Callers of this method should not trust the provider of the + * token to also specify the profile, local binding type, or requirements, but instead have + * their own security requirements about these arguments. + * + * <p>This method, in contrast to {@code verifyAttestation}, executes synchronously and only + * checks that a previous verification succeeded. This allows callers to pass the token to + * others, including system APIs, without those components needing to re-verify the attestation + * data, an operation which can take several seconds. + * + * <p>When {@code maximumAge} is not specified (null), this method verifies the token was + * generated in the past hour. Otherwise, it verifies the token was generated between now and + * {@code maximumAge} ago. The maximum value of {@code maximumAge} is one hour; specifying a + * duration greater than one hour will result in an {@link IllegalArgumentException}. + * + * @param profile the attestation profile which must be in the token + * @param localBindingType the local binding type which must be in the token + * @param requirements the requirements which must be in the token + * @param token the token to be verified + * @param maximumAge the maximum age to accept for the token + */ + @RequiresPermission(Manifest.permission.USE_ATTESTATION_VERIFICATION_SERVICE) + @CheckResult + @VerificationResult + public int verifyToken( + @NonNull AttestationProfile profile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @NonNull VerificationToken token, + @Nullable Duration maximumAge) { + Duration usedMaximumAge; + if (maximumAge == null) { + usedMaximumAge = MAX_TOKEN_AGE; + } else { + if (maximumAge.compareTo(MAX_TOKEN_AGE) > 0) { + throw new IllegalArgumentException( + "maximumAge cannot be greater than " + MAX_TOKEN_AGE + "; was " + + maximumAge); + } + usedMaximumAge = maximumAge; + } + + try { + AndroidFuture<Integer> resultCallback = new AndroidFuture<>(); + resultCallback.orTimeout(5, TimeUnit.SECONDS); + + mService.verifyToken(token, new ParcelDuration(usedMaximumAge), resultCallback); + return resultCallback.get(); // block on result callback + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Throwable t) { + throw new RuntimeException("Error verifying token.", t); + } + } + + /** @hide */ + public AttestationVerificationManager( + @NonNull Context context, + @NonNull IAttestationVerificationManagerService service) { + this.mContext = context; + this.mService = service; + } + + /** @hide */ + @IntDef( + prefix = {"PROFILE_"}, + value = { + PROFILE_UNKNOWN, + PROFILE_APP_DEFINED, + PROFILE_SELF_TRUSTED, + PROFILE_PEER_DEVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttestationProfileId { + } + + /** + * The profile is unknown because it is a profile unknown to this version of the SDK. + */ + public static final int PROFILE_UNKNOWN = 0; + + /** The profile is defined by an app. */ + public static final int PROFILE_APP_DEFINED = 1; + + /** + * A system-defined profile which verifies that the attesting environment can create an + * attestation with the same root certificate as the verifying device with a matching + * attestation challenge. + * + * This profile is intended to be used only for testing. + */ + public static final int PROFILE_SELF_TRUSTED = 2; + + /** + * A system-defined profile which verifies that the attesting environment environment is similar + * to the current device in terms of security model and security configuration. This category is + * fairly broad and most securely configured Android devices should qualify, along with a + * variety of non-Android devices. + */ + public static final int PROFILE_PEER_DEVICE = 3; + + /** @hide */ + @IntDef( + prefix = {"TYPE_"}, + value = { + TYPE_UNKNOWN, + TYPE_APP_DEFINED, + TYPE_PUBLIC_KEY, + TYPE_CHALLENGE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LocalBindingType { + } + + /** + * The type of the local binding data is unknown because it is a type unknown to this version of + * the SDK. + */ + public static final int TYPE_UNKNOWN = 0; + + /** + * A local binding type for app-defined profiles which use local binding data which does not + * match any of the existing system-defined types. + */ + public static final int TYPE_APP_DEFINED = 1; + + /** + * A local binding type where the attestation is bound to a public key negotiated and + * authenticated to a public key. + * + * <p>When using this type, the {@code requirements} bundle contains values for: + * <ul> + * <li>{@link #PARAM_PUBLIC_KEY} + * <li>{@link #PARAM_ID}: identifying the remote environment, optional + * </ul> + */ + public static final int TYPE_PUBLIC_KEY = 2; + + /** + * A local binding type where the attestation is bound to a challenge. + * + * <p>When using this type, the {@code requirements} bundle contains values for: + * <ul> + * <li>{@link #PARAM_CHALLENGE}: containing the challenge + * </ul> + */ + public static final int TYPE_CHALLENGE = 3; + + /** @hide */ + @IntDef( + prefix = {"RESULT_"}, + value = { + RESULT_UNKNOWN, + RESULT_SUCCESS, + RESULT_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface VerificationResult { + } + + /** The result of the verification is unknown because it has a value unknown to this SDK. */ + public static final int RESULT_UNKNOWN = 0; + + /** The result of the verification was successful. */ + public static final int RESULT_SUCCESS = 1; + + /** + * The result of the attestation verification was failure. The attestation could not be + * verified. + */ + public static final int RESULT_FAILURE = 2; + + /** + * Requirements bundle parameter key for a public key, a byte array. + * + * <p>This should contain the encoded key bytes according to the ASN.1 type + * {@code SubjectPublicKeyInfo} defined in the X.509 standard, the same as a call to {@link + * java.security.spec.X509EncodedKeySpec#getEncoded()} would produce. + * + * @see Bundle#putByteArray(String, byte[]) + */ + public static final String PARAM_PUBLIC_KEY = "localbinding.public_key"; + + /** Requirements bundle parameter key for an ID, String. */ + public static final String PARAM_ID = "localbinding.id"; + + /** Requirements bundle parameter for a challenge. */ + public static final String PARAM_CHALLENGE = "localbinding.challenge"; +} diff --git a/core/java/android/security/attestationverification/AttestationVerificationService.java b/core/java/android/security/attestationverification/AttestationVerificationService.java new file mode 100644 index 000000000000..26c3051f7f38 --- /dev/null +++ b/core/java/android/security/attestationverification/AttestationVerificationService.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.annotation.CheckResult; +import android.annotation.NonNull; +import android.app.Service; +import android.os.Bundle; +import android.security.attestationverification.AttestationVerificationManager.VerificationResult; + +/** + * A verifier which can be implemented by apps to verify an attestation (as described in {@link + * AttestationVerificationManager}). + * + * In the manifest for this service, specify the profile and local binding type this verifier + * supports. Create a new service for each combination of profile & local binding type that your app + * supports. Each service must declare an {@code intent-filter} action of {@link #SERVICE_INTERFACE} + * and permission of {@link android.Manifest.permission#BIND_ATTESTATION_VERIFICATION_SERVICE}. + * + * <p>Example: + * {@code + * <pre> + * <service android:name=".MyAttestationVerificationService" + * android:permission="android.permission.BIND_ATTESTATION_VERIFICATION_SERVICE" + * android:exported="true"> + * <intent-filter> + * <action + * android:name="android.security.attestationverification.AttestationVerificationService" /> + * </intent-filter> + * <meta-data android:name="android.security.attestationverification.PROFILE_ID" + * android:value="PROFILE_PLACEHOLDER_0" /> + * <meta-data android:name="android.security.attestationverification.LOCAL_BINDING_TYPE" + * android:value="TYPE_PLACEHOLDER_0" /> + * </service> + * </pre> + * } + * + * <p>For app-defined profiles, an example of the {@code <meta-data>}: + * {@code + * <pre> + * <meta-data android:name="android.security.attestation.PROFILE_PACKAGE_NAME" + * android:value="com.example" /> + * <meta-data android:name="android.security.attestation.PROFILE_NAME" + * android:value="com.example.profile.PROFILE_FOO" /> + * </pre> + * } + * + * @hide + */ +public abstract class AttestationVerificationService extends Service { + + /** + * An intent action for a service to be bound and act as an attestation verifier. + * + * <p>The app will be kept alive for a short duration between verification calls after which + * the system will unbind from this service making the app eligible for cleanup. + * + * <p>The service must also require permission + * {@link android.Manifest.permission#BIND_ATTESTATION_VERIFICATION_SERVICE}. + */ + public static final String SERVICE_INTERFACE = + "android.security.attestationverification.AttestationVerificationService"; + + /** + * Verifies that {@code attestation} attests that the device identified by the local binding + * data in {@code requirements} meets the minimum requirements of this verifier for this + * verifier's profile. + * + * <p>Called by the system to verify an attestation. + * + * <p>The data passed into this method comes directly from apps and should be treated as + * potentially dangerous user input. + * + * @param requirements a {@link Bundle} containing locally-known data which must match {@code + * attestation} + * @param attestation the attestation to verify + * @return whether the verification passed + * @see AttestationVerificationManager#verifyAttestation(AttestationProfile, int, Bundle, + * byte[], java.util.concurrent.Executor, java.util.function.BiConsumer) + */ + @CheckResult + @VerificationResult + public abstract int onVerifyPeerDeviceAttestation( + @NonNull Bundle requirements, + @NonNull byte[] attestation); +} diff --git a/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl b/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl new file mode 100644 index 000000000000..2fb328c3e1fb --- /dev/null +++ b/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.os.Bundle; +import android.os.ParcelDuration; +import android.security.attestationverification.AttestationProfile; +import android.security.attestationverification.VerificationToken; +import com.android.internal.infra.AndroidFuture; + + +/** + * Binder interface to communicate with AttestationVerificationManagerService. + * @hide + */ +oneway interface IAttestationVerificationManagerService { + + void verifyAttestation( + in AttestationProfile profile, + in int localBindingType, + in Bundle requirements, + in byte[] attestation, + in AndroidFuture resultCallback); + + void verifyToken( + in VerificationToken token, + in ParcelDuration maximumTokenAge, + in AndroidFuture resultCallback); +} diff --git a/core/java/android/security/attestationverification/IAttestationVerificationService.aidl b/core/java/android/security/attestationverification/IAttestationVerificationService.aidl new file mode 100644 index 000000000000..082ad3247ead --- /dev/null +++ b/core/java/android/security/attestationverification/IAttestationVerificationService.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.os.Bundle; +import com.android.internal.infra.AndroidFuture; + + +/** + * Binder interface for the system server to communicate with app implementations of + * AttestationVerificationService. + * @hide + */ +oneway interface IAttestationVerificationService { + void onVerifyAttestation( + in Bundle requirements, + in byte[] attestation, + in AndroidFuture callback); +} diff --git a/core/java/android/security/attestationverification/IVerificationResult.aidl b/core/java/android/security/attestationverification/IVerificationResult.aidl new file mode 100644 index 000000000000..f61c45697b31 --- /dev/null +++ b/core/java/android/security/attestationverification/IVerificationResult.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.security.attestationverification.VerificationToken; + + +/** + * The result of an attestation verification. + * + * {@hide} + */ +parcelable IVerificationResult { + /** The result code corresponding to @VerificationResult. */ + int resultCode; + /** The token for the verification or null. */ + VerificationToken token; +} diff --git a/core/java/android/security/attestationverification/VerificationToken.aidl b/core/java/android/security/attestationverification/VerificationToken.aidl new file mode 100644 index 000000000000..666a8b04aac2 --- /dev/null +++ b/core/java/android/security/attestationverification/VerificationToken.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +/** + * {@hide} + */ +parcelable VerificationToken; diff --git a/core/java/android/security/attestationverification/VerificationToken.java b/core/java/android/security/attestationverification/VerificationToken.java new file mode 100644 index 000000000000..ae26823a9c7d --- /dev/null +++ b/core/java/android/security/attestationverification/VerificationToken.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.attestationverification; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.Bundle; +import android.os.Parcelable; +import android.security.attestationverification.AttestationVerificationManager.LocalBindingType; +import android.security.attestationverification.AttestationVerificationManager.VerificationResult; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import java.time.Duration; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; + +/** + * Token representing the result of an attestation verification, which can be passed to other parts + * of the OS or other apps as proof of the verification. + * + * Tokens are only valid within the same UID (which means within a single app unless the deprecated + * android:sharedUserId manifest value is used). + * + * @hide + * @see Bundle#putParcelable(String, Parcelable) + */ +@DataClass( + genConstructor = false, + genHiddenBuilder = true +) +public final class VerificationToken implements Parcelable { + + /** + * The attestation profile which was used to perform the verification. + * @hide + */ + @NonNull + private final AttestationProfile mAttestationProfile; + + /** + * The local binding type of the local binding data used to perform the verification. + * @hide + */ + @LocalBindingType + private final int mLocalBindingType; + + /** + * The requirements used to perform the verification. + * @hide + */ + @NonNull + private final Bundle mRequirements; + + /** + * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, + * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from + * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this + * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, + * Duration)} to verify a valid token and it will return this value. + * + * If the token is valid, this value is returned directly by {#verifyToken}. + * + * @hide + */ + @VerificationResult + private final int mVerificationResult; + + /** + * Time when the token was generated, set by the system. + */ + @NonNull + @DataClass.ParcelWith(ForInstant.class) + private final java.time.Instant mVerificationTime; + + /** + * A Hash-based message authentication code used to verify the contents and authenticity of the + * rest of the token. The hash is created using a secret key known only to the system server. + * When verifying the token, the system re-hashes the token and verifies the generated HMAC is + * the same. + * + * @hide + */ + @NonNull + private final byte[] mHmac; + + /** + * The UID of the process which called {@code verifyAttestation} to create the token, as + * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID + * of calling process does not match this value. This ensures that tokens cannot be shared + * between UIDs. + * + * @hide + */ + private int mUid; + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/security/attestationverification/VerificationToken.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ VerificationToken( + @NonNull AttestationProfile attestationProfile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @VerificationResult int verificationResult, + @NonNull java.time.Instant verificationTime, + @NonNull byte[] hmac, + int uid) { + this.mAttestationProfile = attestationProfile; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAttestationProfile); + this.mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + this.mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + this.mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.class, null, mVerificationResult); + this.mVerificationTime = verificationTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVerificationTime); + this.mHmac = hmac; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHmac); + this.mUid = uid; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * The attestation profile which was used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull AttestationProfile getAttestationProfile() { + return mAttestationProfile; + } + + /** + * The local binding type of the local binding data used to perform the verification. + */ + @DataClass.Generated.Member + public @LocalBindingType int getLocalBindingType() { + return mLocalBindingType; + } + + /** + * The requirements used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Bundle getRequirements() { + return mRequirements; + } + + /** + * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, + * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from + * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this + * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, + * Duration)} to verify a valid token and it will return this value. + * + * If the token is valid, this value is returned directly by {#verifyToken}. + * + * @hide + */ + @DataClass.Generated.Member + public @VerificationResult int getVerificationResult() { + return mVerificationResult; + } + + /** + * Time when the token was generated, set by the system. + */ + @DataClass.Generated.Member + public @NonNull java.time.Instant getVerificationTime() { + return mVerificationTime; + } + + /** + * A Hash-based message authentication code used to verify the contents and authenticity of the + * rest of the token. The hash is created using a secret key known only to the system server. + * When verifying the token, the system re-hashes the token and verifies the generated HMAC is + * the same. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull byte[] getHmac() { + return mHmac; + } + + /** + * The UID of the process which called {@code verifyAttestation} to create the token, as + * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID + * of calling process does not match this value. This ensures that tokens cannot be shared + * between UIDs. + * + * @hide + */ + @DataClass.Generated.Member + public int getUid() { + return mUid; + } + + @DataClass.Generated.Member + static Parcelling<java.time.Instant> sParcellingForVerificationTime = + Parcelling.Cache.get( + ForInstant.class); + static { + if (sParcellingForVerificationTime == null) { + sParcellingForVerificationTime = Parcelling.Cache.put( + new ForInstant()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeTypedObject(mAttestationProfile, flags); + dest.writeInt(mLocalBindingType); + dest.writeBundle(mRequirements); + dest.writeInt(mVerificationResult); + sParcellingForVerificationTime.parcel(mVerificationTime, dest, flags); + dest.writeByteArray(mHmac); + dest.writeInt(mUid); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ VerificationToken(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + AttestationProfile attestationProfile = (AttestationProfile) in.readTypedObject(AttestationProfile.CREATOR); + int localBindingType = in.readInt(); + Bundle requirements = in.readBundle(); + int verificationResult = in.readInt(); + java.time.Instant verificationTime = sParcellingForVerificationTime.unparcel(in); + byte[] hmac = in.createByteArray(); + int uid = in.readInt(); + + this.mAttestationProfile = attestationProfile; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAttestationProfile); + this.mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + this.mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + this.mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.class, null, mVerificationResult); + this.mVerificationTime = verificationTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVerificationTime); + this.mHmac = hmac; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHmac); + this.mUid = uid; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<VerificationToken> CREATOR + = new Parcelable.Creator<VerificationToken>() { + @Override + public VerificationToken[] newArray(int size) { + return new VerificationToken[size]; + } + + @Override + public VerificationToken createFromParcel(@NonNull android.os.Parcel in) { + return new VerificationToken(in); + } + }; + + /** + * A builder for {@link VerificationToken} + * @hide + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull AttestationProfile mAttestationProfile; + private @LocalBindingType int mLocalBindingType; + private @NonNull Bundle mRequirements; + private @VerificationResult int mVerificationResult; + private @NonNull java.time.Instant mVerificationTime; + private @NonNull byte[] mHmac; + private int mUid; + + private long mBuilderFieldsSet = 0L; + + /** + * Creates a new Builder. + * + * @param attestationProfile + * The attestation profile which was used to perform the verification. + * @param localBindingType + * The local binding type of the local binding data used to perform the verification. + * @param requirements + * The requirements used to perform the verification. + * @param verificationResult + * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, + * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from + * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this + * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, + * Duration)} to verify a valid token and it will return this value. + * + * If the token is valid, this value is returned directly by {#verifyToken}. + * @param verificationTime + * Time when the token was generated, set by the system. + * @param hmac + * A Hash-based message authentication code used to verify the contents and authenticity of the + * rest of the token. The hash is created using a secret key known only to the system server. + * When verifying the token, the system re-hashes the token and verifies the generated HMAC is + * the same. + * @param uid + * The UID of the process which called {@code verifyAttestation} to create the token, as + * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID + * of calling process does not match this value. This ensures that tokens cannot be shared + * between UIDs. + */ + public Builder( + @NonNull AttestationProfile attestationProfile, + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @VerificationResult int verificationResult, + @NonNull java.time.Instant verificationTime, + @NonNull byte[] hmac, + int uid) { + mAttestationProfile = attestationProfile; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mAttestationProfile); + mLocalBindingType = localBindingType; + com.android.internal.util.AnnotationValidations.validate( + LocalBindingType.class, null, mLocalBindingType); + mRequirements = requirements; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequirements); + mVerificationResult = verificationResult; + com.android.internal.util.AnnotationValidations.validate( + VerificationResult.class, null, mVerificationResult); + mVerificationTime = verificationTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVerificationTime); + mHmac = hmac; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mHmac); + mUid = uid; + } + + /** + * The attestation profile which was used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Builder setAttestationProfile(@NonNull AttestationProfile value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mAttestationProfile = value; + return this; + } + + /** + * The local binding type of the local binding data used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Builder setLocalBindingType(@LocalBindingType int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mLocalBindingType = value; + return this; + } + + /** + * The requirements used to perform the verification. + */ + @DataClass.Generated.Member + public @NonNull Builder setRequirements(@NonNull Bundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mRequirements = value; + return this; + } + + /** + * The result of the {@link AttestationVerificationManager#verifyAttestation(int, int, Bundle, + * byte[], Executor, BiConsumer)} call. This value is kept hidden to prevent token holders from + * accidentally reading this value without calling {@code verifyToken}. Do <b>not</b> use this + * value directly; call {@link AttestationVerificationManager#verifyToken(VerificationToken, + * Duration)} to verify a valid token and it will return this value. + * + * If the token is valid, this value is returned directly by {#verifyToken}. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setVerificationResult(@VerificationResult int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mVerificationResult = value; + return this; + } + + /** + * Time when the token was generated, set by the system. + */ + @DataClass.Generated.Member + public @NonNull Builder setVerificationTime(@NonNull java.time.Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mVerificationTime = value; + return this; + } + + /** + * A Hash-based message authentication code used to verify the contents and authenticity of the + * rest of the token. The hash is created using a secret key known only to the system server. + * When verifying the token, the system re-hashes the token and verifies the generated HMAC is + * the same. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setHmac(@NonNull byte... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mHmac = value; + return this; + } + + /** + * The UID of the process which called {@code verifyAttestation} to create the token, as + * returned by {@link Binder#getCallingUid()}. Calls to {@code verifyToken} will fail if the UID + * of calling process does not match this value. This ensures that tokens cannot be shared + * between UIDs. + * + * @hide + */ + @DataClass.Generated.Member + public @NonNull Builder setUid(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; + mUid = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull VerificationToken build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x80; // Mark builder used + + VerificationToken o = new VerificationToken( + mAttestationProfile, + mLocalBindingType, + mRequirements, + mVerificationResult, + mVerificationTime, + mHmac, + mUid); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x80) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1633629747234L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/security/attestationverification/VerificationToken.java", + inputSignatures = "private final @android.annotation.NonNull android.security.attestationverification.AttestationProfile mAttestationProfile\nprivate final @android.security.attestationverification.AttestationVerificationManager.LocalBindingType int mLocalBindingType\nprivate final @android.annotation.NonNull android.os.Bundle mRequirements\nprivate final @android.security.attestationverification.AttestationVerificationManager.VerificationResult int mVerificationResult\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) java.time.Instant mVerificationTime\nprivate final @android.annotation.NonNull byte[] mHmac\nprivate int mUid\nclass VerificationToken extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genHiddenBuilder=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/security/attestationverification/package.html b/core/java/android/security/attestationverification/package.html new file mode 100644 index 000000000000..783d0a1b54d5 --- /dev/null +++ b/core/java/android/security/attestationverification/package.html @@ -0,0 +1,3 @@ +<body> +{@hide} +</body> diff --git a/core/java/android/util/BackupUtils.java b/core/java/android/util/BackupUtils.java index 474cedaa0552..4fcb13c2bcf6 100644 --- a/core/java/android/util/BackupUtils.java +++ b/core/java/android/util/BackupUtils.java @@ -37,6 +37,10 @@ public class BackupUtils { public BadVersionException(String message) { super(message); } + + public BadVersionException(String message, Throwable throwable) { + super(message, throwable); + } } public static String readString(DataInputStream in) throws IOException { diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java index ece6b3516f7a..bab20893a118 100644 --- a/core/java/android/util/DebugUtils.java +++ b/core/java/android/util/DebugUtils.java @@ -242,35 +242,49 @@ public class DebugUtils { * * @hide */ - public static String flagsToString(Class<?> clazz, String prefix, int flags) { + public static String flagsToString(Class<?> clazz, String prefix, long flags) { final StringBuilder res = new StringBuilder(); boolean flagsWasZero = flags == 0; for (Field field : clazz.getDeclaredFields()) { final int modifiers = field.getModifiers(); if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) - && field.getType().equals(int.class) && field.getName().startsWith(prefix)) { - try { - final int value = field.getInt(null); - if (value == 0 && flagsWasZero) { - return constNameWithoutPrefix(prefix, field); - } - if (value != 0 && (flags & value) == value) { - flags &= ~value; - res.append(constNameWithoutPrefix(prefix, field)).append('|'); - } - } catch (IllegalAccessException ignored) { + && (field.getType().equals(int.class) || field.getType().equals(long.class)) + && field.getName().startsWith(prefix)) { + final long value = getFieldValue(field); + if (value == 0 && flagsWasZero) { + return constNameWithoutPrefix(prefix, field); + } + if (value != 0 && (flags & value) == value) { + flags &= ~value; + res.append(constNameWithoutPrefix(prefix, field)).append('|'); } } } if (flags != 0 || res.length() == 0) { - res.append(Integer.toHexString(flags)); + res.append(Long.toHexString(flags)); } else { res.deleteCharAt(res.length() - 1); } return res.toString(); } + private static long getFieldValue(Field field) { + // Field could be int or long + try { + final long longValue = field.getLong(null); + if (longValue != 0) { + return longValue; + } + final int intValue = field.getInt(null); + if (intValue != 0) { + return intValue; + } + } catch (IllegalAccessException ignored) { + } + return 0; + } + /** * Gets human-readable representation of constants (static final values). * diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 9cd83135821b..de56d3ad7502 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -151,8 +151,7 @@ public final class AccessibilityInteractionController { if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId && mHandler.hasAccessibilityCallback(message)) { AccessibilityInteractionClient.getInstanceForThread( - interrogatingTid, /* initializeCache= */true) - .setSameThreadMessage(message); + interrogatingTid).setSameThreadMessage(message); } else { // For messages without callback of interrogating client, just handle the // message immediately if this is UI thread. diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index ec613edeedb4..c5bc99d042d7 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -153,11 +153,20 @@ public class HapticFeedbackConstants { /** * Invocation of the voice assistant via hardware button. + * This is a private constant. Feel free to renumber as desired. * @hide */ public static final int ASSISTANT_BUTTON = 10002; /** + * The user has performed a long press on the power button hardware that is resulting + * in an action being performed. + * This is a private constant. Feel free to renumber as desired. + * @hide + */ + public static final int LONG_PRESS_POWER_BUTTON = 10003; + + /** * Flag for {@link View#performHapticFeedback(int, int) * View.performHapticFeedback(int, int)}: Ignore the setting in the * view for whether to perform haptic feedback, do it always. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 0b4857d18dff..2c766bd6fd0d 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -855,6 +855,23 @@ interface IWindowManager void attachWindowContextToWindowToken(IBinder clientToken, IBinder token); /** + * Attaches a {@code clientToken} to associate with DisplayContent. + * <p> + * Note that this API should be invoked after calling + * {@link android.window.WindowTokenClient#attachContext(Context)} + * </p> + * + * @param clientToken {@link android.window.WindowContext#getWindowContextToken() + * the WindowContext's token} + * @param displayId The display associated with the window context + * + * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is + * attached to the DisplayContent successfully. {@code null}, otherwise. + * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid + */ + Configuration attachToDisplayContent(IBinder clientToken, int displayId); + + /** * Detaches {@link android.window.WindowContext} from the window manager node it's currently * attached to. It is no-op if the WindowContext is not attached to a window manager node. * diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 02b2c5d5db84..d609fb8eb234 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -124,7 +124,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { super.setControl(control, showTypes, hideTypes); - if (control == null && !isRequestedVisibleAwaitingControl()) { + // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME: + // 1) Already requested show IME, in the meantime of WM callback the control but got null + // control when relayout comes first + // 2) Make sure no regression on some implicit request IME visibility calls (e.g. + // toggleSoftInput) + if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); removeSurface(); } diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index aa1acc1217df..a6f88a7410c2 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -709,8 +709,8 @@ public class KeyCharacterMap implements Parcelable { } /** - * Queries the framework about whether any physical keys exist on the - * any keyboard attached to the device that are capable of producing the given key code. + * Queries the framework about whether any physical keys exist on any currently attached input + * devices that are capable of producing the given key code. * * @param keyCode The key code to query. * @return True if at least one attached keyboard supports the specified key code. @@ -720,9 +720,8 @@ public class KeyCharacterMap implements Parcelable { } /** - * Queries the framework about whether any physical keys exist on the - * any keyboard attached to the device that are capable of producing the given - * array of key codes. + * Queries the framework about whether any physical keys exist on any currently attached input + * devices that are capable of producing the given array of key codes. * * @param keyCodes The array of key codes to query. * @return A new array of the same size as the key codes array whose elements diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 7af77ca263fe..00754af6a853 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -613,7 +613,10 @@ public final class ThreadedRenderer extends HardwareRenderer { // If there's no arguments, eg 'dumpsys gfxinfo', then dump everything. // If there's a targetted package, eg 'dumpsys gfxinfo com.android.systemui', then only // dump the summary information - int flags = (args == null || args.length == 0) ? FLAG_DUMP_ALL : 0; + if (args == null || args.length == 0) { + return FLAG_DUMP_ALL; + } + int flags = 0; for (int i = 0; i < args.length; i++) { switch (args[i]) { case "framestats": diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 7fe810acd6f3..f69bb6a34675 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4790,6 +4790,16 @@ public interface WindowManager extends ViewManager { return Integer.toString(inputFeature); } } + + /** + * True if the window should consume all pointer events itself, regardless of whether they + * are inside of the window. If the window is modal, its touchable region will expand to the + * size of its task. + * @hide + */ + public boolean isModal() { + return (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; + } } /** diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index e634d601c1f1..94f633314b4e 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -236,6 +236,9 @@ public interface WindowManagerPolicyConstants { */ int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1; + // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and + // make app pair split only have single root then we can just attach the + // divider to the single root task in shell. int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3; int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100; int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101; diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index bc2148840187..dc4c59a1e1ec 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -115,13 +115,13 @@ public final class AccessibilityInteractionClient from a window, mapping from windowId -> timestamp. */ private static final SparseLongArray sScrollingWindows = new SparseLongArray(); - private static AccessibilityCache sAccessibilityCache; + private static SparseArray<AccessibilityCache> sCaches = new SparseArray<>(); private final AtomicInteger mInteractionIdCounter = new AtomicInteger(); private final Object mInstanceLock = new Object(); - private final AccessibilityManager mAccessibilityManager; + private final AccessibilityManager mAccessibilityManager; private volatile int mInteractionId = -1; private volatile int mCallingUid = Process.INVALID_UID; @@ -150,7 +150,7 @@ public final class AccessibilityInteractionClient @UnsupportedAppUsage() public static AccessibilityInteractionClient getInstance() { final long threadId = Thread.currentThread().getId(); - return getInstanceForThread(threadId, true); + return getInstanceForThread(threadId); } /** @@ -161,17 +161,11 @@ public final class AccessibilityInteractionClient * * @return The client for a given <code>threadId</code>. */ - public static AccessibilityInteractionClient getInstanceForThread(long threadId, - boolean initializeCache) { + public static AccessibilityInteractionClient getInstanceForThread(long threadId) { synchronized (sStaticLock) { AccessibilityInteractionClient client = sClients.get(threadId); if (client == null) { - if (Binder.getCallingUid() == Process.SYSTEM_UID) { - // Don't initialize a cache for the system process - client = new AccessibilityInteractionClient(false); - } else { - client = new AccessibilityInteractionClient(initializeCache); - } + client = new AccessibilityInteractionClient(); sClients.put(threadId, client); } return client; @@ -182,20 +176,11 @@ public final class AccessibilityInteractionClient * @return The client for the current thread. */ public static AccessibilityInteractionClient getInstance(Context context) { - return getInstance(/* initializeCache= */true, context); - } - - /** - * @param initializeCache whether to initialize the cache in a new client instance - * @return The client for the current thread. - */ - public static AccessibilityInteractionClient getInstance(boolean initializeCache, - Context context) { final long threadId = Thread.currentThread().getId(); if (context != null) { - return getInstanceForThread(threadId, initializeCache, context); + return getInstanceForThread(threadId, context); } - return getInstanceForThread(threadId, initializeCache); + return getInstanceForThread(threadId); } /** @@ -204,19 +189,14 @@ public final class AccessibilityInteractionClient * We do not have a thread local variable since other threads should be able to * look up the correct client knowing a thread id. See ViewRootImpl for details. * - * @param initializeCache whether to initialize the cache in a new client instance * @return The client for a given <code>threadId</code>. */ - public static AccessibilityInteractionClient getInstanceForThread( - long threadId, boolean initializeCache, Context context) { + public static AccessibilityInteractionClient getInstanceForThread(long threadId, + Context context) { synchronized (sStaticLock) { AccessibilityInteractionClient client = sClients.get(threadId); if (client == null) { - if (Binder.getCallingUid() == Process.SYSTEM_UID) { - client = new AccessibilityInteractionClient(false, context); - } else { - client = new AccessibilityInteractionClient(initializeCache, context); - } + client = new AccessibilityInteractionClient(context); sClients.put(threadId, client); } return client; @@ -238,12 +218,30 @@ public final class AccessibilityInteractionClient /** * Adds a cached accessibility service connection. * + * Adds a cache if {@code initializeCache} is true * @param connectionId The connection id. * @param connection The connection. + * @param initializeCache whether to initialize a cache */ - public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) { + public static void addConnection(int connectionId, IAccessibilityServiceConnection connection, + boolean initializeCache) { synchronized (sConnectionCache) { sConnectionCache.put(connectionId, connection); + if (!initializeCache) { + return; + } + sCaches.put(connectionId, new AccessibilityCache( + new AccessibilityCache.AccessibilityNodeRefresher())); + } + } + + /** + * Gets a cached associated with the connection id if available. + * + */ + public static AccessibilityCache getCache(int connectionId) { + synchronized (sConnectionCache) { + return sCaches.get(connectionId); } } @@ -255,6 +253,7 @@ public final class AccessibilityInteractionClient public static void removeConnection(int connectionId) { synchronized (sConnectionCache) { sConnectionCache.remove(connectionId); + sCaches.remove(connectionId); } } @@ -263,32 +262,21 @@ public final class AccessibilityInteractionClient * tests need to be able to verify this class's interactions with the cache */ @VisibleForTesting - public static void setCache(AccessibilityCache cache) { - sAccessibilityCache = cache; + public static void setCache(int connectionId, AccessibilityCache cache) { + synchronized (sConnectionCache) { + sCaches.put(connectionId, cache); + } } private AccessibilityInteractionClient() { /* reducing constructor visibility */ - this(true); - } - - private AccessibilityInteractionClient(boolean initializeCache) { - initializeCache(initializeCache); mAccessibilityManager = null; } - private AccessibilityInteractionClient(boolean initializeCache, Context context) { - initializeCache(initializeCache); + private AccessibilityInteractionClient(Context context) { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); } - private static void initializeCache(boolean initialize) { - if (initialize && sAccessibilityCache == null) { - sAccessibilityCache = new AccessibilityCache( - new AccessibilityCache.AccessibilityNodeRefresher()); - } - } - /** * Sets the message to be processed if the interacted view hierarchy * and the interacting client are running in the same thread. @@ -333,7 +321,7 @@ public final class AccessibilityInteractionClient * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} + * {@link AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. * @param bypassCache Whether to bypass the cache. * @return The {@link AccessibilityWindowInfo}. @@ -344,21 +332,28 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { AccessibilityWindowInfo window; - if (!bypassCache && sAccessibilityCache != null) { - window = sAccessibilityCache.getWindow(accessibilityWindowId); - if (window != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Window cache hit"); + AccessibilityCache cache = getCache(connectionId); + if (cache != null) { + if (!bypassCache) { + window = cache.getWindow(accessibilityWindowId); + if (window != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Window cache hit"); + } + if (shouldTraceClient()) { + logTraceClient(connection, "getWindow cache", + "connectionId=" + connectionId + ";accessibilityWindowId=" + + accessibilityWindowId + ";bypassCache=false"); + } + return window; } - if (shouldTraceClient()) { - logTraceClient(connection, "getWindow cache", - "connectionId=" + connectionId + ";accessibilityWindowId=" - + accessibilityWindowId + ";bypassCache=false"); + if (DEBUG) { + Log.i(LOG_TAG, "Window cache miss"); } - return window; } + } else { if (DEBUG) { - Log.i(LOG_TAG, "Window cache miss"); + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); } } @@ -374,9 +369,9 @@ public final class AccessibilityInteractionClient + bypassCache); } - if (window != null && sAccessibilityCache != null) { - if (!bypassCache) { - sAccessibilityCache.addWindow(window); + if (window != null) { + if (!bypassCache && cache != null) { + cache.addWindow(window); } return window; } @@ -418,8 +413,9 @@ public final class AccessibilityInteractionClient IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { SparseArray<List<AccessibilityWindowInfo>> windows; - if (sAccessibilityCache != null) { - windows = sAccessibilityCache.getWindowsOnAllDisplays(); + AccessibilityCache cache = getCache(connectionId); + if (cache != null) { + windows = cache.getWindowsOnAllDisplays(); if (windows != null) { if (DEBUG) { Log.i(LOG_TAG, "Windows cache hit"); @@ -433,6 +429,10 @@ public final class AccessibilityInteractionClient if (DEBUG) { Log.i(LOG_TAG, "Windows cache miss"); } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); + } } long populationTimeStamp; @@ -447,8 +447,8 @@ public final class AccessibilityInteractionClient logTraceClient(connection, "getWindows", "connectionId=" + connectionId); } if (windows != null) { - if (sAccessibilityCache != null) { - sAccessibilityCache.setWindowsOnAllDisplays(windows, populationTimeStamp); + if (cache != null) { + cache.setWindowsOnAllDisplays(windows, populationTimeStamp); } return windows; } @@ -533,28 +533,35 @@ public final class AccessibilityInteractionClient try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { - if (!bypassCache && sAccessibilityCache != null) { - AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode( - accessibilityWindowId, accessibilityNodeId); - if (cachedInfo != null) { + if (!bypassCache) { + AccessibilityCache cache = getCache(connectionId); + if (cache != null) { + AccessibilityNodeInfo cachedInfo = cache.getNode( + accessibilityWindowId, accessibilityNodeId); + if (cachedInfo != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Node cache hit for " + + idToString(accessibilityWindowId, accessibilityNodeId)); + } + if (shouldTraceClient()) { + logTraceClient(connection, + "findAccessibilityNodeInfoByAccessibilityId cache", + "connectionId=" + connectionId + ";accessibilityWindowId=" + + accessibilityWindowId + ";accessibilityNodeId=" + + accessibilityNodeId + ";bypassCache=" + + bypassCache + ";prefetchFlags=" + prefetchFlags + + ";arguments=" + arguments); + } + return cachedInfo; + } if (DEBUG) { - Log.i(LOG_TAG, "Node cache hit for " + Log.i(LOG_TAG, "Node cache miss for " + idToString(accessibilityWindowId, accessibilityNodeId)); } - if (shouldTraceClient()) { - logTraceClient(connection, - "findAccessibilityNodeInfoByAccessibilityId cache", - "connectionId=" + connectionId + ";accessibilityWindowId=" - + accessibilityWindowId + ";accessibilityNodeId=" - + accessibilityNodeId + ";bypassCache=" + bypassCache - + ";prefetchFlags=" + prefetchFlags + ";arguments=" - + arguments); + } else { + if (DEBUG) { + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); } - return cachedInfo; - } - if (DEBUG) { - Log.i(LOG_TAG, "Node cache miss for " - + idToString(accessibilityWindowId, accessibilityNodeId)); } } else { // No need to prefech nodes in bypass cache case. @@ -758,19 +765,19 @@ public final class AccessibilityInteractionClient } /** - * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the + * Finds the {@link AccessibilityNodeInfo} that has the * specified focus type. The search is performed in the window whose id is specified * and starts from the node whose accessibility id is specified. * * @param connectionId The id of a connection for interacting with the system. * @param accessibilityWindowId A unique window id. Use - * {@link android.view.accessibility.AccessibilityWindowInfo#ACTIVE_WINDOW_ID} + * {@link AccessibilityWindowInfo#ACTIVE_WINDOW_ID} * to query the currently active window. Use - * {@link android.view.accessibility.AccessibilityWindowInfo#ANY_WINDOW_ID} to query all + * {@link AccessibilityWindowInfo#ANY_WINDOW_ID} to query all * windows * @param accessibilityNodeId A unique view id or virtual descendant id from * where to start the search. Use - * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} + * {@link AccessibilityNodeInfo#ROOT_NODE_ID} * to start from the root. * @param focusType The focus type. * @return The accessibility focused {@link AccessibilityNodeInfo}. @@ -781,8 +788,9 @@ public final class AccessibilityInteractionClient try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection != null) { - if (sAccessibilityCache != null) { - AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getFocus(focusType, + AccessibilityCache cache = getCache(connectionId); + if (cache != null) { + AccessibilityNodeInfo cachedInfo = cache.getFocus(focusType, accessibilityNodeId, accessibilityWindowId); if (cachedInfo != null) { if (DEBUG) { @@ -796,6 +804,10 @@ public final class AccessibilityInteractionClient Log.i(LOG_TAG, "Focused node cache miss with " + idToString(accessibilityWindowId, accessibilityNodeId)); } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); + } } final int interactionId = mInteractionIdCounter.getAndIncrement(); if (shouldTraceClient()) { @@ -956,16 +968,25 @@ public final class AccessibilityInteractionClient } /** - * Clears the accessibility cache. + * Clears the cache associated with {@code connectionId} + * @param connectionId the connection id + * TODO(207417185): Modify UnsupportedAppUsage */ @UnsupportedAppUsage() - public void clearCache() { - if (sAccessibilityCache != null) { - sAccessibilityCache.clear(); + public void clearCache(int connectionId) { + AccessibilityCache cache = getCache(connectionId); + if (cache == null) { + return; } + cache.clear(); } - public void onAccessibilityEvent(AccessibilityEvent event) { + /** + * Informs the cache associated with {@code connectionId} of {@code event} + * @param event the event + * @param connectionId the connection id + */ + public void onAccessibilityEvent(AccessibilityEvent event, int connectionId) { switch (event.getEventType()) { case AccessibilityEvent.TYPE_VIEW_SCROLLED: updateScrollingWindow(event.getWindowId(), SystemClock.uptimeMillis()); @@ -978,9 +999,14 @@ public final class AccessibilityInteractionClient default: break; } - if (sAccessibilityCache != null) { - sAccessibilityCache.onAccessibilityEvent(event); + AccessibilityCache cache = getCache(connectionId); + if (cache == null) { + if (DEBUG) { + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); + } + return; } + cache.onAccessibilityEvent(event); } /** @@ -1216,8 +1242,15 @@ public final class AccessibilityInteractionClient } } info.setSealed(true); - if (!bypassCache && sAccessibilityCache != null) { - sAccessibilityCache.add(info); + if (!bypassCache) { + AccessibilityCache cache = getCache(connectionId); + if (cache == null) { + if (DEBUG) { + Log.w(LOG_TAG, "Cache is null for connection id: " + connectionId); + } + return; + } + cache.add(info); } } } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index bb3f4e56b92d..dc617270cbf9 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -121,6 +121,8 @@ public final class AccessibilityManager { public static final int STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED = 0x00000400; /** @hide */ public static final int STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED = 0x00000800; + /** @hide */ + public static final int STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED = 0x00001000; /** @hide */ public static final int DALTONIZER_DISABLED = -1; @@ -244,6 +246,8 @@ public final class AccessibilityManager { @UnsupportedAppUsage(trackingBug = 123768939L) boolean mIsHighTextContrastEnabled; + boolean mIsAudioDescriptionByDefaultRequested; + // accessibility tracing state int mAccessibilityTracingState = 0; @@ -1293,15 +1297,19 @@ public final class AccessibilityManager { (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0; final boolean highTextContrastEnabled = (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0; + final boolean audioDescriptionEnabled = + (stateFlags & STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED) != 0; final boolean wasEnabled = isEnabled(); final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled; final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled; + // Ensure listeners get current state from isZzzEnabled() calls. mIsEnabled = enabled; mIsTouchExplorationEnabled = touchExplorationEnabled; mIsHighTextContrastEnabled = highTextContrastEnabled; + mIsAudioDescriptionByDefaultRequested = audioDescriptionEnabled; if (wasEnabled != isEnabled()) { notifyAccessibilityStateChanged(); @@ -1678,6 +1686,29 @@ public final class AccessibilityManager { } } + /** + * Determines if users want to select sound track with audio description by default. + * + * Audio description, also referred to as a video description, described video, or + * more precisely called a visual description, is a form of narration used to provide + * information surrounding key visual elements in a media work for the benefit of + * blind and visually impaired consumers. + * + * The method provides the preference value to content provider apps to select the + * default sound track during playing a video or movie. + * + * @return {@code true} if the audio description is enabled, {@code false} otherwise. + */ + public boolean isAudioDescriptionRequested() { + synchronized (mLock) { + IAccessibilityManager service = getServiceLocked(); + if (service == null) { + return false; + } + return mIsAudioDescriptionByDefaultRequested; + } + } + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 4730eaa8eee9..7680aa6c61fd 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3957,8 +3957,10 @@ public class AccessibilityNodeInfo implements Parcelable { } if (isBitSet(nonDefaultFields, fieldIndex++)) { + parcel.writeString(mCollectionItemInfo.getRowTitle()); parcel.writeInt(mCollectionItemInfo.getRowIndex()); parcel.writeInt(mCollectionItemInfo.getRowSpan()); + parcel.writeString(mCollectionItemInfo.getColumnTitle()); parcel.writeInt(mCollectionItemInfo.getColumnIndex()); parcel.writeInt(mCollectionItemInfo.getColumnSpan()); parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0); @@ -4100,8 +4102,9 @@ public class AccessibilityNodeInfo implements Parcelable { ci.mHierarchical, ci.mSelectionMode); CollectionItemInfo cii = other.mCollectionItemInfo; mCollectionItemInfo = (cii == null) ? null - : new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex, - cii.mColumnSpan, cii.mHeading, cii.mSelected); + : new CollectionItemInfo(cii.mRowTitle, cii.mRowIndex, cii.mRowSpan, + cii.mColumnTitle, cii.mColumnIndex, cii.mColumnSpan, + cii.mHeading, cii.mSelected); ExtraRenderingInfo ti = other.mExtraRenderingInfo; mExtraRenderingInfo = (ti == null) ? null : new ExtraRenderingInfo(ti); @@ -4221,8 +4224,10 @@ public class AccessibilityNodeInfo implements Parcelable { if (mCollectionItemInfo != null) mCollectionItemInfo.recycle(); mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++) ? CollectionItemInfo.obtain( + parcel.readString(), parcel.readInt(), parcel.readInt(), + parcel.readString(), parcel.readInt(), parcel.readInt(), parcel.readInt() == 1, @@ -5570,8 +5575,9 @@ public class AccessibilityNodeInfo implements Parcelable { * @hide */ public static CollectionItemInfo obtain(CollectionItemInfo other) { - return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex, - other.mColumnSpan, other.mHeading, other.mSelected); + return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan, + other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading, + other.mSelected); } /** @@ -5612,10 +5618,36 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static CollectionItemInfo obtain(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading, boolean selected) { + return obtain(null, rowIndex, rowSpan, null, columnIndex, + columnSpan, heading, selected); + } + + /** + * Obtains a pooled instance. + * + * <p>In most situations object pooling is not beneficial. Creates a new instance using the + * constructor {@link + * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int, + * int, int, int, boolean, boolean)} instead. + * + * @param rowTitle The row title at which the item is located. + * @param rowIndex The row index at which the item is located. + * @param rowSpan The number of rows the item spans. + * @param columnTitle The column title at which the item is located. + * @param columnIndex The column index at which the item is located. + * @param columnSpan The number of columns the item spans. + * @param heading Whether the item is a heading. (Prefer + * {@link AccessibilityNodeInfo#setHeading(boolean)}) + * @param selected Whether the item is selected. + */ + @NonNull + public static CollectionItemInfo obtain(@Nullable String rowTitle, int rowIndex, + int rowSpan, @Nullable String columnTitle, int columnIndex, int columnSpan, + boolean heading, boolean selected) { final CollectionItemInfo info = sPool.acquire(); if (info == null) { - return new CollectionItemInfo( - rowIndex, rowSpan, columnIndex, columnSpan, heading, selected); + return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle, + columnIndex, columnSpan, heading, selected); } info.mRowIndex = rowIndex; @@ -5624,6 +5656,8 @@ public class AccessibilityNodeInfo implements Parcelable { info.mColumnSpan = columnSpan; info.mHeading = heading; info.mSelected = selected; + info.mRowTitle = rowTitle; + info.mColumnTitle = columnTitle; return info; } @@ -5633,6 +5667,8 @@ public class AccessibilityNodeInfo implements Parcelable { private int mColumnSpan; private int mRowSpan; private boolean mSelected; + private String mRowTitle; + private String mColumnTitle; /** * Creates a new instance. @@ -5660,12 +5696,33 @@ public class AccessibilityNodeInfo implements Parcelable { */ public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading, boolean selected) { + this(null, rowIndex, rowSpan, null, columnIndex, columnSpan, + heading, selected); + } + + /** + * Creates a new instance. + * + * @param rowTitle The row title at which the item is located. + * @param rowIndex The row index at which the item is located. + * @param rowSpan The number of rows the item spans. + * @param columnTitle The column title at which the item is located. + * @param columnIndex The column index at which the item is located. + * @param columnSpan The number of columns the item spans. + * @param heading Whether the item is a heading. + * @param selected Whether the item is selected. + */ + public CollectionItemInfo(@Nullable String rowTitle, int rowIndex, int rowSpan, + @Nullable String columnTitle, int columnIndex, int columnSpan, boolean heading, + boolean selected) { mRowIndex = rowIndex; mRowSpan = rowSpan; mColumnIndex = columnIndex; mColumnSpan = columnSpan; mHeading = heading; mSelected = selected; + mRowTitle = rowTitle; + mColumnTitle = columnTitle; } /** @@ -5725,6 +5782,26 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the row title at which the item is located. + * + * @return The row title. + */ + @Nullable + public String getRowTitle() { + return mRowTitle; + } + + /** + * Gets the column title at which the item is located. + * + * @return The column title. + */ + @Nullable + public String getColumnTitle() { + return mColumnTitle; + } + + /** * Recycles this instance. * * <p>In most situations object pooling is not beneficial, and recycling is not necessary. @@ -5741,6 +5818,8 @@ public class AccessibilityNodeInfo implements Parcelable { mRowSpan = 0; mHeading = false; mSelected = false; + mRowTitle = null; + mColumnTitle = null; } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 078ab25e8b6c..4e8d2da70159 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -98,4 +98,6 @@ interface IAccessibilityManager { int getFocusStrokeWidth(); int getFocusColor(); + + boolean isAudioDescriptionByDefaultEnabled(); } diff --git a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl index ddf68fcb1311..67d96678f420 100644 --- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl +++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl @@ -38,9 +38,14 @@ oneway interface IWindowMagnificationConnection { * or {@link Float#NaN} to leave unchanged. * @param centerY the screen-relative Y coordinate around which to center, * or {@link Float#NaN} to leave unchanged. + * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset between + * frame position X and centerX + * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset between + * frame position Y and centerY * @param callback The callback called when the animation is completed or interrupted. */ void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, in IRemoteMagnificationAnimationCallback callback); /** diff --git a/core/java/android/widget/RemoteViewsListAdapter.java b/core/java/android/widget/RemoteViewsListAdapter.java index 827d03317d6a..46f80f3b5129 100644 --- a/core/java/android/widget/RemoteViewsListAdapter.java +++ b/core/java/android/widget/RemoteViewsListAdapter.java @@ -89,8 +89,7 @@ public class RemoteViewsListAdapter extends BaseAdapter { RemoteViews rv = mRemoteViewsList.get(position); rv.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); View v; - if (convertView != null && rv != null && - convertView.getId() == rv.getLayoutId()) { + if (convertView != null && convertView.getId() == rv.getLayoutId()) { v = convertView; rv.reapply(mContext, v, null /* handler */, null /* size */, mColorResources); } else { diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java new file mode 100644 index 000000000000..7677b89dff96 --- /dev/null +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.pm.ActivityInfo; + +import java.io.PrintWriter; +import java.util.List; + +/** + * Abstract class to control the policies of the windows that can be displayed on the virtual + * display. + * + * @hide + */ +public abstract class DisplayWindowPolicyController { + /** + * The window flags that we are interested in. + * @see android.view.WindowManager.LayoutParams + * @see #keepActivityOnWindowFlagsChanged + */ + private int mWindowFlags; + + /** + * Returns {@code true} if the given window flags contain the flags that we're interested in. + */ + public final boolean isInterestedWindowFlags(int windowFlags) { + return (mWindowFlags & windowFlags) != 0; + } + + /** + * Sets the window flags that we’re interested in and expected + * #keepActivityOnWindowFlagsChanged to be called if any changes. + */ + public final void setInterestedWindowFlags(int windowFlags) { + mWindowFlags = windowFlags; + } + + /** + * Returns {@code true} if the given activities can be displayed on this virtual display. + */ + public abstract boolean canContainActivities(@NonNull List<ActivityInfo> activities); + + /** + * Called when an Activity window is layouted with the new changes where contains the + * window flags that we’re interested in. + * Returns {@code false} if the Activity cannot remain on the display and the activity task will + * be moved back to default display. + */ + public abstract boolean keepActivityOnWindowFlagsChanged( + ActivityInfo activityInfo, int windowFlags); + + /** + * This is called when the top activity of the display is changed. + */ + public void onTopActivityChanged(ComponentName topActivity, int uid) {} + + /** + * This is called when the apps that contains running activities on the display has changed. + */ + public void onRunningAppsChanged(int[] runningUids) {} + + /** Dump debug data */ + public void dump(String prefix, final PrintWriter pw) { + pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}"); + pw.println(prefix + " mWindowFlags=" + mWindowFlags); + } +} diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl index 5eb432e785ee..cdfa206423c2 100644 --- a/core/java/android/window/ITaskFragmentOrganizer.aidl +++ b/core/java/android/window/ITaskFragmentOrganizer.aidl @@ -19,12 +19,11 @@ package android.window; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; /** @hide */ oneway interface ITaskFragmentOrganizer { - void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo); diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java deleted file mode 100644 index 89d9a9508a71..000000000000 --- a/core/java/android/window/TaskFragmentAppearedInfo.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.window; - -import android.annotation.NonNull; -import android.annotation.TestApi; -import android.os.Parcel; -import android.os.Parcelable; -import android.view.SurfaceControl; - -/** - * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer. - * @hide - */ -@TestApi -public final class TaskFragmentAppearedInfo implements Parcelable { - - @NonNull - private final TaskFragmentInfo mTaskFragmentInfo; - - @NonNull - private final SurfaceControl mLeash; - - /** @hide */ - public TaskFragmentAppearedInfo( - @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) { - mTaskFragmentInfo = taskFragmentInfo; - mLeash = leash; - } - - @NonNull - public TaskFragmentInfo getTaskFragmentInfo() { - return mTaskFragmentInfo; - } - - @NonNull - public SurfaceControl getLeash() { - return mLeash; - } - - private TaskFragmentAppearedInfo(Parcel in) { - mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR); - mLeash = in.readTypedObject(SurfaceControl.CREATOR); - } - - /** @hide */ - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedObject(mTaskFragmentInfo, flags); - dest.writeTypedObject(mLeash, flags); - } - - @NonNull - public static final Creator<TaskFragmentAppearedInfo> CREATOR = - new Creator<TaskFragmentAppearedInfo>() { - @Override - public TaskFragmentAppearedInfo createFromParcel(Parcel in) { - return new TaskFragmentAppearedInfo(in); - } - - @Override - public TaskFragmentAppearedInfo[] newArray(int size) { - return new TaskFragmentAppearedInfo[size]; - } - }; - - @Override - public String toString() { - return "TaskFragmentAppearedInfo{" - + " taskFragmentInfo=" + mTaskFragmentInfo - + "}"; - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 337c5a14e9d3..7e7d37083b5b 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -120,8 +120,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** Called when a TaskFragment is created and organized by this organizer. */ - public void onTaskFragmentAppeared( - @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {} + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** Called when the status of an organized TaskFragment is changed. */ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} @@ -169,7 +168,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() { @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) { + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { mExecutor.execute( () -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo)); } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 5aa623388574..17b675f93f86 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -19,13 +19,10 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.view.IWindowManager; import android.view.WindowManager.LayoutParams.WindowType; -import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -38,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public class WindowContextController { - private final IWindowManager mWms; /** * {@code true} to indicate that the {@code mToken} is associated with a * {@link com.android.server.wm.DisplayArea}. Note that {@code mToken} is able to attach a @@ -56,14 +52,7 @@ public class WindowContextController { * {@link Context#getWindowContextToken()}. */ public WindowContextController(@NonNull WindowTokenClient token) { - this(token, WindowManagerGlobal.getWindowManagerService()); - } - - /** Used for test only. DO NOT USE it in production code. */ - @VisibleForTesting - public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) { mToken = token; - mWms = mockWms; } /** @@ -80,19 +69,7 @@ public class WindowContextController { throw new IllegalStateException("A Window Context can be only attached to " + "a DisplayArea once."); } - try { - final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type, - displayId, options); - if (configuration != null) { - mAttachedToDisplayArea = true; - // Send the DisplayArea's configuration to WindowContext directly instead of - // waiting for dispatching from WMS. - mToken.onConfigurationChanged(configuration, displayId, - false /* shouldReportConfigChange */); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mAttachedToDisplayArea = mToken.attachToDisplayArea(type, displayId, options); } /** @@ -120,22 +97,14 @@ public class WindowContextController { throw new IllegalStateException("The Window Context should have been attached" + " to a DisplayArea."); } - try { - mWms.attachWindowContextToWindowToken(mToken, windowToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.attachToWindowToken(windowToken); } /** Detaches the window context from the node it's currently associated with. */ public void detachIfNeeded() { if (mAttachedToDisplayArea) { - try { - mWms.detachWindowContextFromWindowContainer(mToken); - mAttachedToDisplayArea = false; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mToken.detachFromWindowContainerIfNeeded(); + mAttachedToDisplayArea = false; } } } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index f3e3859b4256..b331a9e81e27 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -21,6 +21,7 @@ import static android.window.ConfigurationHelper.isDifferentDisplay; import static android.window.ConfigurationHelper.shouldUpdateResources; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; @@ -31,7 +32,11 @@ import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; +import android.os.RemoteException; import android.util.Log; +import android.view.IWindowManager; +import android.view.WindowManager.LayoutParams.WindowType; +import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; @@ -59,10 +64,14 @@ public class WindowTokenClient extends IWindowToken.Stub { private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); + private IWindowManager mWms; + private final Configuration mConfiguration = new Configuration(); private boolean mShouldDumpConfigForIme; + private boolean mAttachToWindowContainer; + /** * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} * can only attach one {@link Context}. @@ -84,6 +93,88 @@ public class WindowTokenClient extends IWindowToken.Stub { } /** + * Attaches this {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. + * + * @param type The window type of the {@link WindowContext} + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @param options The window context launched option + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayArea(@WindowType int type, int displayId, + @Nullable Bundle options) { + try { + final Configuration configuration = getWindowManagerService() + .attachWindowContextToDisplayArea(this, type, displayId, options); + if (configuration == null) { + return false; + } + onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); + mAttachToWindowContainer = true; + return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code DisplayContent}. + * + * @param displayId The {@link Context#getDisplayId() ID of display} to associate with + * @return {@code true} if attaching successfully. + */ + public boolean attachToDisplayContent(int displayId) { + final IWindowManager wms = getWindowManagerService(); + // #createSystemUiContext may call this method before WindowManagerService is initialized. + if (wms == null) { + return false; + } + try { + final Configuration configuration = wms.attachToDisplayContent(this, displayId); + if (configuration == null) { + return false; + } + onConfigurationChanged(configuration, displayId, false /* shouldReportConfigChange */); + mAttachToWindowContainer = true; + return true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attaches this {@link WindowTokenClient} to a {@code windowToken}. + * + * @param windowToken the window token to associated with + */ + public void attachToWindowToken(IBinder windowToken) { + try { + getWindowManagerService().attachWindowContextToWindowToken(this, windowToken); + mAttachToWindowContainer = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** Detaches this {@link WindowTokenClient} from associated WindowContainer if there's one. */ + public void detachFromWindowContainerIfNeeded() { + if (!mAttachToWindowContainer) { + return; + } + try { + getWindowManagerService().detachWindowContextFromWindowContainer(this); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private IWindowManager getWindowManagerService() { + if (mWms == null) { + mWms = WindowManagerGlobal.getWindowManagerService(); + } + return mWms; + } + + /** * Called when {@link Configuration} updates from the server side receive. * * @param newConfig the updated {@link Configuration} diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java index 0047f43b5258..ee4d46dbbbd3 100644 --- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java +++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java @@ -23,12 +23,10 @@ import android.accounts.AccountManager; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.os.Bundle; import android.os.PersistableBundle; -import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -44,6 +42,8 @@ public class ConfirmUserCreationActivity extends AlertActivity private static final String TAG = "CreateUser"; + private static final String USER_TYPE = UserManager.USER_TYPE_FULL_SECONDARY; + private String mUserName; private String mAccountName; private String mAccountType; @@ -103,7 +103,7 @@ public class ConfirmUserCreationActivity extends AlertActivity boolean cantCreateUser = mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER) || !mUserManager.isAdminUser(); // Check the system state and user count - boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers(); + boolean cantCreateAnyMoreUsers = !mUserManager.canAddMoreUsers(USER_TYPE); // Check the account existence final Account account = new Account(mAccountName, mAccountType); boolean accountExists = mAccountName != null && mAccountType != null @@ -130,7 +130,7 @@ public class ConfirmUserCreationActivity extends AlertActivity setResult(RESULT_CANCELED); if (which == BUTTON_POSITIVE && mCanProceed) { Log.i(TAG, "Ok, creating user"); - UserInfo user = mUserManager.createUser(mUserName, 0); + UserInfo user = mUserManager.createUser(mUserName, USER_TYPE, 0); if (user == null) { Log.e(TAG, "Couldn't create user"); finish(); diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index bff813e82b1f..6a6f60e967c1 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -512,6 +512,11 @@ public final class SystemUiDeviceConfigFlags { */ public static final String DEFAULT_QR_CODE_SCANNER = "default_qr_code_scanner"; + /** + * (boolean) Whether the task manager entrypoint is enabled. + */ + public static final String TASK_MANAGER_ENABLED = "task_manager_enabled"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 06d68e049ac5..aba43d859db4 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -3522,6 +3522,10 @@ public class BatteryStatsImpl extends BatteryStats { * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code> */ private int writeHistoryTag(HistoryTag tag) { + if (tag.string == null) { + Slog.wtfStack(TAG, "writeHistoryTag called with null name"); + } + Integer idxObj = mHistoryTagPool.get(tag); int idx; if (idxObj != null) { @@ -12182,7 +12186,7 @@ public class BatteryStatsImpl extends BatteryStats { mHistoryTags = new SparseArray<>(mHistoryTagPool.size()); for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) { - mHistoryTags.put(entry.getValue(), entry.getKey()); + mHistoryTags.put(entry.getValue() & ~TAG_FIRST_OCCURRENCE_FLAG, entry.getKey()); } } @@ -16341,9 +16345,6 @@ public class BatteryStatsImpl extends BatteryStats { for (int i=0; i<numTags; i++) { int idx = in.readInt(); String str = in.readString(); - if (str == null) { - throw new ParcelFormatException("null history tag string"); - } int uid = in.readInt(); HistoryTag tag = new HistoryTag(); tag.string = str; diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 92d5a47a2ed0..6d4b8c5ea1ad 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -724,9 +724,6 @@ public final class Zygote { DataOutputStream usapOutputStream = null; ZygoteArguments args = null; - // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. - blockSigTerm(); - LocalSocket sessionSocket = null; if (argBuffer == null) { // Read arguments from usapPoolSocket instead. @@ -742,6 +739,10 @@ public final class Zygote { ZygoteCommandBuffer tmpArgBuffer = null; try { sessionSocket = usapPoolSocket.accept(); + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + // This is safe from a race condition because the pool is only flushed after + // the SystemServer changes its internal state to stop using the USAP pool. + blockSigTerm(); usapOutputStream = new DataOutputStream(sessionSocket.getOutputStream()); @@ -759,9 +760,10 @@ public final class Zygote { unblockSigTerm(); IoUtils.closeQuietly(sessionSocket); IoUtils.closeQuietly(tmpArgBuffer); - blockSigTerm(); } } else { + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + blockSigTerm(); try { args = ZygoteArguments.getInstance(argBuffer); } catch (Exception ex) { diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java index 1ab316d07e42..3147c34a7129 100644 --- a/core/java/com/android/internal/util/Parcelling.java +++ b/core/java/com/android/internal/util/Parcelling.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -306,5 +307,33 @@ public interface Parcelling<T> { return string == null ? null : UUID.fromString(string); } } + + /** + * A {@link Parcelling} for {@link Instant}. + * + * The minimum value of an instant uses a millisecond offset of about -3.15e19 which is + * larger than Long.MIN_VALUE, so we can use Long.MIN_VALUE as a sentinel value to indicate + * a null Instant. + */ + class ForInstant implements Parcelling<Instant> { + + @Override + public void parcel(Instant item, Parcel dest, int parcelFlags) { + dest.writeLong(item == null ? Long.MIN_VALUE : item.getEpochSecond()); + dest.writeInt(item == null ? Integer.MIN_VALUE : item.getNano()); + } + + @Override + public Instant unparcel(Parcel source) { + long epochSecond = source.readLong(); + int afterNano = source.readInt(); + + if (epochSecond == Long.MIN_VALUE) { + return null; + } else { + return Instant.ofEpochSecond(epochSecond, afterNano); + } + } + } } } diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 6cea8bf6ef83..f2bcb026684e 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -73,6 +73,9 @@ per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google # Although marked "view" this is mostly graphics stuff per-file android_view_* = file:/graphics/java/android/graphics/OWNERS +# Verity +per-file com_android_internal_security_Verity* = ebiggers@google.com, victorhsieh@google.com + # VINTF per-file android_os_VintfObject* = file:platform/system/libvintf:/OWNERS per-file android_os_VintfRuntimeInfo* = file:platform/system/libvintf:/OWNERS diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 4c2b114c724a..5e0d9b32380c 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -34,6 +34,7 @@ #include <vector> #include <android-base/logging.h> +#include <android-base/properties.h> #include <bionic/malloc.h> #include <debuggerd/client.h> #include <log/log.h> @@ -859,7 +860,22 @@ static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject claz return poolsSizeKb; } +static bool halSupportsGpuPrivateMemory() { + int productApiLevel = + android::base::GetIntProperty("ro.product.first_api_level", + android::base::GetIntProperty("ro.build.version.sdk", + __ANDROID_API_FUTURE__)); + int boardApiLevel = + android::base::GetIntProperty("ro.board.api_level", + android::base::GetIntProperty("ro.board.first_api_level", + __ANDROID_API_FUTURE__)); + + return std::min(productApiLevel, boardApiLevel) >= __ANDROID_API_S__; +} + static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz) { + static bool gpuPrivateMemorySupported = halSupportsGpuPrivateMemory(); + struct memtrack_proc* p = memtrack_proc_new(); if (p == nullptr) { LOG(ERROR) << "getGpuPrivateMemoryKb: Failed to create memtrack_proc"; @@ -876,6 +892,12 @@ static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz) ssize_t gpuPrivateMem = memtrack_proc_gl_pss(p); memtrack_proc_destroy(p); + + // Old HAL implementations may return 0 for GPU private memory if not supported + if (gpuPrivateMem == 0 && !gpuPrivateMemorySupported) { + return -1; + } + return gpuPrivateMem / 1024; } diff --git a/core/jni/android_view_CompositionSamplingListener.cpp b/core/jni/android_view_CompositionSamplingListener.cpp index 783b0d45ba93..59c01dc37593 100644 --- a/core/jni/android_view_CompositionSamplingListener.cpp +++ b/core/jni/android_view_CompositionSamplingListener.cpp @@ -16,21 +16,19 @@ #define LOG_TAG "CompositionSamplingListener" -#include "android_util_Binder.h" -#include "core_jni_helpers.h" - -#include <nativehelper/JNIHelp.h> - +#include <android/gui/BnRegionSamplingListener.h> #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> -#include <utils/Log.h> -#include <utils/RefBase.h> #include <binder/IServiceManager.h> - -#include <gui/IRegionSamplingListener.h> #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> #include <ui/Rect.h> +#include <utils/Log.h> +#include <utils/RefBase.h> + +#include "android_util_Binder.h" +#include "core_jni_helpers.h" namespace android { @@ -41,18 +39,18 @@ struct { jmethodID mDispatchOnSampleCollected; } gListenerClassInfo; -struct CompositionSamplingListener : public BnRegionSamplingListener { +struct CompositionSamplingListener : public gui::BnRegionSamplingListener { CompositionSamplingListener(JNIEnv* env, jobject listener) : mListener(env->NewWeakGlobalRef(listener)) {} - void onSampleCollected(float medianLuma) override { + binder::Status onSampleCollected(float medianLuma) override { JNIEnv* env = AndroidRuntime::getJNIEnv(); LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onSampleCollected."); jobject listener = env->NewGlobalRef(mListener); if (listener == NULL) { // Weak reference went out of scope - return; + return binder::Status::ok(); } env->CallStaticVoidMethod(gListenerClassInfo.mClass, gListenerClassInfo.mDispatchOnSampleCollected, listener, @@ -64,6 +62,8 @@ struct CompositionSamplingListener : public BnRegionSamplingListener { LOGE_EX(env); env->ExceptionClear(); } + + return binder::Status::ok(); } protected: diff --git a/core/jni/com_android_internal_security_VerityUtils.cpp b/core/jni/com_android_internal_security_VerityUtils.cpp index 411a392a075c..c5b3d8ae936c 100644 --- a/core/jni/com_android_internal_security_VerityUtils.cpp +++ b/core/jni/com_android_internal_security_VerityUtils.cpp @@ -16,25 +16,25 @@ #define LOG_TAG "VerityUtils" -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedPrimitiveArray.h> -#include <nativehelper/ScopedUtfChars.h> -#include <utils/Log.h> -#include "jni.h" - +#include <android-base/unique_fd.h> #include <errno.h> #include <fcntl.h> +#include <linux/fs.h> #include <linux/fsverity.h> #include <linux/stat.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> #include <string.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> - -#include <android-base/unique_fd.h> +#include <utils/Log.h> #include <type_traits> +#include "jni.h" + namespace android { namespace { @@ -73,18 +73,31 @@ int enableFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArra int statxForFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath) { ScopedUtfChars path(env, filePath); + // Call statx and check STATX_ATTR_VERITY. struct statx out = {}; if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) { return -errno; } - // Validity check. - if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) { - ALOGE("Unexpected, STATX_ATTR_VERITY not supported by kernel"); - return -ENOSYS; + if (out.stx_attributes_mask & STATX_ATTR_VERITY) { + return (out.stx_attributes & STATX_ATTR_VERITY) != 0; + } + + // STATX_ATTR_VERITY is not supported for the file path. + // In this case, call ioctl(FS_IOC_GETFLAGS) and check FS_VERITY_FL. + ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC)); + if (rfd.get() < 0) { + ALOGE("open failed at %s", path.c_str()); + return -errno; + } + + unsigned int flags; + if (ioctl(rfd.get(), FS_IOC_GETFLAGS, &flags) < 0) { + ALOGE("ioctl(FS_IOC_GETFLAGS) failed at %s", path.c_str()); + return -errno; } - return (out.stx_attributes & STATX_ATTR_VERITY) != 0; + return (flags & FS_VERITY_FL) != 0; } int measureFsverity(JNIEnv *env, jobject /* clazz */, jstring filePath, jbyteArray digest) { diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto index 891e9fca36aa..5fdcfdf35a37 100644 --- a/core/proto/android/app/location_time_zone_manager.proto +++ b/core/proto/android/app/location_time_zone_manager.proto @@ -23,6 +23,19 @@ import "frameworks/base/core/proto/android/privacy.proto"; option java_multiple_files = true; option java_outer_classname = "LocationTimeZoneManagerProto"; +// A state enum that matches states for LocationTimeZoneProviderController. See that class for +// details. +enum ControllerStateEnum { + CONTROLLER_STATE_UNKNOWN = 0; + CONTROLLER_STATE_PROVIDERS_INITIALIZING = 1; + CONTROLLER_STATE_STOPPED = 2; + CONTROLLER_STATE_INITIALIZING = 3; + CONTROLLER_STATE_UNCERTAIN = 4; + CONTROLLER_STATE_CERTAIN = 5; + CONTROLLER_STATE_FAILED = 6; + CONTROLLER_STATE_DESTROYED = 7; +} + // Represents the state of the LocationTimeZoneManagerService for use in tests. message LocationTimeZoneManagerServiceStateProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; @@ -30,6 +43,7 @@ message LocationTimeZoneManagerServiceStateProto { optional GeolocationTimeZoneSuggestionProto last_suggestion = 1; repeated TimeZoneProviderStateProto primary_provider_states = 2; repeated TimeZoneProviderStateProto secondary_provider_states = 3; + repeated ControllerStateEnum controller_states = 4; } // The state tracked for a LocationTimeZoneProvider. diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 83e26f66fd62..db5d49d61ebc 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -601,7 +601,7 @@ message GlobalSettingsProto { // ringer mode. optional SettingProto mode_ringer = 75 [ (android.privacy).dest = DEST_AUTOMATIC ]; - optional SettingProto apply_ramping_ringer = 147 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 147; // Used to be apply_ramping_ringer message MultiSim { option (android.msg_privacy).dest = DEST_EXPLICIT; diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index f8b5b233c607..73d6a17799d9 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -242,7 +242,9 @@ message SystemSettingsProto { optional SettingProto when_to_make_wifi_calls = 34 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto apply_ramping_ringer = 35 [ (android.privacy).dest = DEST_AUTOMATIC ]; + // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 35; + // Next tag = 36; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 601280a25873..a49e3b502c96 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1840,11 +1840,11 @@ <permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" android:protectionLevel="signature|privileged" /> - <!-- Allows applications to act as network scorers. @hide @SystemApi--> + <!-- @deprecated Allows applications to act as network scorers. @hide @SystemApi--> <permission android:name="android.permission.SCORE_NETWORKS" android:protectionLevel="signature|privileged" /> - <!-- Allows applications to request network + <!-- @deprecated Allows applications to request network recommendations and scores from the NetworkScoreService. @SystemApi <p>Not for use by third-party applications. @hide --> @@ -2793,6 +2793,11 @@ android:label="@string/permlab_manageProfileAndDeviceOwners" android:description="@string/permdesc_manageProfileAndDeviceOwners" /> + <!-- @SystemApi @hide Allows an application to query device policies set by any admin on + the device.--> + <permission android:name="android.permission.QUERY_ADMIN_POLICY" + android:protectionLevel="signature|role" /> + <!-- @TestApi @hide Allows an application to reset the record of previous system update freeze periods. --> <permission android:name="android.permission.CLEAR_FREEZE_PERIOD" @@ -3015,6 +3020,13 @@ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" android:protectionLevel="internal|role" /> + <!-- Allows an application to create a "self-managed" association. + @hide + @SystemApi + --> + <permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" + android:protectionLevel="signature|privileged" /> + <!-- Allows a companion app to associate to Wi-Fi. <p>Only for use by a single pre-approved app. @hide @@ -3463,6 +3475,23 @@ <permission android:name="android.permission.UPDATE_FONTS" android:protectionLevel="signature|privileged" /> + <!-- Allows an application to use the AttestationVerificationService. + @hide --> + <permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" + android:protectionLevel="signature" /> + + <!-- Allows an application to export a AttestationVerificationService to verify attestations on + behalf of AttestationVerificationManager for system-defined attestation profiles. + @hide --> + <permission android:name="android.permission.VERIFY_ATTESTATION" + android:protectionLevel="signature" /> + + <!-- Must be required by any AttestationVerificationService to ensure that only the system can + bind to it. + @hide --> + <permission android:name="android.permission.BIND_ATTESTATION_VERIFICATION_SERVICE" + android:protectionLevel="signature" /> + <!-- ========================================= --> <!-- Permissions for special development tools --> <!-- ========================================= --> @@ -5916,11 +5945,10 @@ <permission android:name="android.permission.RENOUNCE_PERMISSIONS" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to read nearby streaming policy. The policy allows the device - to stream its notifications and apps to nearby devices. - @hide --> + <!-- Allows an application to read nearby streaming policy. The policy controls + whether to allow the device to stream its notifications and apps to nearby devices. --> <permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="normal" /> <!-- @SystemApi Allows the holder to set the source of the data when setting a clip on the clipboard. @@ -5963,6 +5991,14 @@ <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" android:protectionLevel="internal|role" /> + <!-- @SystemApi Must be required by a safety source to send an update using the + {@link android.safetycenter.SafetyCenterManager}. + <p>Protection level: signature|privileged + @hide + --> + <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" + android:protectionLevel="signature|privileged" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/layout-watch/preference_list_fragment_material.xml b/core/res/res/layout-watch/preference_list_fragment_material.xml index 22e66d513936..8f2658b357fd 100644 --- a/core/res/res/layout-watch/preference_list_fragment_material.xml +++ b/core/res/res/layout-watch/preference_list_fragment_material.xml @@ -43,7 +43,7 @@ android:paddingStart="@dimen/dialog_padding_material" android:paddingEnd="@dimen/dialog_padding_material" android:paddingBottom="8dp" - android:textAppearance="@style/TextAppearance.Material.Title" + android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center_horizontal|top" /> </com.android.internal.widget.WatchHeaderListView> </FrameLayout> diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml index 2b9f95227f08..304affe068a4 100644 --- a/core/res/res/layout/splash_screen_view.xml +++ b/core/res/res/layout/splash_screen_view.xml @@ -36,6 +36,7 @@ android:layout_marginBottom="60dp" android:padding="0dp" android:background="@null" + android:forceHasOverlappingRendering="false" android:contentDescription="@string/splash_screen_view_branding_description"/> </android.window.SplashScreenView>
\ No newline at end of file diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml index 861e329f2de9..34b6a54be493 100644 --- a/core/res/res/values-sw600dp/config.xml +++ b/core/res/res/values-sw600dp/config.xml @@ -51,10 +51,5 @@ <!-- If true, show multiuser switcher by default unless the user specifically disables it. --> <bool name="config_showUserSwitcherByDefault">true</bool> - - <!-- Enable dynamic keyguard positioning for large-width screens. This will cause the keyguard - to be aligned to one side of the screen when in landscape mode. --> - <bool name="config_enableDynamicKeyguardPositioning">true</bool> - </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2c60fbd8f207..2b830b483d8d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1483,6 +1483,30 @@ <integer-array name="config_autoBrightnessLevels"> </integer-array> + <!-- Array of light sensor lux values to define our levels for auto backlight brightness + support whilst in idle mode. + The N entries of this array define N + 1 control points as follows: + (1-based arrays) + + Point 1: (0, value[1]): lux <= 0 + Point 2: (level[1], value[2]): 0 < lux <= level[1] + Point 3: (level[2], value[3]): level[2] < lux <= level[3] + ... + Point N+1: (level[N], value[N+1]): level[N] < lux + + The control points must be strictly increasing. Each control point + corresponds to an entry in the brightness backlight values arrays. + For example, if lux == level[1] (first element of the levels array) + then the brightness will be determined by value[2] (second element + of the brightness values array). + + Spline interpolation is used to determine the auto-brightness + backlight values for lux levels between these control points. + + Must be overridden in platform specific overlays --> + <integer-array name="config_autoBrightnessLevelsIdle"> + </integer-array> + <!-- Timeout (in milliseconds) after which we remove the effects any user interactions might've had on the brightness mapping. This timeout doesn't start until we transition to a non-interactive display policy so that we don't reset while users are using their devices, @@ -1506,6 +1530,10 @@ <integer-array name="config_autoBrightnessLcdBacklightValues_doze"> </integer-array> + <!-- Enables idle screen brightness mode on this device. + If this is true, config_autoBrightnessDisplayValuesNitsIdle must be defined. --> + <bool name="config_enableIdleScreenBrightnessMode">false</bool> + <!-- Array of desired screen brightness in nits corresponding to the lux values in the config_autoBrightnessLevels array. As with config_screenBrightnessMinimumNits and config_screenBrightnessMaximumNits, the display brightness is defined as the measured @@ -1522,6 +1550,13 @@ <array name="config_autoBrightnessDisplayValuesNits"> </array> + <!-- Array of desired screen brightness in nits for idle screen brightness mode. + This array should meet the same requirements as config_autoBrightnessDisplayValuesNits. + This array also corresponds to the lux values given in config_autoBrightnessLevelsIdle. + In order to activate this mode, config_enableIdleScreenBrightnessMode must be true. --> + <array name="config_autoBrightnessDisplayValuesNitsIdle"> + </array> + <!-- Array of output values for button backlight corresponding to the luX values in the config_autoBrightnessLevels array. This array should have size one greater than the size of the config_autoBrightnessLevels array. @@ -1767,6 +1802,13 @@ provider services. --> <string name="config_secondaryLocationTimeZoneProviderPackageName" translatable="false"></string> + <!-- Whether the time zone detection logic supports fall back from geolocation suggestions to + telephony suggestions temporarily in certain circumstances. Reduces time zone detection + latency during some scenarios like air travel. Only useful when both geolocation and + telephony time zone detection are supported on a device. + See com.android.server.timezonedetector.TimeZoneDetectorStrategy for more information. --> + <bool name="config_supportTelephonyTimeZoneFallback" translatable="false">false</bool> + <!-- Whether to enable network location overlay which allows network location provider to be replaced by an app at run-time. When disabled, only the config_networkLocationProviderPackageName package will be searched for network location @@ -4943,9 +4985,10 @@ the app in the letterbox mode. --> <item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item> - <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and - min between device bottom corner radii will be used instead. --> - <integer name="config_letterboxActivityCornersRadius">-1</integer> + <!-- Corners radius for activity presented the letterbox mode. Values < 0 enable rounded + corners with radius equal to min between device bottom corner radii. Default 0 value turns + off rounded corners logic in LetterboxUiController. --> + <integer name="config_letterboxActivityCornersRadius">0</integer> <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are ignored and 0 is used. --> @@ -5044,10 +5087,6 @@ <!-- Whether to select voice/data/sms preference without user confirmation --> <bool name="config_voice_data_sms_auto_fallback">false</bool> - <!-- Whether to enable dynamic keyguard positioning for wide screen devices (e.g. only using - half of the screen, to be accessible using only one hand). --> - <bool name="config_enableDynamicKeyguardPositioning">false</bool> - <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot --> <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index faa99027d9ad..45d9a36d244d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1902,6 +1902,7 @@ <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" /> <java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" /> <java-symbol type="array" name="config_autoBrightnessLevels" /> + <java-symbol type="array" name="config_autoBrightnessLevelsIdle" /> <java-symbol type="array" name="config_ambientThresholdLevels" /> <java-symbol type="array" name="config_ambientBrighteningThresholds" /> <java-symbol type="array" name="config_ambientDarkeningThresholds" /> @@ -2237,6 +2238,7 @@ <java-symbol type="string" name="config_primaryLocationTimeZoneProviderPackageName" /> <java-symbol type="bool" name="config_enableSecondaryLocationTimeZoneProvider" /> <java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" /> + <java-symbol type="bool" name="config_supportTelephonyTimeZoneFallback" /> <java-symbol type="bool" name="config_autoResetAirplaneMode" /> <java-symbol type="string" name="config_notificationAccessConfirmationActivity" /> <java-symbol type="bool" name="config_killableInputMethods" /> @@ -3788,7 +3790,9 @@ <java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" /> <java-symbol type="drawable" name="ic_logout" /> + <java-symbol type="bool" name="config_enableIdleScreenBrightnessMode" /> <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" /> + <java-symbol type="array" name="config_autoBrightnessDisplayValuesNitsIdle" /> <java-symbol type="array" name="config_screenBrightnessBacklight" /> <java-symbol type="array" name="config_screenBrightnessNits" /> @@ -4322,8 +4326,6 @@ <java-symbol type="bool" name="config_voice_data_sms_auto_fallback" /> - <java-symbol type="bool" name="config_enableDynamicKeyguardPositioning" /> - <java-symbol type="attr" name="colorAccentPrimary" /> <java-symbol type="attr" name="colorAccentSecondary" /> <java-symbol type="attr" name="colorAccentTertiary" /> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index 409025bc670d..8eb6ebcda826 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -587,7 +587,8 @@ public class BluetoothTestUtils extends Assert { final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); mContext.registerReceiver(receiver, filter); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); + assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE), + BluetoothStatusCodes.SUCCESS); boolean success = false; try { success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, @@ -637,7 +638,8 @@ public class BluetoothTestUtils extends Assert { final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); mContext.registerReceiver(receiver, filter); - assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE)); + assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE), + BluetoothStatusCodes.SUCCESS); boolean success = false; try { success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 207671e71854..60d48b226754 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -533,7 +533,7 @@ public class TransactionParcelTests { } @Override - public void scheduleCrash(String s, int i) throws RemoteException { + public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException { } @Override diff --git a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java new file mode 100644 index 000000000000..570b713479ee --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import static com.google.common.truth.Truth.assertThat; + +import static java.util.function.Function.identity; + +import android.content.pm.PackagePartitions.SystemPartition; +import android.os.SystemProperties; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +public class PackagePartitionsTest { + + @Test + public void testPackagePartitionsFingerprint() { + final ArrayList<SystemPartition> partitions = PackagePartitions.getOrderedPartitions( + identity()); + final String[] properties = new String[partitions.size() + 1]; + for (int i = 0; i < partitions.size(); i++) { + final String name = partitions.get(i).getName(); + properties[i] = "ro." + name + ".build.fingerprint"; + } + properties[partitions.size()] = "ro.build.fingerprint"; + + assertThat(SystemProperties.digestOf(properties)).isEqualTo(PackagePartitions.FINGERPRINT); + } +} diff --git a/core/tests/coretests/src/android/os/CombinedVibrationTest.java b/core/tests/coretests/src/android/os/CombinedVibrationTest.java index 06b5d180518f..508856ba4ae5 100644 --- a/core/tests/coretests/src/android/os/CombinedVibrationTest.java +++ b/core/tests/coretests/src/android/os/CombinedVibrationTest.java @@ -125,6 +125,12 @@ public class CombinedVibrationTest { VibrationEffect.createOneShot(1, 1)).getDuration()); assertEquals(-1, CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration()); + assertEquals(-1, CombinedVibration.createParallel( + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .getDuration()); assertEquals(Long.MAX_VALUE, CombinedVibration.createParallel( VibrationEffect.createWaveform( new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration()); @@ -175,6 +181,83 @@ public class CombinedVibrationTest { } @Test + public void testIsHapticFeedbackCandidateMono() { + assertTrue(CombinedVibration.createParallel( + VibrationEffect.createOneShot(1, 1)).isHapticFeedbackCandidate()); + assertTrue(CombinedVibration.createParallel( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).isHapticFeedbackCandidate()); + assertTrue(CombinedVibration.createParallel( + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .isHapticFeedbackCandidate()); + // Too long to be classified as a haptic feedback. + assertFalse(CombinedVibration.createParallel( + VibrationEffect.createOneShot(10_000, 1)).isHapticFeedbackCandidate()); + // Repeating vibrations should not be classified as a haptic feedback. + assertFalse(CombinedVibration.createParallel( + VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0)) + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidateStereo() { + assertTrue(CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.createOneShot(1, 1)) + .addVibrator(2, VibrationEffect.createWaveform(new long[]{6}, new int[]{1}, -1)) + .combine() + .isHapticFeedbackCandidate()); + assertTrue(CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addVibrator(2, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .isHapticFeedbackCandidate()); + // Repeating vibrations should not be classified as a haptic feedback. + assertFalse(CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addVibrator(2, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0)) + .combine() + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidateSequential() { + assertTrue(CombinedVibration.startSequential() + .addNext(1, VibrationEffect.createOneShot(10, 10), 10) + .addNext(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{1}, -1)) + .combine() + .isHapticFeedbackCandidate()); + assertTrue(CombinedVibration.startSequential() + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addNext(2, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL) + .compose()) + .combine() + .isHapticFeedbackCandidate()); + // Repeating vibrations should not be classified as a haptic feedback. + assertFalse(CombinedVibration.startSequential() + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addNext(2, VibrationEffect.createWaveform(new long[]{1}, new int[]{1}, 0)) + .combine() + .isHapticFeedbackCandidate()); + // Too many effects to be classified as a haptic feedback. + assertFalse(CombinedVibration.startSequential() + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) + .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)) + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_THUD)) + .combine() + .isHapticFeedbackCandidate()); + } + + @Test public void testHasVibratorMono_returnsTrueForAnyVibrator() { CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index 6cbfffc96116..781564b7be35 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -17,6 +17,7 @@ package android.os; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @@ -312,8 +313,106 @@ public class VibrationEffectTest { assertTrue(100 / 255f > ((StepSegment) scaledDown.getSegments().get(1)).getAmplitude()); } + + @Test + public void testDuration() { + assertEquals(1, VibrationEffect.createOneShot(1, 1).getDuration()); + assertEquals(-1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration()); + assertEquals(-1, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose() + .getDuration()); + assertEquals(6, VibrationEffect.createWaveform( + new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1).getDuration()); + assertEquals(Long.MAX_VALUE, VibrationEffect.createWaveform( + new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).getDuration()); + } + + @Test + public void testIsHapticFeedbackCandidate_repeatingEffects_notCandidates() { + assertFalse(VibrationEffect.createWaveform( + new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0).isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_longEffects_notCandidates() { + assertFalse(VibrationEffect.createOneShot(1500, 255).isHapticFeedbackCandidate()); + assertFalse(VibrationEffect.createWaveform( + new long[]{200, 200, 700}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate()); + assertFalse(VibrationEffect.startWaveform() + .addRamp(1, 500) + .addStep(1, 200) + .addRamp(0, 500) + .build() + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_shortEffects_areCandidates() { + assertTrue(VibrationEffect.createOneShot(500, 255).isHapticFeedbackCandidate()); + assertTrue(VibrationEffect.createWaveform( + new long[]{100, 200, 300}, new int[]{1, 2, 3}, -1).isHapticFeedbackCandidate()); + assertTrue(VibrationEffect.startWaveform() + .addRamp(1, 300) + .addStep(1, 200) + .addRamp(0, 300) + .build() + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_longCompositions_notCandidates() { + assertFalse(VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL) + .compose() + .isHapticFeedbackCandidate()); + + assertFalse(VibrationEffect.startComposition() + .addEffect(VibrationEffect.createOneShot(1500, 255)) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .compose() + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_shortCompositions_areCandidates() { + assertTrue(VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .compose() + .isHapticFeedbackCandidate()); + + assertTrue(VibrationEffect.startComposition() + .addEffect(VibrationEffect.createOneShot(100, 255)) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .compose() + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_prebakedRingtones_notCandidates() { + assertFalse(VibrationEffect.get( + VibrationEffect.RINGTONES[1]).isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_prebakedNotRingtoneConstants_areCandidates() { + assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_CLICK).isHapticFeedbackCandidate()); + assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_THUD).isHapticFeedbackCandidate()); + assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate()); + } + private Resources mockRingtoneResources() { - return mockRingtoneResources(new String[] { + return mockRingtoneResources(new String[]{ RINGTONE_URI_1, RINGTONE_URI_2, RINGTONE_URI_3 diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java index 0ac8f08c25cf..bdd76a5c162a 100644 --- a/core/tests/coretests/src/android/os/VibratorTest.java +++ b/core/tests/coretests/src/android/os/VibratorTest.java @@ -223,7 +223,7 @@ public class VibratorTest { public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage( - AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build(); + AudioAttributes.USAGE_VOICE_COMMUNICATION).build(); mVibratorSpy.vibrate(effect, audioAttributes); @@ -235,7 +235,7 @@ public class VibratorTest { assertEquals(VibrationAttributes.USAGE_COMMUNICATION_REQUEST, vibrationAttributes.getUsage()); // Keeps original AudioAttributes usage to be used by the VibratorService. - assertEquals(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY, + assertEquals(AudioAttributes.USAGE_VOICE_COMMUNICATION, vibrationAttributes.getAudioUsage()); } @@ -250,17 +250,4 @@ public class VibratorTest { VibrationAttributes vibrationAttributes = captor.getValue(); assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes); } - - @Test - public void vibrate_withoutAudioAttributesAndLongEffect_hasUnknownUsage() { - mVibratorSpy.vibrate(VibrationEffect.createOneShot(10_000, 255)); - - ArgumentCaptor<VibrationAttributes> captor = ArgumentCaptor.forClass( - VibrationAttributes.class); - verify(mVibratorSpy).vibrate(anyInt(), anyString(), any(), isNull(), captor.capture()); - - VibrationAttributes vibrationAttributes = captor.getValue(); - assertEquals(VibrationAttributes.USAGE_UNKNOWN, vibrationAttributes.getUsage()); - assertEquals(AudioAttributes.USAGE_UNKNOWN, vibrationAttributes.getAudioUsage()); - } } diff --git a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java index de80812f9c29..a0e1f437f1da 100644 --- a/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java +++ b/core/tests/coretests/src/android/os/vibrator/PrebakedSegmentTest.java @@ -17,6 +17,7 @@ package android.os.vibrator; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; @@ -105,4 +106,49 @@ public class PrebakedSegmentTest { VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM); assertSame(prebaked, prebaked.scale(0.5f)); } + + @Test + public void testDuration() { + assertEquals(-1, new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .getDuration()); + assertEquals(-1, new PrebakedSegment( + VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .getDuration()); + assertEquals(-1, new PrebakedSegment( + VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .getDuration()); + assertEquals(-1, new PrebakedSegment( + VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .getDuration()); + } + + @Test + public void testIsHapticFeedbackCandidate_prebakedConstants_areCandidates() { + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_HEAVY_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + assertTrue(new PrebakedSegment( + VibrationEffect.EFFECT_TEXTURE_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + } + + @Test + public void testIsHapticFeedbackCandidate_prebakedRingtones_notCandidates() { + assertFalse(new PrebakedSegment( + VibrationEffect.RINGTONES[1], true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + .isHapticFeedbackCandidate()); + } } diff --git a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java index 538655bb394b..a69055335663 100644 --- a/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java +++ b/core/tests/coretests/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -127,4 +127,28 @@ public class PrimitiveSegmentTest { assertEquals(0f, initial.scale(1.5f).scale(2 / 3f).getScale(), TOLERANCE); assertEquals(0f, initial.scale(0.8f).scale(1.25f).getScale(), TOLERANCE); } + + @Test + public void testDuration() { + assertEquals(-1, new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration()); + assertEquals(-1, new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100).getDuration()); + assertEquals(-1, new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_SPIN, 1, 0).getDuration()); + } + + @Test + public void testIsHapticFeedbackCandidate_returnsTrue() { + assertTrue(new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).isHapticFeedbackCandidate()); + assertTrue(new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10).isHapticFeedbackCandidate()); + assertTrue(new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10).isHapticFeedbackCandidate()); + assertTrue(new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_THUD, 1, 10).isHapticFeedbackCandidate()); + assertTrue(new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_SPIN, 1, 10).isHapticFeedbackCandidate()); + } } diff --git a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java index 174b4a76b3c3..5f80d2a10515 100644 --- a/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java +++ b/core/tests/coretests/src/android/os/vibrator/RampSegmentTest.java @@ -125,4 +125,16 @@ public class RampSegmentTest { assertEquals(0.35f, initial.scale(0.8f).getStartAmplitude(), TOLERANCE); assertEquals(0.5f, initial.scale(0.8f).scale(1.25f).getStartAmplitude(), TOLERANCE); } + + @Test + public void testDuration() { + assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration()); + } + + @Test + public void testIsHapticFeedbackCandidate_returnsTrue() { + // A single ramp segment duration is not checked here, but contributes to the effect known + // duration checked in VibrationEffect implementations. + assertTrue(new RampSegment(0.5f, 1, 0, 0, 5_000).isHapticFeedbackCandidate()); + } } diff --git a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java index 79529b8cb13c..fdce86a27ac4 100644 --- a/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java +++ b/core/tests/coretests/src/android/os/vibrator/StepSegmentTest.java @@ -141,4 +141,16 @@ public class StepSegmentTest { assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scale(1.5f).getAmplitude(), TOLERANCE); } + + @Test + public void testDuration() { + assertEquals(5, new StepSegment(0, 0, 5).getDuration()); + } + + @Test + public void testIsHapticFeedbackCandidate_returnsTrue() { + // A single step segment duration is not checked here, but contributes to the effect known + // duration checked in VibrationEffect implementations. + assertTrue(new StepSegment(0, 0, 5_000).isHapticFeedbackCandidate()); + } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index 33c6a4bae33f..e689b5d33107 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -45,7 +45,6 @@ import androidx.test.runner.AndroidJUnit4; import com.google.common.base.Throwables; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,13 +81,6 @@ public class AccessibilityCacheTest { mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher); } - @After - public void tearDown() { - // Make sure we're recycling all of our window and node infos. - mAccessibilityCache.clear(); - AccessibilityInteractionClient.getInstance().clearCache(); - } - @Test public void testEmptyCache_returnsNull() { assertNull(mAccessibilityCache.getNode(0, 0)); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java index 7e1e7f4bdd7f..3e061d208937 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -17,6 +17,9 @@ package android.view.accessibility; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.MockitoAnnotations.initMocks; @@ -40,6 +43,8 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public class AccessibilityInteractionClientTest { private static final int MOCK_CONNECTION_ID = 0xabcd; + private static final int MOCK_CONNECTION_OTHER_ID = 0xabce; + private MockConnection mMockConnection = new MockConnection(); @Mock private AccessibilityCache mMockCache; @@ -47,8 +52,8 @@ public class AccessibilityInteractionClientTest { @Before public void setUp() { initMocks(this); - AccessibilityInteractionClient.setCache(mMockCache); - AccessibilityInteractionClient.addConnection(MOCK_CONNECTION_ID, mMockConnection); + AccessibilityInteractionClient.addConnection( + MOCK_CONNECTION_ID, mMockConnection, /*initializeCache=*/true); } /** @@ -58,6 +63,7 @@ public class AccessibilityInteractionClientTest { */ @Test public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() { + AccessibilityInteractionClient.setCache(MOCK_CONNECTION_ID, mMockCache); final int windowId = 0x1234; final long accessibilityNodeId = 0x4321L; AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain(); @@ -71,6 +77,42 @@ public class AccessibilityInteractionClientTest { verifyZeroInteractions(mMockCache); } + @Test + public void getCache_differentConnections_returnsDifferentCaches() { + MockConnection mOtherMockConnection = new MockConnection(); + AccessibilityInteractionClient.addConnection( + MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/true); + + AccessibilityCache firstCache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID); + AccessibilityCache secondCache = AccessibilityInteractionClient.getCache( + MOCK_CONNECTION_OTHER_ID); + assertNotEquals(firstCache, secondCache); + } + + @Test + public void getCache_addConnectionWithoutCache_returnsNullCache() { + // Need to first remove from process cache + AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_OTHER_ID); + + MockConnection mOtherMockConnection = new MockConnection(); + AccessibilityInteractionClient.addConnection( + MOCK_CONNECTION_OTHER_ID, mOtherMockConnection, /*initializeCache=*/false); + + AccessibilityCache cache = AccessibilityInteractionClient.getCache( + MOCK_CONNECTION_OTHER_ID); + assertNull(cache); + } + + @Test + public void getCache_removeConnection_returnsNull() { + AccessibilityCache cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID); + assertNotNull(cache); + + AccessibilityInteractionClient.removeConnection(MOCK_CONNECTION_ID); + cache = AccessibilityInteractionClient.getCache(MOCK_CONNECTION_ID); + assertNull(cache); + } + private static class MockConnection extends AccessibilityServiceConnectionImpl { AccessibilityNodeInfo mInfoToReturn; diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java index a6e351d9cee7..52cb9f318dd0 100644 --- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java +++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java @@ -24,16 +24,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.content.res.Configuration; import android.os.Binder; import android.platform.test.annotations.Presubmit; -import android.view.IWindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -59,17 +56,14 @@ import org.mockito.MockitoAnnotations; public class WindowContextControllerTest { private WindowContextController mController; @Mock - private IWindowManager mMockWms; - @Mock private WindowTokenClient mMockToken; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mController = new WindowContextController(mMockToken, mMockWms); + mController = new WindowContextController(mMockToken); doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean()); - doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(), - anyInt(), anyInt(), any()); + doReturn(true).when(mMockToken).attachToDisplayArea(anyInt(), anyInt(), any()); } @Test(expected = IllegalStateException.class) @@ -81,10 +75,10 @@ public class WindowContextControllerTest { } @Test - public void testDetachIfNeeded_NotAttachedYet_DoNothing() throws Exception { + public void testDetachIfNeeded_NotAttachedYet_DoNothing() { mController.detachIfNeeded(); - verify(mMockWms, never()).detachWindowContextFromWindowContainer(any()); + verify(mMockToken, never()).detachFromWindowContainerIfNeeded(); } @Test @@ -93,8 +87,6 @@ public class WindowContextControllerTest { null /* options */); assertThat(mController.mAttachedToDisplayArea).isTrue(); - verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY), - eq(false) /* shouldReportConfigChange */); mController.detachIfNeeded(); diff --git a/data/etc/displayconfig/Android.bp b/data/etc/displayconfig/Android.bp new file mode 100644 index 000000000000..b2a45d21a4e0 --- /dev/null +++ b/data/etc/displayconfig/Android.bp @@ -0,0 +1,28 @@ +// Copyright 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"}; +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +prebuilt_etc { + name: "default_television.xml", + sub_dir: "displayconfig", + src: "default_television.xml", +} diff --git a/data/etc/displayconfig/OWNERS b/data/etc/displayconfig/OWNERS new file mode 100644 index 000000000000..6ce1ee4d3de2 --- /dev/null +++ b/data/etc/displayconfig/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/display/OWNERS diff --git a/data/etc/displayconfig/default_television.xml b/data/etc/displayconfig/default_television.xml new file mode 100644 index 000000000000..2540f5943c20 --- /dev/null +++ b/data/etc/displayconfig/default_television.xml @@ -0,0 +1,24 @@ +<displayConfiguration> + <densityMap> + <density> + <height>480</height> + <width>720</width> + <density>120</density> + </density> + <density> + <height>720</height> + <width>1280</width> + <density>213</density> + </density> + <density> + <height>1080</height> + <width>1920</width> + <density>320</density> + </density> + <density> + <height>2160</height> + <width>3840</width> + <density>640</density> + </density> + </densityMap> +</displayConfiguration> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 103b836ae237..f83f4019840a 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -422,6 +422,11 @@ applications that come with the platform <permission name="android.permission.MANAGE_NOTIFICATIONS"/> <!-- Permission required for CompanionDeviceManager CTS test. --> <permission name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" /> + <permission name="android.permission.MANAGE_COMPANION_DEVICES" /> + <permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" /> + <permission name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" /> + <permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" /> + <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" /> <!-- Permission required for testing registering pull atom callbacks. --> <permission name="android.permission.REGISTER_STATS_PULL_ATOM"/> <!-- Permission required for testing system audio effect APIs. --> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 8ccf02caf8c0..6bfbd8d55ab0 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -889,6 +889,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" }, + "-1145384901": { + "message": "shouldWaitAnimatingExit: isTransition: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", @@ -1273,6 +1279,12 @@ "group": "WM_DEBUG_SCREEN_ON", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-743856570": { + "message": "shouldWaitAnimatingExit: isAnimating: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-743431900": { "message": "Configuration no differences in %s", "level": "VERBOSE", @@ -1777,6 +1789,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-208825711": { + "message": "shouldWaitAnimatingExit: isWallpaperTarget: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -2989,6 +3007,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" }, + "1087494661": { + "message": "Clear window stuck on animatingExit status: %s", + "level": "WARN", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "1088929964": { "message": "onLockTaskPackagesUpdated: starting new locktask task=%s", "level": "DEBUG", diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 0f356a6d2d3c..2f56b181f455 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -74,6 +74,10 @@ import java.io.IOException; * getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(), * getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction()) * </pre> + * + * <p>An alternate drawable can be specified using <code><monochrome></code> tag which can be + * drawn in place of the two (background and foreground) layers. This drawable is tinted + * according to the device or surface theme. */ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback { @@ -120,6 +124,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ private static final int BACKGROUND_ID = 0; private static final int FOREGROUND_ID = 1; + private static final int MONOCHROME_ID = 2; /** * State variable that maintains the {@link ChildDrawable} array. @@ -188,6 +193,18 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback */ public AdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable) { + this(backgroundDrawable, foregroundDrawable, null); + } + + /** + * Constructor used to dynamically create this drawable. + * + * @param backgroundDrawable drawable that should be rendered in the background + * @param foregroundDrawable drawable that should be rendered in the foreground + * @param monochromeDrawable an alternate drawable which can be tinted per system theme color + */ + public AdaptiveIconDrawable(@Nullable Drawable backgroundDrawable, + @Nullable Drawable foregroundDrawable, @Nullable Drawable monochromeDrawable) { this((LayerState)null, null); if (backgroundDrawable != null) { addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable)); @@ -195,6 +212,9 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback if (foregroundDrawable != null) { addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable)); } + if (monochromeDrawable != null) { + addLayer(MONOCHROME_ID, createChildDrawable(monochromeDrawable)); + } } /** @@ -227,9 +247,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback state.mSourceDrawableId = Resources.getAttributeSetSourceResId(attrs); final ChildDrawable[] array = state.mChildren; - for (int i = 0; i < state.mChildren.length; i++) { - final ChildDrawable layer = array[i]; - layer.setDensity(deviceDensity); + for (int i = 0; i < array.length; i++) { + array[i].setDensity(deviceDensity); } inflateLayers(r, parser, attrs, theme); @@ -286,6 +305,18 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback return mLayerState.mChildren[BACKGROUND_ID].mDrawable; } + + /** + * Returns the monochrome version of this drawable. Callers can use a tinted version of + * this drawable instead of the original drawable on surfaces stressing user theming. + * + * @return the monochrome drawable + */ + @Nullable + public Drawable getMonochrome() { + return mLayerState.mChildren[MONOCHROME_ID].mDrawable; + } + @Override protected void onBoundsChange(Rect bounds) { if (bounds.isEmpty()) { @@ -316,9 +347,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { final ChildDrawable r = mLayerState.mChildren[i]; - if (r == null) { - continue; - } final Drawable d = r.mDrawable; if (d == null) { continue; @@ -359,14 +387,11 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback if (mLayersShader == null) { mCanvas.setBitmap(mLayersBitmap); mCanvas.drawColor(Color.BLACK); - for (int i = 0; i < mLayerState.N_CHILDREN; i++) { - if (mLayerState.mChildren[i] == null) { - continue; - } - final Drawable dr = mLayerState.mChildren[i].mDrawable; - if (dr != null) { - dr.draw(mCanvas); - } + if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) { + mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas); + } + if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) { + mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas); } mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); mPaint.setShader(mLayersShader); @@ -480,12 +505,18 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback continue; } String tagName = parser.getName(); - if (tagName.equals("background")) { - childIndex = BACKGROUND_ID; - } else if (tagName.equals("foreground")) { - childIndex = FOREGROUND_ID; - } else { - continue; + switch (tagName) { + case "background": + childIndex = BACKGROUND_ID; + break; + case "foreground": + childIndex = FOREGROUND_ID; + break; + case "monochrome": + childIndex = MONOCHROME_ID; + break; + default: + continue; } final ChildDrawable layer = new ChildDrawable(state.mDensity); @@ -941,7 +972,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback static class LayerState extends ConstantState { private int[] mThemeAttrs; - final static int N_CHILDREN = 2; + static final int N_CHILDREN = 3; ChildDrawable[] mChildren; // The density at which to render the drawable and its children. diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 7354c90c5c98..b843589376c4 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -79,7 +79,7 @@ import java.util.Arrays; * mask using {@code setId(..., android.R.id.mask)} or an existing mask layer * may be replaced using {@code setDrawableByLayerId(android.R.id.mask, ...)}. * <pre> - * <code><!-- A red ripple masked against an opaque rectangle. --/> + * <code><!-- A red ripple masked against an opaque rectangle. --> * <ripple android:color="#ffff0000"> * <item android:id="@android:id/mask" * android:drawable="@android:color/white" /> @@ -92,12 +92,12 @@ import java.util.Arrays; * If no mask layer is set, the ripple effect is masked against the composite * of the child layers. * <pre> - * <code><!-- A green ripple drawn atop a black rectangle. --/> + * <code><!-- A green ripple drawn atop a black rectangle. --> * <ripple android:color="#ff00ff00"> * <item android:drawable="@android:color/black" /> * </ripple> * - * <!-- A blue ripple drawn atop a drawable resource. --/> + * <!-- A blue ripple drawn atop a drawable resource. --> * <ripple android:color="#ff0000ff"> * <item android:drawable="@drawable/my_drawable" /> * </ripple></code> @@ -108,7 +108,7 @@ import java.util.Arrays; * background within the View's hierarchy. In this case, the drawing region * may extend outside of the Drawable bounds. * <pre> - * <code><!-- An unbounded red ripple. --/> + * <code><!-- An unbounded red ripple. --> * <ripple android:color="#ffff0000" /></code> * </pre> * diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index df5b3f582d8c..a34d0abcf753 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -287,7 +287,7 @@ public class MeasuredText { * * @param computeHyphenation true if you want to use automatic hyphenations. */ - public @NonNull Builder setComputeHyphenation(boolean computeHyphenation) { + public @NonNull @Deprecated Builder setComputeHyphenation(boolean computeHyphenation) { setComputeHyphenation( computeHyphenation ? HYPHENATION_MODE_NORMAL : HYPHENATION_MODE_NONE); return this; @@ -331,8 +331,6 @@ public class MeasuredText { * * {@link #HYPHENATION_MODE_NONE} is by default. * - * @see #setComputeHyphenation(boolean) - * * @param mode a hyphenation mode. */ public @NonNull Builder setComputeHyphenation(@HyphenationMode int mode) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 85ef270ac49d..df751fc9fa48 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -27,8 +27,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.ArrayMap; -import android.view.SurfaceControl; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; @@ -51,9 +49,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { /** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */ private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>(); - /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */ - private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>(); - /** * Mapping from the client assigned unique token to the TaskFragment parent * {@link Configuration}. @@ -67,7 +62,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { - void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo); + void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @@ -259,15 +254,12 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo(); - final IBinder fragmentToken = info.getFragmentToken(); - final SurfaceControl leash = taskFragmentAppearedInfo.getLeash(); - mFragmentInfos.put(fragmentToken, info); - mFragmentLeashes.put(fragmentToken, leash); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); + mFragmentInfos.put(fragmentToken, taskFragmentInfo); if (mCallback != null) { - mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo); + mCallback.onTaskFragmentAppeared(taskFragmentInfo); } } @@ -284,7 +276,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); - mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken()); mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken()); if (mCallback != null) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 68c19041940c..fe6c7ba3b24c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -37,7 +37,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -110,14 +109,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) { - TaskFragmentContainer container = getContainer( - taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken()); + public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } - container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo()); + container.setInfo(taskFragmentInfo); if (container.isFinished()) { mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); } diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS deleted file mode 100644 index e2c67fd8f8d4..000000000000 --- a/libs/WindowManager/Shell/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# sysui owners -hwwang@google.com -winsonc@google.com -madym@google.com diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml index 9f44acadb420..329e5b9b31a0 100644 --- a/packages/SystemUI/res/color/prv_text_color_on_accent.xml +++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml @@ -14,7 +14,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?androidprv:attr/textColorOnAccent" /> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="35" /> </selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 75bc46125324..8e98b82088dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; +import android.app.WindowConfiguration; import android.content.Context; import android.content.LocusId; import android.content.pm.ActivityInfo; @@ -122,6 +123,16 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Callbacks for events in which the focus has changed. + */ + public interface FocusListener { + /** + * Notifies when the task which is focused has changed. + */ + void onFocusTaskChanged(RunningTaskInfo taskInfo); + } + + /** * Keys map from either a task id or {@link TaskListenerType}. * @see #addListenerForTaskId * @see #addListenerForType @@ -142,6 +153,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements /** @see #addLocusIdListener */ private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>(); + private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>(); + private final Object mLock = new Object(); private StartingWindowController mStartingWindow; @@ -155,6 +168,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements @Nullable private final Optional<RecentTasksController> mRecentTasks; + @Nullable + private RunningTaskInfo mLastFocusedTaskInfo; + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, Optional.empty() /* recentTasksController */); @@ -200,6 +216,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + @Override + public void unregisterOrganizer() { + super.unregisterOrganizer(); + if (mStartingWindow != null) { + mStartingWindow.clearAllWindows(); + } + } + public void createRootTask(int displayId, int windowingMode, TaskListener listener) { ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s", displayId, windowingMode, listener.toString()); @@ -331,6 +355,27 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Adds a listener to be notified for task focus changes. + */ + public void addFocusListener(FocusListener listener) { + synchronized (mLock) { + mFocusListeners.add(listener); + if (mLastFocusedTaskInfo != null) { + listener.onFocusTaskChanged(mLastFocusedTaskInfo); + } + } + } + + /** + * Removes listener. + */ + public void removeLocusIdListener(FocusListener listener) { + synchronized (mLock) { + mFocusListeners.remove(listener); + } + } + @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { if (mStartingWindow != null) { @@ -422,6 +467,18 @@ public class ShellTaskOrganizer extends TaskOrganizer implements mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskWindowingModeChanged(taskInfo)); } + // TODO (b/207687679): Remove check for HOME once bug is fixed + final boolean isFocusedOrHome = taskInfo.isFocused + || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME + && taskInfo.isVisible); + final boolean focusTaskChanged = (mLastFocusedTaskInfo == null + || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome; + if (focusTaskChanged) { + for (int i = 0; i < mFocusListeners.size(); i++) { + mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo); + } + mLastFocusedTaskInfo = taskInfo; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java index c8449a362988..c807f667868a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java @@ -291,8 +291,10 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou final String innerPrefix = prefix + " "; final String childPrefix = innerPrefix + " "; pw.println(prefix + this); - pw.println(innerPrefix + "Root taskId=" + getRootTaskId() - + " winMode=" + mRootTaskInfo.getWindowingMode()); + if (mRootTaskInfo != null) { + pw.println(innerPrefix + "Root taskId=" + mRootTaskInfo.taskId + + " winMode=" + mRootTaskInfo.getWindowingMode()); + } if (mTaskInfo1 != null) { pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId + " winMode=" + mTaskInfo1.getWindowingMode()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS new file mode 100644 index 000000000000..4d9b520e3f0e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-modules apppair owner +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 300319a2f78f..b40021ec82a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -32,6 +32,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -529,9 +530,10 @@ public class BubbleStackView extends FrameLayout // Otherwise, we either tapped the stack (which means we're collapsed // and should expand) or the currently selected bubble (we're expanded // and should collapse). - if (!maybeShowStackEdu()) { + if (!maybeShowStackEdu() && !mShowedUserEducationInTouchListenerActive) { mBubbleData.setExpanded(!mBubbleData.isExpanded()); } + mShowedUserEducationInTouchListenerActive = false; } } }; @@ -549,6 +551,14 @@ public class BubbleStackView extends FrameLayout return true; } + mShowedUserEducationInTouchListenerActive = false; + if (maybeShowStackEdu()) { + mShowedUserEducationInTouchListenerActive = true; + return true; + } else if (isStackEduShowing()) { + mStackEduView.hide(false /* fromExpansion */); + } + // If the manage menu is visible, just hide it. if (mShowingManage) { showManageMenu(false /* show */); @@ -607,7 +617,8 @@ public class BubbleStackView extends FrameLayout // If we're expanding or collapsing, ignore all touch events. if (mIsExpansionAnimating // Also ignore events if we shouldn't be draggable. - || (mPositioner.showingInTaskbar() && !mIsExpanded)) { + || (mPositioner.showingInTaskbar() && !mIsExpanded) + || mShowedUserEducationInTouchListenerActive) { return; } @@ -628,7 +639,7 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); } else { - if (mStackEduView != null) { + if (isStackEduShowing()) { mStackEduView.hide(false /* fromExpansion */); } mStackAnimationController.moveStackFromTouch( @@ -646,6 +657,10 @@ public class BubbleStackView extends FrameLayout || (mPositioner.showingInTaskbar() && !mIsExpanded)) { return; } + if (mShowedUserEducationInTouchListenerActive) { + mShowedUserEducationInTouchListenerActive = false; + return; + } // First, see if the magnetized object consumes the event - if so, the bubble was // released in the target or flung out of it, and we should ignore the event. @@ -738,6 +753,7 @@ public class BubbleStackView extends FrameLayout private ImageView mManageSettingsIcon; private TextView mManageSettingsText; private boolean mShowingManage = false; + private boolean mShowedUserEducationInTouchListenerActive = false; private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); private BubblePositioner mPositioner; @@ -929,10 +945,12 @@ public class BubbleStackView extends FrameLayout showManageMenu(false /* show */); } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { mManageEduView.hide(); - } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + } else if (isStackEduShowing()) { mStackEduView.hide(false /* isExpanding */); } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); + } else { + maybeShowStackEdu(); } }); @@ -1116,6 +1134,9 @@ public class BubbleStackView extends FrameLayout * Whether the educational view should show for the expanded view "manage" menu. */ private boolean shouldShowManageEdu() { + if (ActivityManager.isRunningInTestHarness()) { + return false; + } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) && mExpandedBubble != null; @@ -1140,6 +1161,9 @@ public class BubbleStackView extends FrameLayout * Whether education view should show for the collapsed stack. */ private boolean shouldShowStackEdu() { + if (ActivityManager.isRunningInTestHarness()) { + return false; + } final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION); final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext); if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { @@ -1157,7 +1181,7 @@ public class BubbleStackView extends FrameLayout * @return true if education view for collapsed stack should show and was not showing before. */ private boolean maybeShowStackEdu() { - if (!shouldShowStackEdu()) { + if (!shouldShowStackEdu() || isExpanded()) { return false; } if (mStackEduView == null) { @@ -1168,9 +1192,13 @@ public class BubbleStackView extends FrameLayout return mStackEduView.show(mPositioner.getDefaultStartPosition()); } + private boolean isStackEduShowing() { + return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE; + } + // Recreates & shows the education views. Call when a theme/config change happens. private void updateUserEdu() { - if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + if (isStackEduShowing()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); @@ -1852,7 +1880,7 @@ public class BubbleStackView extends FrameLayout cancelDelayedExpandCollapseSwitchAnimations(); final boolean showVertically = mPositioner.showBubblesVertically(); mIsExpanded = true; - if (mStackEduView != null) { + if (isStackEduShowing()) { mStackEduView.hide(true /* fromExpansion */); } beforeExpandedViewAnimation(); @@ -2390,7 +2418,7 @@ public class BubbleStackView extends FrameLayout if (flyoutMessage == null || flyoutMessage.message == null || !bubble.showFlyout() - || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) + || isStackEduShowing() || isExpanded() || mIsExpansionAnimating || mIsGestureInProgress @@ -2512,7 +2540,7 @@ public class BubbleStackView extends FrameLayout * them. */ public void getTouchableRegion(Rect outRect) { - if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + if (isStackEduShowing()) { // When user education shows then capture all touches outRect.set(0, 0, getWidth(), getHeight()); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS new file mode 100644 index 000000000000..8271014d290e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-module bubble owner +madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index f6a90b7a76cd..3846de73842d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -125,6 +125,7 @@ class StackEducationView constructor( * @return true if user education was shown, false otherwise. */ fun show(stackPosition: PointF): Boolean { + isHiding = false if (visibility == VISIBLE) return false controller.updateWindowFlagsForBackpress(true /* interceptBack */) @@ -164,6 +165,7 @@ class StackEducationView constructor( */ fun hide(isExpanding: Boolean) { if (visibility != VISIBLE || isHiding) return + isHiding = true controller.updateWindowFlagsForBackpress(false /* interceptBack */) animate() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index d07fff3cce79..3579bf441390 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -321,6 +321,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mSplitLayoutHandler.onLayoutSizeChanged(this); } + /** Sets divide position base on the ratio within root bounds. */ + public void setDivideRatio(float ratio) { + final int position = isLandscape() + ? mRootBounds.left + (int) (mRootBounds.width() * ratio) + : mRootBounds.top + (int) (mRootBounds.height() * ratio); + DividerSnapAlgorithm.SnapTarget snapTarget = + mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); + setDividePosition(snapTarget.position); + } + /** Resets divider position. */ public void resetDividerPosition() { mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 287d0fb66aa9..54ce6bb7acba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -56,6 +56,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; +import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; @@ -156,8 +157,16 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static DragAndDropController provideDragAndDropController(Context context, - DisplayController displayController, UiEventLogger uiEventLogger) { - return new DragAndDropController(context, displayController, uiEventLogger); + DisplayController displayController, UiEventLogger uiEventLogger, + IconProvider iconProvider, @ShellMainThread ShellExecutor mainExecutor) { + return new DragAndDropController(context, displayController, uiEventLogger, iconProvider, + mainExecutor); + } + + @WMSingleton + @Provides + static DragAndDrop provideDragAndDrop(DragAndDropController dragAndDropController) { + return dragAndDropController.asDragAndDrop(); } @WMSingleton @@ -346,7 +355,7 @@ public abstract class WMShellBaseModule { @Provides static Optional<OneHandedController> providesOneHandedController( @DynamicOverride Optional<OneHandedController> oneHandedController) { - if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { + if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { return oneHandedController; } return Optional.empty(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 46c7b508d6e8..f562fd9cf1af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -241,10 +241,11 @@ public class WMShellModule { static PhonePipMenuController providesPipPhoneMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController pipMediaController, SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, mainExecutor, mainHandler); + systemWindows, splitScreenOptional, mainExecutor, mainHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java new file mode 100644 index 000000000000..edeff6e37182 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.draganddrop; + +import android.content.res.Configuration; + +import com.android.wm.shell.common.annotations.ExternalThread; + +/** + * Interface for telling DragAndDrop stuff. + */ +@ExternalThread +public interface DragAndDrop { + + /** Called when the theme changes. */ + void onThemeChanged(); + + /** Called when the configuration changes. */ + void onConfigChanged(Configuration newConfig); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index d2b4711d30af..101295d246bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -41,7 +41,6 @@ import android.content.res.Configuration; import android.graphics.PixelFormat; import android.util.Slog; import android.util.SparseArray; -import android.view.Display; import android.view.DragEvent; import android.view.LayoutInflater; import android.view.SurfaceControl; @@ -53,8 +52,10 @@ import android.widget.FrameLayout; import com.android.internal.logging.InstanceId; import com.android.internal.logging.UiEventLogger; import com.android.internal.protolog.common.ProtoLog; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -71,16 +72,26 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange private final Context mContext; private final DisplayController mDisplayController; private final DragAndDropEventLogger mLogger; + private final IconProvider mIconProvider; private SplitScreenController mSplitScreen; + private ShellExecutor mMainExecutor; + private DragAndDropImpl mImpl; private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); public DragAndDropController(Context context, DisplayController displayController, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) { mContext = context; mDisplayController = displayController; mLogger = new DragAndDropEventLogger(uiEventLogger); + mIconProvider = iconProvider; + mMainExecutor = mainExecutor; + mImpl = new DragAndDropImpl(); + } + + public DragAndDrop asDragAndDrop() { + return mImpl; } public void initialize(Optional<SplitScreenController> splitscreen) { @@ -117,7 +128,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange R.layout.global_drop_target, null); rootView.setOnDragListener(this); rootView.setVisibility(View.INVISIBLE); - DragLayout dragLayout = new DragLayout(context, mSplitScreen); + DragLayout dragLayout = new DragLayout(context, mSplitScreen, mIconProvider); rootView.addView(dragLayout, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); try { @@ -267,6 +278,18 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange return mimeTypes; } + private void onThemeChange() { + for (int i = 0; i < mDisplayDropTargets.size(); i++) { + mDisplayDropTargets.get(i).dragLayout.onThemeChange(); + } + } + + private void onConfigChanged(Configuration newConfig) { + for (int i = 0; i < mDisplayDropTargets.size(); i++) { + mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig); + } + } + private static class PerDisplay { final int displayId; final Context context; @@ -287,4 +310,21 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange dragLayout = dl; } } + + private class DragAndDropImpl implements DragAndDrop { + + @Override + public void onThemeChanged() { + mMainExecutor.execute(() -> { + DragAndDropController.this.onThemeChange(); + }); + } + + @Override + public void onConfigChanged(Configuration newConfig) { + mMainExecutor.execute(() -> { + DragAndDropController.this.onConfigChanged(newConfig); + }); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index efc9ed0f75b2..20d8054f6e90 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -16,78 +16,138 @@ package com.android.wm.shell.draganddrop; -import static com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN; -import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN; -import static com.android.wm.shell.animation.Interpolators.LINEAR; -import static com.android.wm.shell.animation.Interpolators.LINEAR_OUT_SLOW_IN; +import static android.app.StatusBarManager.DISABLE_NONE; + +import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.StatusBarManager; import android.content.ClipData; import android.content.Context; -import android.graphics.Canvas; +import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Insets; -import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.RemoteException; import android.view.DragEvent; import android.view.SurfaceControl; -import android.view.View; +import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsets.Type; - -import androidx.annotation.NonNull; +import android.widget.LinearLayout; import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; import java.util.ArrayList; +import java.util.List; /** * Coordinates the visible drop targets for the current drag. */ -public class DragLayout extends View { +public class DragLayout extends LinearLayout { + + // While dragging the status bar is hidden. + private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS + | StatusBarManager.DISABLE_NOTIFICATION_ALERTS + | StatusBarManager.DISABLE_CLOCK + | StatusBarManager.DISABLE_SYSTEM_INFO; private final DragAndDropPolicy mPolicy; + private final SplitScreenController mSplitScreenController; + private final IconProvider mIconProvider; + private final StatusBarManager mStatusBarManager; private DragAndDropPolicy.Target mCurrentTarget = null; - private DropOutlineDrawable mDropOutline; + private DropZoneView mDropZoneView1; + private DropZoneView mDropZoneView2; + private int mDisplayMargin; private Insets mInsets = Insets.NONE; private boolean mIsShowing; private boolean mHasDropped; - public DragLayout(Context context, SplitScreenController splitscreen) { + @SuppressLint("WrongConstant") + public DragLayout(Context context, SplitScreenController splitScreenController, + IconProvider iconProvider) { super(context); - mPolicy = new DragAndDropPolicy(context, splitscreen); + mSplitScreenController = splitScreenController; + mIconProvider = iconProvider; + mPolicy = new DragAndDropPolicy(context, splitScreenController); + mStatusBarManager = context.getSystemService(StatusBarManager.class); + mDisplayMargin = context.getResources().getDimensionPixelSize( R.dimen.drop_layout_display_margin); - mDropOutline = new DropOutlineDrawable(context); - setBackground(mDropOutline); - setWillNotDraw(false); + + mDropZoneView1 = new DropZoneView(context); + mDropZoneView2 = new DropZoneView(context); + addView(mDropZoneView1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + addView(mDropZoneView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1; + ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1; + updateContainerMargins(); } @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout()); recomputeDropTargets(); + + final int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + mDropZoneView1.setBottomInset(mInsets.bottom); + mDropZoneView2.setBottomInset(mInsets.bottom); + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + mDropZoneView1.setBottomInset(0); + mDropZoneView2.setBottomInset(mInsets.bottom); + } return super.onApplyWindowInsets(insets); } - @Override - protected boolean verifyDrawable(@NonNull Drawable who) { - return who == mDropOutline || super.verifyDrawable(who); + public void onThemeChange() { + mDropZoneView1.onThemeChange(); + mDropZoneView2.onThemeChange(); } - @Override - protected void onDraw(Canvas canvas) { - if (mCurrentTarget != null) { - mDropOutline.draw(canvas); + public void onConfigChanged(Configuration newConfig) { + final int orientation = getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE + && getOrientation() != HORIZONTAL) { + setOrientation(LinearLayout.HORIZONTAL); + updateContainerMargins(); + } else if (orientation == Configuration.ORIENTATION_PORTRAIT + && getOrientation() != VERTICAL) { + setOrientation(LinearLayout.VERTICAL); + updateContainerMargins(); + } + } + + private void updateContainerMargins() { + final int orientation = getResources().getConfiguration().orientation; + final float halfMargin = mDisplayMargin / 2f; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + mDropZoneView1.setContainerMargin( + mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin); + mDropZoneView2.setContainerMargin( + halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin); + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + mDropZoneView1.setContainerMargin( + mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin); + mDropZoneView2.setContainerMargin( + mDisplayMargin, halfMargin, mDisplayMargin, mDisplayMargin); } } @@ -104,6 +164,43 @@ public class DragLayout extends View { mPolicy.start(displayLayout, initialData, loggerSessionId); mHasDropped = false; mCurrentTarget = null; + + List<ActivityManager.RunningTaskInfo> tasks = null; + // Figure out the splashscreen info for the existing task(s). + try { + tasks = ActivityTaskManager.getService().getTasks(2, + false /* filterOnlyVisibleRecents */, + false /* keepIntentExtra */); + } catch (RemoteException e) { + // don't show an icon / will just use the defaults + } + if (tasks != null && !tasks.isEmpty()) { + ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0); + Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); + int bgColor1 = getResizingBackgroundColor(taskInfo1); + + boolean alreadyInSplit = mSplitScreenController != null + && mSplitScreenController.isSplitScreenVisible(); + if (alreadyInSplit && tasks.size() > 1) { + ActivityManager.RunningTaskInfo taskInfo2 = tasks.get(1); + Drawable icon2 = mIconProvider.getIcon(taskInfo2.topActivityInfo); + int bgColor2 = getResizingBackgroundColor(taskInfo2); + + // figure out which task is on which side + int splitPosition1 = mSplitScreenController.getSplitPosition(taskInfo1.taskId); + boolean isTask1TopOrLeft = splitPosition1 == SPLIT_POSITION_TOP_OR_LEFT; + if (isTask1TopOrLeft) { + mDropZoneView1.setAppInfo(bgColor1, icon1); + mDropZoneView2.setAppInfo(bgColor2, icon2); + } else { + mDropZoneView2.setAppInfo(bgColor1, icon1); + mDropZoneView1.setAppInfo(bgColor2, icon2); + } + } else { + mDropZoneView1.setAppInfo(bgColor1, icon1); + mDropZoneView2.setAppInfo(bgColor1, icon1); + } + } } public void show() { @@ -139,20 +236,14 @@ public class DragLayout extends View { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); if (target == null) { // Animating to no target - mDropOutline.startVisibilityAnimation(false, LINEAR); - Rect finalBounds = new Rect(mCurrentTarget.drawRegion); - finalBounds.inset(mDisplayMargin, mDisplayMargin); - mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN); + animateSplitContainers(false, null /* animCompleteCallback */); } else if (mCurrentTarget == null) { // Animating to first target - mDropOutline.startVisibilityAnimation(true, LINEAR); - Rect initialBounds = new Rect(target.drawRegion); - initialBounds.inset(mDisplayMargin, mDisplayMargin); - mDropOutline.setRegionBounds(initialBounds); - mDropOutline.startBoundsAnimation(target.drawRegion, LINEAR_OUT_SLOW_IN); + animateSplitContainers(true, null /* animCompleteCallback */); + animateHighlight(target); } else { - // Bounds change - mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN); + // Switching between targets + animateHighlight(target); } mCurrentTarget = target; } @@ -163,26 +254,7 @@ public class DragLayout extends View { */ public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; - ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR); - ObjectAnimator boundsAnimator = null; - if (mCurrentTarget != null) { - Rect finalBounds = new Rect(mCurrentTarget.drawRegion); - finalBounds.inset(mDisplayMargin, mDisplayMargin); - boundsAnimator = mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN); - } - - if (hideCompleteCallback != null) { - ObjectAnimator lastAnim = boundsAnimator != null - ? boundsAnimator - : alphaAnimator; - lastAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - hideCompleteCallback.run(); - } - }); - } - + animateSplitContainers(false, hideCompleteCallback); mCurrentTarget = null; } @@ -201,4 +273,49 @@ public class DragLayout extends View { hide(event, dropCompleteCallback); return handledDrop; } + + private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) { + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); + mDropZoneView1.setShowingMargin(visible); + mDropZoneView2.setShowingMargin(visible); + ObjectAnimator animator = mDropZoneView1.getAnimator(); + if (animCompleteCallback != null) { + if (animator != null) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animCompleteCallback.run(); + } + }); + } else { + // If there's no animator the animation is done so run immediately + animCompleteCallback.run(); + } + } + } + + private void animateHighlight(DragAndDropPolicy.Target target) { + if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT + || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) { + mDropZoneView1.setShowingHighlight(true); + mDropZoneView1.setShowingSplash(false); + + mDropZoneView2.setShowingHighlight(false); + mDropZoneView2.setShowingSplash(true); + } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT + || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) { + mDropZoneView1.setShowingHighlight(false); + mDropZoneView1.setShowingSplash(true); + + mDropZoneView2.setShowingHighlight(true); + mDropZoneView2.setShowingSplash(false); + } + } + + private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java new file mode 100644 index 000000000000..2f47af57d496 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.draganddrop; + +import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Path; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.IntProperty; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.wm.shell.R; + +/** + * Renders a drop zone area for items being dragged. + */ +public class DropZoneView extends FrameLayout { + + private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f); + private static final int HIGHLIGHT_ALPHA_INT = 255; + private static final int MARGIN_ANIMATION_ENTER_DURATION = 400; + private static final int MARGIN_ANIMATION_EXIT_DURATION = 250; + + private static final FloatProperty<DropZoneView> INSETS = + new FloatProperty<DropZoneView>("insets") { + @Override + public void setValue(DropZoneView v, float percent) { + v.setMarginPercent(percent); + } + + @Override + public Float get(DropZoneView v) { + return v.getMarginPercent(); + } + }; + + private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA = + new IntProperty<ColorDrawable>("splashscreen") { + @Override + public void setValue(ColorDrawable d, int alpha) { + d.setAlpha(alpha); + } + + @Override + public Integer get(ColorDrawable d) { + return d.getAlpha(); + } + }; + + private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA = + new IntProperty<ColorDrawable>("highlight") { + @Override + public void setValue(ColorDrawable d, int alpha) { + d.setAlpha(alpha); + } + + @Override + public Integer get(ColorDrawable d) { + return d.getAlpha(); + } + }; + + private final Path mPath = new Path(); + private final float[] mContainerMargin = new float[4]; + private float mCornerRadius; + private float mBottomInset; + private int mMarginColor; // i.e. color used for negative space like the container insets + private int mHighlightColor; + + private boolean mShowingHighlight; + private boolean mShowingSplash; + private boolean mShowingMargin; + + // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate + private ObjectAnimator mSplashAnimator; + private ObjectAnimator mHighlightAnimator; + private ObjectAnimator mMarginAnimator; + private float mMarginPercent; + + // Renders a highlight or neutral transparent color + private ColorDrawable mDropZoneDrawable; + // Renders the translucent splashscreen with the app icon in the middle + private ImageView mSplashScreenView; + private ColorDrawable mSplashBackgroundDrawable; + // Renders the margin / insets around the dropzone container + private MarginView mMarginView; + + public DropZoneView(Context context) { + this(context, null); + } + + public DropZoneView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setContainerMargin(0, 0, 0, 0); // make sure it's populated + + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + mMarginColor = getResources().getColor(R.color.taskbar_background); + mHighlightColor = getResources().getColor(android.R.color.system_accent1_500); + + mDropZoneDrawable = new ColorDrawable(); + mDropZoneDrawable.setColor(mHighlightColor); + mDropZoneDrawable.setAlpha(0); + setBackgroundDrawable(mDropZoneDrawable); + + mSplashScreenView = new ImageView(context); + mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER); + mSplashBackgroundDrawable = new ColorDrawable(); + mSplashBackgroundDrawable.setColor(Color.WHITE); + mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT); + mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable); + addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + mSplashScreenView.setAlpha(0f); + + mMarginView = new MarginView(context); + addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + + public void onThemeChange() { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext()); + mMarginColor = getResources().getColor(R.color.taskbar_background); + mHighlightColor = getResources().getColor(android.R.color.system_accent1_500); + + final int alpha = mDropZoneDrawable.getAlpha(); + mDropZoneDrawable.setColor(mHighlightColor); + mDropZoneDrawable.setAlpha(alpha); + + if (mMarginPercent > 0) { + mMarginView.invalidate(); + } + } + + /** Sets the desired margins around the drop zone container when fully showing. */ + public void setContainerMargin(float left, float top, float right, float bottom) { + mContainerMargin[0] = left; + mContainerMargin[1] = top; + mContainerMargin[2] = right; + mContainerMargin[3] = bottom; + if (mMarginPercent > 0) { + mMarginView.invalidate(); + } + } + + /** Sets the bottom inset so the drop zones are above bottom navigation. */ + public void setBottomInset(float bottom) { + mBottomInset = bottom; + ((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom; + if (mMarginPercent > 0) { + mMarginView.invalidate(); + } + } + + /** Sets the color and icon to use for the splashscreen when shown. */ + public void setAppInfo(int splashScreenColor, Drawable appIcon) { + mSplashBackgroundDrawable.setColor(splashScreenColor); + mSplashScreenView.setImageDrawable(appIcon); + } + + /** @return an active animator for this view if one exists. */ + @Nullable + public ObjectAnimator getAnimator() { + if (mMarginAnimator != null && mMarginAnimator.isRunning()) { + return mMarginAnimator; + } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) { + return mHighlightAnimator; + } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) { + return mSplashAnimator; + } + return null; + } + + /** Animates the splashscreen to show or hide. */ + public void setShowingSplash(boolean showingSplash) { + if (mShowingSplash != showingSplash) { + mShowingSplash = showingSplash; + animateSplashToState(); + } + } + + /** Animates the highlight indicating the zone is hovered on or not. */ + public void setShowingHighlight(boolean showingHighlight) { + if (mShowingHighlight != showingHighlight) { + mShowingHighlight = showingHighlight; + animateHighlightToState(); + } + } + + /** Animates the margins around the drop zone to show or hide. */ + public void setShowingMargin(boolean visible) { + if (mShowingMargin != visible) { + mShowingMargin = visible; + animateMarginToState(); + } + if (!mShowingMargin) { + setShowingHighlight(false); + setShowingSplash(false); + } + } + + private void animateSplashToState() { + if (mSplashAnimator != null) { + mSplashAnimator.cancel(); + } + mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable, + SPLASHSCREEN_ALPHA, + mSplashBackgroundDrawable.getAlpha(), + mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0); + if (!mShowingSplash) { + mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN); + } + mSplashAnimator.start(); + mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start(); + } + + private void animateHighlightToState() { + if (mHighlightAnimator != null) { + mHighlightAnimator.cancel(); + } + mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable, + HIGHLIGHT_ALPHA, + mDropZoneDrawable.getAlpha(), + mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0); + if (!mShowingHighlight) { + mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN); + } + mHighlightAnimator.start(); + } + + private void animateMarginToState() { + if (mMarginAnimator != null) { + mMarginAnimator.cancel(); + } + mMarginAnimator = ObjectAnimator.ofFloat(this, INSETS, + mMarginPercent, + mShowingMargin ? 1f : 0f); + mMarginAnimator.setInterpolator(FAST_OUT_SLOW_IN); + mMarginAnimator.setDuration(mShowingMargin + ? MARGIN_ANIMATION_ENTER_DURATION + : MARGIN_ANIMATION_EXIT_DURATION); + mMarginAnimator.start(); + } + + private void setMarginPercent(float percent) { + if (percent != mMarginPercent) { + mMarginPercent = percent; + mMarginView.invalidate(); + } + } + + private float getMarginPercent() { + return mMarginPercent; + } + + /** Simple view that draws a rounded rect margin around its contents. **/ + private class MarginView extends View { + + MarginView(Context context) { + super(context); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mPath.reset(); + mPath.addRoundRect(mContainerMargin[0] * mMarginPercent, + mContainerMargin[1] * mMarginPercent, + getWidth() - (mContainerMargin[2] * mMarginPercent), + getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset, + mCornerRadius * mMarginPercent, + mCornerRadius * mMarginPercent, + Path.Direction.CW); + mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD); + canvas.clipPath(mPath); + canvas.drawColor(mMarginColor); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 5c8e7d03eb01..52ff21bc3172 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -16,7 +16,6 @@ package com.android.wm.shell.freeform; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; @@ -28,7 +27,6 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.SurfaceControl; -import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; @@ -85,13 +83,6 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); return; } - - // Clears windowing mode and window bounds to let the task inherits from its new parent. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setBounds(taskInfo.token, null) - .setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - mSyncQueue.queue(wct); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d", taskInfo.taskId); mTasks.remove(taskInfo.taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OWNERS new file mode 100644 index 000000000000..41177f01b208 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-module one handed mode owner +lbill@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS new file mode 100644 index 000000000000..afddfab99a2b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-module pip owner +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 8d9ad4d1b96c..caa1f017082b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -90,6 +90,11 @@ public interface PipMenuController { default void updateMenuBounds(Rect destinationBounds) {} /** + * Update when the current focused task changes. + */ + default void onFocusTaskChanged(RunningTaskInfo taskInfo) {} + + /** * Returns a default LayoutParams for the PIP Menu. * @param width the PIP stack width. * @param height the PIP stack height. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 6cc5f09827af..854fc60e15e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -98,7 +98,7 @@ import java.util.function.IntConsumer; * see also {@link PipMotionHelper}. */ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, - DisplayController.OnDisplaysChangedListener { + DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; /** @@ -286,6 +286,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor.execute(() -> { mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); }); + mTaskOrganizer.addFocusListener(this); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); } @@ -772,6 +773,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } @Override + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + mPipMenuController.onFocusTaskChanged(taskInfo); + } + + @Override public boolean supportSizeCompatUI() { // PIP doesn't support size compat. return false; @@ -1249,11 +1255,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } else if (isOutPipDirection(direction)) { // If we are animating to fullscreen or split screen, then we need to reset the // override bounds on the task to ensure that the task "matches" the parent's bounds. - if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { - taskBounds = destinationBounds; - } else { - taskBounds = null; - } + taskBounds = null; applyWindowingModeChangeOnExit(wct, direction); } else { // Just a resize in PIP @@ -1282,6 +1284,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private boolean isPipTopLeft() { + if (!mSplitScreenOptional.isPresent()) { + return false; + } final Rect topLeft = new Rect(); final Rect bottomRight = new Rect(); mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 5687f4d62444..eb512afa644d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone; import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.RemoteAction; import android.content.Context; import android.content.pm.ParceledListSlice; @@ -43,10 +44,12 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Manages the PiP menu view which can show menu options or a scrim. @@ -114,6 +117,7 @@ public class PhonePipMenuController implements PipMenuController { private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; + private final Optional<SplitScreenController> mSplitScreenController; private ParceledListSlice<RemoteAction> mAppActions; private ParceledListSlice<RemoteAction> mMediaActions; private SyncRtSurfaceTransactionApplier mApplier; @@ -145,6 +149,7 @@ public class PhonePipMenuController implements PipMenuController { public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -152,6 +157,7 @@ public class PhonePipMenuController implements PipMenuController { mSystemWindows = systemWindows; mMainExecutor = mainExecutor; mMainHandler = mainHandler; + mSplitScreenController = splitScreenOptional; } public boolean isMenuVisible() { @@ -180,7 +186,8 @@ public class PhonePipMenuController implements PipMenuController { if (mPipMenuView != null) { detachPipMenuView(); } - mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler); + mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, + mSplitScreenController); mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); @@ -209,6 +216,13 @@ public class PhonePipMenuController implements PipMenuController { updateMenuLayout(destinationBounds); } + @Override + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + if (mPipMenuView != null) { + mPipMenuView.onFocusTaskChanged(taskInfo); + } + } + /** * Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some * reason (ie. the window isn't ready yet, thus {@link android.view.ViewRootImpl} is diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index b209699c1a19..82e827398bb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip.phone; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; @@ -32,8 +33,10 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.app.ActivityManager; import android.app.PendingIntent.CanceledException; import android.app.RemoteAction; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -61,11 +64,13 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Translucent window that gets started on top of a task in PIP to allow the user to control it. @@ -105,6 +110,7 @@ public class PipMenuView extends FrameLayout { private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; private int mDismissFadeOutDurationMs; + private boolean mFocusedTaskAllowSplitScreen; private final List<RemoteAction> mActions = new ArrayList<>(); @@ -116,6 +122,7 @@ public class PipMenuView extends FrameLayout { private AnimatorSet mMenuContainerAnimator; private PhonePipMenuController mController; + private Optional<SplitScreenController> mSplitScreenControllerOptional; private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -144,12 +151,14 @@ public class PipMenuView extends FrameLayout { protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; public PipMenuView(Context context, PhonePipMenuController controller, - ShellExecutor mainExecutor, Handler mainHandler) { + ShellExecutor mainExecutor, Handler mainHandler, + Optional<SplitScreenController> splitScreenController) { super(context, null, 0); mContext = context; mController = controller; mMainExecutor = mainExecutor; mMainHandler = mainHandler; + mSplitScreenControllerOptional = splitScreenController; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); inflate(context, R.layout.pip_menu, this); @@ -255,6 +264,15 @@ public class PipMenuView extends FrameLayout { return super.dispatchGenericMotionEvent(event); } + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent() + && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); + mFocusedTaskAllowSplitScreen = isSplitScreen + || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && taskInfo.supportsSplitScreenMultiWindow + && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME); + } + void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { mAllowMenuTimeout = allowMenuTimeout; @@ -278,7 +296,8 @@ public class PipMenuView extends FrameLayout { ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, - mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f); + mEnterSplitButton.getAlpha(), + ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, enterSplitAnim); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 3d3a63057dde..4a990975be0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -83,14 +83,15 @@ interface ISplitScreen { * Starts tasks simultaneously in one transition. */ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, - in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; + in Bundle sideOptions, int sidePosition, float splitRatio, + in RemoteTransition remoteTransition) = 10; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, - in RemoteAnimationAdapter adapter) = 11; + float splitRatio, in RemoteAnimationAdapter adapter) = 11; /** * Blocking call that notifies and gets additional split-screen targets when entering diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS new file mode 100644 index 000000000000..7237d2bde39f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-modules splitscreen owner +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 6b42ed775fb7..d2e341d3c38c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -183,6 +183,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.isSplitScreenVisible(); } + public boolean isTaskInSplitScreen(int taskId) { + return isSplitScreenVisible() + && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + } + + public @SplitPosition int getSplitPosition(int taskId) { + return mStageCoordinator.getSplitPosition(taskId); + } + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, new WindowContainerTransaction()); @@ -605,21 +614,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, - adapter)); + splitRatio, adapter)); } @Override public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, + @SplitPosition int sidePosition, float splitRatio, @Nullable RemoteTransition remoteTransition) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions, - sideTaskId, sideOptions, sidePosition, remoteTransition)); + sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index a3726d46d2a4..8ad0f200d448 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -274,6 +274,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mSideStageListener.mVisible && mMainStageListener.mVisible; } + @SplitScreen.StageType + int getStageOfTask(int taskId) { + if (mMainStage.containsTask(taskId)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsTask(taskId)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { StageTaskListener targetStage; @@ -322,7 +333,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, - @Nullable Bundle sideOptions, @SplitPosition int sidePosition, + @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, @Nullable RemoteTransition remoteTransition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); mainOptions = mainOptions != null ? mainOptions : new Bundle(); @@ -334,6 +345,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); + mSplitLayout.setDivideRatio(splitRatio); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); addActivityOptions(sideOptions, mSideStage); @@ -349,7 +361,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one legacy transition. */ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter) { final WindowContainerTransaction wct = new WindowContainerTransaction(); // Need to add another wrapper here in shell so that we can inject the divider bar // and also manage the process elevation via setRunningRemote @@ -404,6 +416,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, sideOptions = sideOptions != null ? sideOptions : new Bundle(); setSideStagePosition(sidePosition, wct); + mSplitLayout.setDivideRatio(splitRatio); // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); @@ -665,6 +678,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, outBottomOrRightBounds.set(mSplitLayout.getBounds2()); } + @SplitPosition + int getSplitPosition(int taskId) { + if (mSideStage.getTopVisibleChildTaskId() == taskId) { + return getSideStagePosition(); + } else if (mMainStage.getTopVisibleChildTaskId() == taskId) { + return getMainStagePosition(); + } + return SPLIT_POSITION_UNDEFINED; + } + private void addActivityOptions(Bundle opts, StageTaskListener stage) { opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); } @@ -768,6 +791,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); // Make the stages adjacent to each other so they occlude what's behind them. wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); + wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); mTaskOrganizer.applyTransaction(wct); } } @@ -775,6 +799,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onStageRootTaskVanished(StageListenerImpl stageListener) { if (stageListener == mMainStageListener || stageListener == mSideStageListener) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); // Deactivate the main stage if it no longer has a root task. mMainStage.deactivate(wct); mTaskOrganizer.applyTransaction(wct); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS new file mode 100644 index 000000000000..264e88f32bff --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-modules stagesplit owner +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java index 5f48c73cb2d6..014f02bcf8b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java @@ -72,6 +72,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { private final int mAppRevealDuration; private final int mAnimationDuration; private final float mIconStartAlpha; + private final float mBrandingStartAlpha; private final TransactionPool mTransactionPool; private ValueAnimator mMainAnimator; @@ -94,9 +95,17 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { || iconView.getLayoutParams().height == 0) { mIconFadeOutDuration = 0; mIconStartAlpha = 0; + mBrandingStartAlpha = 0; mAppRevealDelay = 0; } else { iconView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + // The branding view could only exists when the icon is present. + final View brandingView = view.getBrandingView(); + if (brandingView != null) { + mBrandingStartAlpha = brandingView.getAlpha(); + } else { + mBrandingStartAlpha = 0; + } mIconFadeOutDuration = context.getResources().getInteger( R.integer.starting_window_app_reveal_icon_fade_out_duration); mAppRevealDelay = context.getResources().getInteger( @@ -334,13 +343,21 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener { // ignore } - private void onAnimationProgress(float linearProgress) { - View iconView = mSplashScreenView.getIconView(); + private void onFadeOutProgress(float linearProgress) { + final float iconProgress = ICON_INTERPOLATOR.getInterpolation( + getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration)); + final View iconView = mSplashScreenView.getIconView(); + final View brandingView = mSplashScreenView.getBrandingView(); if (iconView != null) { - final float iconProgress = ICON_INTERPOLATOR.getInterpolation( - getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration)); iconView.setAlpha(mIconStartAlpha * (1 - iconProgress)); } + if (brandingView != null) { + brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress)); + } + } + + private void onAnimationProgress(float linearProgress) { + onFadeOutProgress(linearProgress); final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay, mAppRevealDuration); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index a9c81b3f3c1f..73f65b08a7eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -134,7 +134,8 @@ public class StartingSurfaceDrawer { mDisplayManager.getDisplay(DEFAULT_DISPLAY); } - private final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); + @VisibleForTesting + final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>(); /** * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is @@ -459,8 +460,23 @@ public class StartingSurfaceDrawer { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Task start finish, remove starting surface for task: %d", removalInfo.taskId); - removeWindowSynced(removalInfo); + removeWindowSynced(removalInfo, false /* immediately */); + } + /** + * Clear all starting windows immediately. + */ + public void clearAllWindows() { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "Clear all starting windows immediately"); + final int taskSize = mStartingWindowRecords.size(); + final int[] taskIds = new int[taskSize]; + for (int i = taskSize - 1; i >= 0; --i) { + taskIds[i] = mStartingWindowRecords.keyAt(i); + } + for (int i = taskSize - 1; i >= 0; --i) { + removeWindowNoAnimate(taskIds[i]); + } } /** @@ -542,7 +558,8 @@ public class StartingSurfaceDrawer { return shouldSaveView; } - private void saveSplashScreenRecord(IBinder appToken, int taskId, View view, + @VisibleForTesting + void saveSplashScreenRecord(IBinder appToken, int taskId, View view, @StartingWindowType int suggestType) { final StartingWindowRecord tView = new StartingWindowRecord(appToken, view, null/* TaskSnapshotWindow */, suggestType); @@ -551,19 +568,18 @@ public class StartingSurfaceDrawer { private void removeWindowNoAnimate(int taskId) { mTmpRemovalInfo.taskId = taskId; - removeWindowSynced(mTmpRemovalInfo); + removeWindowSynced(mTmpRemovalInfo, true /* immediately */); } void onImeDrawnOnTask(int taskId) { final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null && record.mTaskSnapshotWindow != null && record.mTaskSnapshotWindow.hasImeSurface()) { - record.mTaskSnapshotWindow.removeImmediately(); + removeWindowNoAnimate(taskId); } - mStartingWindowRecords.remove(taskId); } - protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) { + protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) { final int taskId = removalInfo.taskId; final StartingWindowRecord record = mStartingWindowRecords.get(taskId); if (record != null) { @@ -571,7 +587,8 @@ public class StartingSurfaceDrawer { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Removing splash screen window for task: %d", taskId); if (record.mContentView != null) { - if (record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { + if (immediately + || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) { removeWindowInner(record.mDecorView, false); } else { if (removalInfo.playRevealAnimation) { @@ -594,8 +611,12 @@ public class StartingSurfaceDrawer { if (record.mTaskSnapshotWindow != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "Removing task snapshot window for %d", taskId); - record.mTaskSnapshotWindow.scheduleRemove( - () -> mStartingWindowRecords.remove(taskId), removalInfo.deferRemoveForIme); + if (immediately) { + record.mTaskSnapshotWindow.removeImmediately(); + } else { + record.mTaskSnapshotWindow.scheduleRemove(() -> + mStartingWindowRecords.remove(taskId), removalInfo.deferRemoveForIme); + } } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index b62360ee45c5..487eb7055e40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -194,6 +194,18 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo } /** + * Clear all starting window immediately, called this method when releasing the task organizer. + */ + public void clearAllWindows() { + mSplashScreenExecutor.execute(() -> { + mStartingSurfaceDrawer.clearAllWindows(); + synchronized (mTaskBackgroundColors) { + mTaskBackgroundColors.clear(); + } + }); + } + + /** * The interface for calls from outside the Shell, within the host process. */ private class StartingSurfaceImpl implements StartingSurface { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 1d463d57e44c..e6c2f38ec3cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.apppairs +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -82,7 +83,7 @@ class AppPairsTestCannotPairNonResizeableApps( @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerRotatesScales() { // This test doesn't work in shell transitions because of b/206753786 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 10cf0b7c4773..1a3e42cbe04c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.apppairs +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -67,7 +68,7 @@ class AppPairsTestPairPrimaryAndSecondaryApps( @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerRotatesScales() { // This test doesn't work in shell transitions because of b/206753786 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt index 722ec34670cf..5c78b2998d60 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.apppairs +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -86,7 +87,7 @@ class AppPairsTestSupportPairNonResizeableApps( @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerRotatesScales() { // This test doesn't work in shell transitions because of b/206753786 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 38c008c14a78..251d92d4f008 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.flicker.apppairs import android.os.SystemClock +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -71,7 +72,7 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerRotatesScales() { // This test doesn't work in shell transitions because of b/206753786 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 13824b864ff1..d47057fb3bae 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.apppairs +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -87,7 +87,7 @@ class RotateTwoLaunchedAppsInAppPairsMode( testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @FlakyTest + @Postsubmit @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index c003084e3583..097867a95fb7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.apppairs +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -89,13 +89,13 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( } } - @FlakyTest(bugId = 172776659) + @Postsubmit @Test fun appPairsPrimaryBoundsIsVisibleAtEnd() = testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation, primaryApp.component) - @FlakyTest(bugId = 172776659) + @Postsubmit @Test fun appPairsSecondaryBoundsIsVisibleAtEnd() = testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt index 1605d8034796..a928bbdb288f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.bubble import android.content.Context import android.graphics.Point -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.util.DisplayMetrics import android.view.WindowManager import androidx.test.filters.RequiresDevice @@ -66,7 +66,7 @@ class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(tes } } - @Postsubmit + @Presubmit @Test fun testAppIsAlwaysVisible() { testSpec.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt index a8f17a75dbb3..64636be1195f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt @@ -16,7 +16,7 @@ package com.android.wm.shell.flicker.bubble -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -49,7 +49,7 @@ class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test } } - @Postsubmit + @Presubmit @Test fun testAppIsAlwaysVisible() { testSpec.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index 388515505462..ef7d65e8a732 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -74,7 +73,7 @@ class ExitLegacySplitScreenFromBottom( splitScreenApp.component, secondaryApp.component, FlickerComponentName.SNAPSHOT) - @Postsubmit + @FlakyTest @Test fun layerBecomesInvisible() { testSpec.assertLayers { @@ -94,11 +93,11 @@ class ExitLegacySplitScreenFromBottom( } } - @Postsubmit + @FlakyTest @Test fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - @Postsubmit + @FlakyTest @Test fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt index 079a6efdd31f..c1fba7d1530c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -124,7 +124,7 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( } } - @Postsubmit + @FlakyTest @Test fun nonResizableAppWindowBecomesVisible() { testSpec.assertWm { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index a787f2bdfc81..77fb101acc6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -122,11 +122,11 @@ class LegacySplitScreenToLauncher( @Test fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - @Postsubmit + @FlakyTest @Test fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible() - @Postsubmit + @FlakyTest @Test fun layerBecomesInvisible() { testSpec.assertLayers { @@ -136,7 +136,7 @@ class LegacySplitScreenToLauncher( } } - @Postsubmit + @FlakyTest @Test fun focusDoesNotChange() { testSpec.assertEventLog { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index 030e04051b08..34c0f849f874 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -25,8 +27,10 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -56,6 +60,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) { + @get:Rule + val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) /** * Defines the transition used to run the test @@ -76,6 +82,24 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit } } + @Postsubmit + @Test + fun runPresubmitAssertion() { + flickerRule.checkPresubmitAssertions() + } + + @Postsubmit + @Test + fun runPostsubmitAssertion() { + flickerRule.checkPostsubmitAssertions() + } + + @FlakyTest + @Test + fun runFlakyAssertion() { + flickerRule.checkFlakyAssertions() + } + /** {@inheritDoc} */ @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 2def9797fb62..1b7220eea60a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -25,9 +27,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -57,6 +61,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) { + @get:Rule + val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) + override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) @@ -65,6 +72,24 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran } } + @Postsubmit + @Test + fun runPresubmitAssertion() { + flickerRule.checkPresubmitAssertions() + } + + @Postsubmit + @Test + fun runPostsubmitAssertion() { + flickerRule.checkPostsubmitAssertions() + } + + @FlakyTest + @Test + fun runFlakyAssertion() { + flickerRule.checkFlakyAssertions() + } + @Before fun onBefore() { // This CUJ don't work in shell transitions because of b/204570898 b/204562589 diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 9191d0ef27cc..40be21af009c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -72,22 +72,6 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti } } - @Presubmit - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - @Presubmit - @Test - override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - - @Presubmit - @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - - @Presubmit - @Test - override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - @FlakyTest @Test override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() @@ -104,14 +88,6 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti testSpec.statusBarLayerRotatesScales() } - @Presubmit - @Test - override fun entireScreenCovered() = super.entireScreenCovered() - - @Presubmit - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 9c261051896a..a940a7f0fcc3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -82,19 +83,19 @@ class SetRequestedOrientationWhilePinnedTest( } } - @FlakyTest + @Postsubmit @Test override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - @FlakyTest + @Postsubmit @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() - @FlakyTest + @Postsubmit @Test override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() @@ -102,7 +103,7 @@ class SetRequestedOrientationWhilePinnedTest( @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - @FlakyTest + @Postsubmit @Test override fun statusBarLayerRotatesScales() { // This test doesn't work in shell transitions because of b/206753786 @@ -110,7 +111,7 @@ class SetRequestedOrientationWhilePinnedTest( super.statusBarLayerRotatesScales() } - @FlakyTest + @Postsubmit @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { @@ -118,7 +119,7 @@ class SetRequestedOrientationWhilePinnedTest( } } - @FlakyTest + @Postsubmit @Test fun pipAppShowsOnTop() { testSpec.assertWmEnd { @@ -126,7 +127,7 @@ class SetRequestedOrientationWhilePinnedTest( } } - @FlakyTest + @Postsubmit @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { @@ -134,13 +135,13 @@ class SetRequestedOrientationWhilePinnedTest( } } - @FlakyTest + @Postsubmit @Test fun pipAlwaysVisible() = testSpec.assertWm { this.isAppWindowVisible(pipApp.component) } - @FlakyTest + @Postsubmit @Test fun pipAppLayerCoversFullScreen() { testSpec.assertLayersEnd { @@ -148,7 +149,7 @@ class SetRequestedOrientationWhilePinnedTest( } } - @FlakyTest + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 73eebad040d8..453050fcfab4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -106,6 +106,12 @@ public class SplitLayoutTests extends ShellTestCase { } @Test + public void testSetDivideRatio() { + mSplitLayout.setDivideRatio(0.5f); + verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); + } + + @Test public void testOnDoubleTappedDivider() { mSplitLayout.onDoubleTappedDivider(); verify(mSplitLayoutHandler).onDoubleTappedDivider(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index bfa2c92f6679..9f745208d3ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -30,7 +30,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; +import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; import org.junit.Test; @@ -59,8 +61,8 @@ public class DragAndDropControllerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - - mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger); + mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger, + mock(IconProvider.class), mock(ShellExecutor.class)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 70b7c6793492..d92b12e60780 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -117,16 +118,19 @@ public class StartingSurfaceDrawerTests { WindowManager.LayoutParams params, int suggestType) { // listen for addView mAddWindowForTask = taskId; + saveSplashScreenRecord(appToken, taskId, view, suggestType); // Do not wait for background color return false; } @Override - protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo) { + protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, + boolean immediately) { // listen for removeView if (mAddWindowForTask == removalInfo.taskId) { mAddWindowForTask = 0; } + mStartingWindowRecords.remove(removalInfo.taskId); } } @@ -179,7 +183,7 @@ public class StartingSurfaceDrawerTests { removalInfo.taskId = windowInfo.taskInfo.taskId; mStartingSurfaceDrawer.removeStartingWindow(removalInfo); waitHandlerIdle(mTestHandler); - verify(mStartingSurfaceDrawer).removeWindowSynced(any()); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false)); assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0); } @@ -267,11 +271,32 @@ public class StartingSurfaceDrawerTests { // Verify the task snapshot with IME snapshot will be removed when received the real IME // drawn callback. + // makeTaskSnapshotWindow shall call removeWindowSynced before there add a new + // StartingWindowRecord for the task. mStartingSurfaceDrawer.onImeDrawnOnTask(1); - verify(mockSnapshotWindow).removeImmediately(); + verify(mStartingSurfaceDrawer, times(2)) + .removeWindowSynced(any(), eq(true)); } } + @Test + public void testClearAllWindows() { + final int taskId = 1; + final StartingWindowInfo windowInfo = + createWindowInfo(taskId, android.R.style.Theme); + mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder, + STARTING_WINDOW_TYPE_SPLASH_SCREEN); + waitHandlerIdle(mTestHandler); + verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(), + eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN)); + assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId); + + mStartingSurfaceDrawer.clearAllWindows(); + waitHandlerIdle(mTestHandler); + verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true)); + assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0); + } + private StartingWindowInfo createWindowInfo(int taskId, int themeResId) { StartingWindowInfo windowInfo = new StartingWindowInfo(); final ActivityInfo info = new ActivityInfo(); diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index d17c32817994..bbd4c814da81 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -641,22 +641,25 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // Retrieve all the resource ids belonging to this policy chunk - std::unordered_set<uint32_t> ids; const auto ids_begin = overlayable_child_chunk.data_ptr().convert<ResTable_ref>(); const auto ids_end = ids_begin + dtohl(policy_header->entry_count); + std::unordered_set<uint32_t> ids; + ids.reserve(ids_end - ids_begin); for (auto id_iter = ids_begin; id_iter != ids_end; ++id_iter) { if (!id_iter) { + LOG(ERROR) << "NULL ResTable_ref record??"; return {}; } ids.insert(dtohl(id_iter->ident)); } // Add the pairing of overlayable properties and resource ids to the package - OverlayableInfo overlayable_info{}; - overlayable_info.name = name; - overlayable_info.actor = actor; - overlayable_info.policy_flags = policy_header->policy_flags; - loaded_package->overlayable_infos_.emplace_back(overlayable_info, ids); + OverlayableInfo overlayable_info { + .name = name, + .actor = actor, + .policy_flags = policy_header->policy_flags + }; + loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids)); loaded_package->defines_overlayable_ = true; break; } @@ -683,15 +686,23 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, break; } - std::unordered_set<uint32_t> finalized_ids; const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>(); if (!lib_alias) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small."; + return {}; + } + if ((child_chunk.data_size() / sizeof(ResTable_staged_alias_entry)) + < dtohl(lib_alias->count)) { + LOG(ERROR) << "RES_TABLE_STAGED_ALIAS_TYPE is too small to hold entries."; return {}; } const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>(); const auto entry_end = entry_begin + dtohl(lib_alias->count); + std::unordered_set<uint32_t> finalized_ids; + finalized_ids.reserve(entry_end - entry_begin); for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) { if (!entry_iter) { + LOG(ERROR) << "NULL ResTable_staged_alias_entry record??"; return {}; } auto finalized_id = dtohl(entry_iter->finalizedResId); @@ -702,8 +713,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } auto staged_id = dtohl(entry_iter->stagedResId); - auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id, - finalized_id)); + auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id); if (!success) { LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.", staged_id); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 491f5f58035a..a2d0103450f5 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -107,6 +107,8 @@ cc_defaults { target: { android: { shared_libs: [ + "android.hardware.graphics.common-V3-ndk", + "android.hardware.graphics.common@1.2", "liblog", "libcutils", "libutils", @@ -126,9 +128,11 @@ cc_defaults { static_libs: [ "libEGL_blobCache", "libprotoutil", + "libshaders", "libstatslog_hwui", "libstatspull_lazy", "libstatssocket_lazy", + "libtonemap", ], }, host: { diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index 0b3b39397ee4..a5c0924579eb 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -20,9 +20,11 @@ // TODO: Use public SurfaceTexture APIs once available and include public NDK header file instead. #include <surfacetexture/surface_texture_platform.h> + #include "AutoBackendTextureRelease.h" #include "Matrix.h" #include "Properties.h" +#include "android/hdr_metadata.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" #include "renderthread/RenderThread.h" @@ -147,6 +149,9 @@ void DeferredLayerUpdater::apply() { mUpdateTexImage = false; float transformMatrix[16]; android_dataspace dataspace; + AHdrMetadataType hdrMetadataType; + android_cta861_3_metadata cta861_3; + android_smpte2086_metadata smpte2086; int slot; bool newContent = false; ARect currentCrop; @@ -155,8 +160,9 @@ void DeferredLayerUpdater::apply() { // is necessary if the SurfaceTexture queue is in synchronous mode, and we // cannot tell which mode it is in. AHardwareBuffer* hardwareBuffer = ASurfaceTexture_dequeueBuffer( - mSurfaceTexture.get(), &slot, &dataspace, transformMatrix, &outTransform, - &newContent, createReleaseFence, fenceWait, this, ¤tCrop); + mSurfaceTexture.get(), &slot, &dataspace, &hdrMetadataType, &cta861_3, + &smpte2086, transformMatrix, &outTransform, &newContent, createReleaseFence, + fenceWait, this, ¤tCrop); if (hardwareBuffer) { mCurrentSlot = slot; @@ -173,7 +179,18 @@ void DeferredLayerUpdater::apply() { SkRect currentCropRect = SkRect::MakeLTRB(currentCrop.left, currentCrop.top, currentCrop.right, currentCrop.bottom); - updateLayer(forceFilter, layerImage, outTransform, currentCropRect); + + float maxLuminanceNits = -1.f; + if (hdrMetadataType & HDR10_SMPTE2086) { + maxLuminanceNits = std::max(smpte2086.maxLuminance, maxLuminanceNits); + } + + if (hdrMetadataType & HDR10_CTA861_3) { + maxLuminanceNits = + std::max(cta861_3.maxContentLightLevel, maxLuminanceNits); + } + updateLayer(forceFilter, layerImage, outTransform, currentCropRect, + maxLuminanceNits); } } } @@ -186,13 +203,15 @@ void DeferredLayerUpdater::apply() { } void DeferredLayerUpdater::updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage, - const uint32_t transform, SkRect currentCrop) { + const uint32_t transform, SkRect currentCrop, + float maxLuminanceNits) { mLayer->setBlend(mBlend); mLayer->setForceFilter(forceFilter); mLayer->setSize(mWidth, mHeight); mLayer->setCurrentCropRect(currentCrop); mLayer->setWindowTransform(transform); mLayer->setImage(layerImage); + mLayer->setMaxLuminanceNits(maxLuminanceNits); } void DeferredLayerUpdater::detachSurfaceTexture() { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index da8041f0d0fe..9a4c5505fa35 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -91,7 +91,7 @@ public: void detachSurfaceTexture(); void updateLayer(bool forceFilter, const sk_sp<SkImage>& layerImage, const uint32_t transform, - SkRect currentCrop); + SkRect currentCrop, float maxLuminanceNits = -1.f); void destroyLayer(); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 0789344f970e..47eb5d3bfb83 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -96,6 +96,12 @@ public: inline sk_sp<SkImage> getImage() const { return this->layerImage; } + inline void setMaxLuminanceNits(float maxLuminanceNits) { + mMaxLuminanceNits = maxLuminanceNits; + } + + inline float getMaxLuminanceNits() { return mMaxLuminanceNits; } + void draw(SkCanvas* canvas); protected: @@ -158,6 +164,11 @@ private: */ bool mBlend = false; + /** + * Max input luminance if the layer is HDR + */ + float mMaxLuminanceNits = -1; + }; // struct Layer } // namespace uirenderer diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 14396569ccdd..553b08febdcc 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -15,12 +15,19 @@ */ #include "LayerDrawable.h" + +#include <shaders/shaders.h> +#include <utils/Color.h> #include <utils/MathUtils.h> +#include "DeviceInfo.h" #include "GrBackendSurface.h" #include "SkColorFilter.h" +#include "SkRuntimeEffect.h" #include "SkSurface.h" #include "gl/GrGLTypes.h" +#include "math/mat4.h" +#include "system/graphics-base-v1.0.h" #include "system/window.h" namespace android { @@ -69,6 +76,35 @@ static bool shouldFilterRect(const SkMatrix& matrix, const SkRect& srcRect, cons isIntegerAligned(dstDevRect.y())); } +static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, + const shaders::LinearEffect& linearEffect, + float maxDisplayLuminance, float maxLuminance) { + auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect)); + auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString)); + if (!runtimeEffect) { + LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str()); + } + + SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect)); + + effectBuilder.child("child") = std::move(shader); + + const auto uniforms = shaders::buildLinearEffectUniforms(linearEffect, mat4(), + maxDisplayLuminance, maxLuminance); + + for (const auto& uniform : uniforms) { + effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size()); + } + + return effectBuilder.makeShader(nullptr, false); +} + +static bool isHdrDataspace(ui::Dataspace dataspace) { + const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK; + + return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG; +} + // TODO: Context arg probably doesn't belong here – do debug check at callsite instead. bool LayerDrawable::DrawLayer(GrRecordingContext* context, SkCanvas* canvas, @@ -150,8 +186,30 @@ bool LayerDrawable::DrawLayer(GrRecordingContext* context, sampling = SkSamplingOptions(SkFilterMode::kLinear); } - canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint, - constraint); + const auto sourceDataspace = static_cast<ui::Dataspace>( + ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType())); + const SkImageInfo& imageInfo = canvas->imageInfo(); + const auto destinationDataspace = static_cast<ui::Dataspace>( + ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType())); + + if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) { + const auto effect = shaders::LinearEffect{ + .inputDataspace = sourceDataspace, + .outputDataspace = destinationDataspace, + .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType, + .fakeInputDataspace = destinationDataspace}; + auto shader = layerImage->makeShader(sampling, + SkMatrix::RectToRect(skiaSrcRect, skiaDestRect)); + constexpr float kMaxDisplayBrightess = 1000.f; + shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess, + layer->getMaxLuminanceNits()); + paint.setShader(shader); + canvas->drawRect(skiaDestRect, paint); + } else { + canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint, + constraint); + } + canvas->restore(); // restore the original matrix if (useLayerTransform) { diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 8f04cfb70469..f43586f8d9d0 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -17,24 +17,29 @@ #define LOG_TAG "PointerController" //#define LOG_NDEBUG 0 -// Log debug messages about pointer updates -#define DEBUG_POINTER_UPDATES 0 - #include "PointerController.h" -#include "MouseCursorController.h" #include "PointerControllerContext.h" -#include "TouchSpotController.h" - -#include <log/log.h> -#include <SkBitmap.h> #include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> -#include <SkPaint.h> namespace android { +namespace { + +const ui::Transform kIdentityTransform; + +} // namespace + +// --- PointerController::DisplayInfoListener --- + +void PointerController::DisplayInfoListener::onWindowInfosChanged( + const std::vector<android::gui::WindowInfo>&, + const std::vector<android::gui::DisplayInfo>& displayInfo) { + mPointerController.onDisplayInfosChanged(displayInfo); +} + // --- PointerController --- std::shared_ptr<PointerController> PointerController::create( @@ -63,9 +68,16 @@ std::shared_ptr<PointerController> PointerController::create( PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController) - : mContext(policy, looper, spriteController, *this), mCursorController(mContext) { + : mContext(policy, looper, spriteController, *this), + mCursorController(mContext), + mDisplayInfoListener(new DisplayInfoListener(*this)) { std::scoped_lock lock(mLock); mLocked.presentation = Presentation::SPOT; + SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener); +} + +PointerController::~PointerController() { + SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener); } bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, @@ -74,7 +86,14 @@ bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX } void PointerController::move(float deltaX, float deltaY) { - mCursorController.move(deltaX, deltaY); + const int32_t displayId = mCursorController.getDisplayId(); + vec2 transformed; + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); + } + mCursorController.move(transformed.x, transformed.y); } void PointerController::setButtonState(int32_t buttonState) { @@ -86,12 +105,26 @@ int32_t PointerController::getButtonState() const { } void PointerController::setPosition(float x, float y) { - std::scoped_lock lock(mLock); - mCursorController.setPosition(x, y); + const int32_t displayId = mCursorController.getDisplayId(); + vec2 transformed; + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + transformed = transform.transform(x, y); + } + mCursorController.setPosition(transformed.x, transformed.y); } void PointerController::getPosition(float* outX, float* outY) const { + const int32_t displayId = mCursorController.getDisplayId(); mCursorController.getPosition(outX, outY); + { + std::scoped_lock lock(mLock); + const auto& transform = getTransformForDisplayLocked(displayId); + const auto xy = transform.inverse().transform(*outX, *outY); + *outX = xy.x; + *outY = xy.y; + } } int32_t PointerController::getDisplayId() const { @@ -130,11 +163,25 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { std::scoped_lock lock(mLock); + std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; + const ui::Transform& transform = getTransformForDisplayLocked(displayId); + + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { + const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()]; + + const vec2 xy = transform.transform(spotCoords[index].getXYValue()); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y); + + float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + } + auto it = mLocked.spotControllers.find(displayId); if (it == mLocked.spotControllers.end()) { mLocked.spotControllers.try_emplace(displayId, displayId, mContext); } - mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits); + mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits); } void PointerController::clearSpots() { @@ -194,7 +241,7 @@ void PointerController::doInactivityTimeout() { void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) { std::unordered_set<int32_t> displayIdSet; - for (DisplayViewport viewport : viewports) { + for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); } @@ -214,4 +261,17 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& } } +void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) { + std::scoped_lock lock(mLock); + mLocked.mDisplayInfos = displayInfo; +} + +const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { + const auto& di = mLocked.mDisplayInfos; + auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { + return info.displayId == displayId; + }); + return it != di.end() ? it->transform : kIdentityTransform; +} + } // namespace android diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 97567bab202b..796077f6c38c 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -47,7 +47,7 @@ public: const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController); - virtual ~PointerController() = default; + ~PointerController() override; virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; virtual void move(float deltaX, float deltaY); @@ -72,6 +72,8 @@ public: void reloadPointerResources(); void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports); + void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos); + private: friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; @@ -85,9 +87,23 @@ private: struct Locked { Presentation presentation; + std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; } mLocked GUARDED_BY(mLock); + class DisplayInfoListener : public gui::WindowInfosListener { + public: + explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){}; + void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&, + const std::vector<android::gui::DisplayInfo>&) override; + + private: + PointerController& mPointerController; + }; + sp<DisplayInfoListener> mDisplayInfoListener; + + const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock); + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController); void clearSpotsLocked(); diff --git a/lint-baseline.xml b/lint-baseline.xml new file mode 100644 index 000000000000..79b21551a76e --- /dev/null +++ b/lint-baseline.xml @@ -0,0 +1,565 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev"> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final String component = Settings.Secure.getString(getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java" + line="60" + column="42"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(context.getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java" + line="188" + column="32"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(context.getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java" + line="194" + column="32"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(context.getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/AssistUtils.java" + line="216" + column="24"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" * volume, false otherwise." + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/media/java/android/media/AudioManager.java" + line="1028" + column="22"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" public final boolean isEnabled() {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="70" + column="38"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" public final String getRawLocale() {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="81" + column="40"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getFloat()` called from system process. Please call `android.provider.Settings.Secure#getFloatForUser()` instead. " + errorLine1=" public final float getFontScale() {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="111" + column="39"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" public int getRawUserStyle() {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="120" + column="34"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final int foregroundColor = Secure.getInt(" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="478" + column="24"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final int backgroundColor = Secure.getInt(" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="480" + column="24"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final int edgeType = Secure.getInt(" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="482" + column="17"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final int edgeColor = Secure.getInt(" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="484" + column="18"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final int windowColor = Secure.getInt(" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="486" + column="20"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/accessibility/CaptioningManager.java" + line="489" + column="17"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" String nearbyComponent = Settings.Secure.getString(" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/ChooserActivity.java" + line="1156" + column="26"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" final ContentResolver cr = context.getContentResolver();" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java" + line="587" + column="40"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Secure.getInt(cr, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0) == 1" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/display/ColorDisplayManager.java" + line="588" + column="68"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" int inversionEnabled = Settings.Secure.getInt(contentResolver," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/security/ConfirmationPrompt.java" + line="220" + column="40"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getFloat()` called from system process. Please call `android.provider.Settings.System#getFloatForUser()` instead. " + errorLine1=" float fontScale = Settings.System.getFloat(contentResolver," + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/core/java/android/security/ConfirmationPrompt.java" + line="225" + column="35"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" int a11yEnabled = Settings.Secure.getInt(contentResolver," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/security/ConfirmationPrompt.java" + line="237" + column="39"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" Secure.getInt(resolver, Secure.RELEASE_COMPRESS_BLOCKS_ON_INSTALL, 1) != 0;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/content/F2fsUtils.java" + line="96" + column="16"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" int speed = DEFAULT_POINTER_SPEED;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/input/InputManager.java" + line="865" + column="22"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final ContentResolver contentResolver = fallbackContext.getContentResolver();" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java" + line="2860" + column="60"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" if (mShowImeWithHardKeyboard == ShowImeWithHardKeyboardType.UNKNOWN) {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java" + line="1205" + column="79"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" if (showImeWithHardKeyboardUri.equals(uri)) {" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/inputmethodservice/InputMethodService.java" + line="1225" + column="54"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" RemoteViews.MARGIN_BOTTOM, 0);" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/app/Notification.java" + line="5619" + column="29"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(context.getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/hardware/biometrics/ParentalControlsUtilsInternal.java" + line="40" + column="20"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(mContext.getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java" + line="328" + column="32"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java" + line="3129" + column="29"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java" + line="3131" + column="22"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getString()` called from system process. Please call `android.provider.Settings.System#getStringForUser()` instead. " + errorLine1=" final String touchDataJson = Settings.System.getString(" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/app/PlatLogoActivity.java" + line="184" + column="58"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) == 1;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java" + line="463" + column="8"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java" + line="97" + column="23"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" final String setting = getDefaultRingtoneSetting(type);" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/media/java/android/media/RingtoneManager.java" + line="1105" + column="45"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final String targetString = Settings.Secure.getString(context.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java" + line="55" + column="45"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final String targetsValue = Settings.Secure.getString(context.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java" + line="82" + column="45"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final String targetString = Settings.Secure.getString(context.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/util/ShortcutUtils.java" + line="112" + column="45"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" String serviceComponent = Settings.Secure.getString(mContext.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/SpeechRecognizer.java" + line="665" + column="3"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" boolean cap = System.getInt(resolver, System.TEXT_AUTO_CAPS, 1) > 0;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/text/method/TextKeyListener.java" + line="296" + column="30"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" boolean text = System.getInt(resolver, System.TEXT_AUTO_REPLACE, 1) > 0;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/text/method/TextKeyListener.java" + line="297" + column="31"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" boolean period = System.getInt(resolver, System.TEXT_AUTO_PUNCTUATE, 1) > 0;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/text/method/TextKeyListener.java" + line="298" + column="33"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.System#getInt()` called from system process. Please call `android.provider.Settings.System#getIntForUser()` instead. " + errorLine1=" boolean pw = System.getInt(resolver, System.TEXT_SHOW_PASSWORD, 1) > 0;" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/text/method/TextKeyListener.java" + line="299" + column="29"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(getContentResolver(), name, defaultValue);" + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/tts/TextToSpeechService.java" + line="422" + column="24"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" return Settings.Secure.getInt(getContext().getContentResolver()," + errorLine2=" ~~~~~~"> + <location + file="frameworks/base/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java" + line="63" + column="24"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" String engine = getString(mContext.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/tts/TtsEngines.java" + line="116" + column="17"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE));" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/tts/TtsEngines.java" + line="337" + column="9"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" getString(mContext.getContentResolver(), Settings.Secure.TTS_DEFAULT_LOCALE)," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/tts/TtsEngines.java" + line="373" + column="13"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" final String prefList = Settings.Secure.getString(mContext.getContentResolver()," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/speech/tts/TtsEngines.java" + line="527" + column="41"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getInt()` called from system process. Please call `android.provider.Settings.Secure#getIntForUser()` instead. " + errorLine1=" }" + errorLine2=" ^"> + <location + file="frameworks/base/core/java/android/service/autofill/UserData.java" + line="535" + column="10"/> + </issue> + + <issue + id="NonUserGetterCalled" + message="`android.provider.Settings.Secure#getString()` called from system process. Please call `android.provider.Settings.Secure#getStringForUser()` instead. " + errorLine1=" public static boolean isActiveService(Context context, ComponentName service) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/core/java/android/service/voice/VoiceInteractionService.java" + line="156" + column="74"/> + </issue> + +</issues> diff --git a/media/Android.bp b/media/Android.bp index 15b24b220768..fcdfd72c91d5 100644 --- a/media/Android.bp +++ b/media/Android.bp @@ -115,6 +115,7 @@ aidl_interface { aidl_interface { name: "android.media.soundtrigger.types", vendor_available: true, + host_supported: true, flags: [ "-Werror", "-Weverything", diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 2916b0156055..337b45c4c531 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -62,6 +62,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -1021,6 +1022,29 @@ public class AudioManager { } /** + * Returns the current user setting for ramping ringer on incoming phone call ringtone. + * + * @return true if the incoming phone call ringtone is configured to gradually increase its + * volume, false otherwise. + */ + public boolean isRampingRingerEnabled() { + return Settings.System.getInt(getContext().getContentResolver(), + Settings.System.APPLY_RAMPING_RINGER, 0) != 0; + } + + /** + * Sets the flag for enabling ramping ringer on incoming phone call ringtone. + * + * @see #isRampingRingerEnabled() + * @hide + */ + @TestApi + public void setRampingRingerEnabled(boolean enabled) { + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.APPLY_RAMPING_RINGER, enabled ? 1 : 0); + } + + /** * Checks valid ringer mode values. * * @return true if the ringer mode indicated is valid, false otherwise. diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index bac44adbe101..e979a1b57ae2 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -163,6 +163,14 @@ public abstract class Image implements AutoCloseable { * {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}. * </td> * </tr> + * <tr> + * <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td> + * <td>1</td> + * <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane + * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. + * </td> + * </tr> * </table> * * @see android.graphics.ImageFormat diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 4daedfc6072d..939b679676aa 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -5106,7 +5106,6 @@ final public class MediaCodec { public MediaImage( @NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly, long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) { - mFormat = ImageFormat.YUV_420_888; mTimestamp = timestamp; mIsImageValid = true; mIsReadOnly = buffer.isReadOnly(); @@ -5119,6 +5118,11 @@ final public class MediaCodec { mBufferContext = 0; + int cbPlaneOffset = -1; + int crPlaneOffset = -1; + int planeOffsetInc = -1; + int pixelStride = -1; + // read media-info. See MediaImage2 if (info.remaining() == 104) { int type = info.getInt(); @@ -5136,14 +5140,27 @@ final public class MediaCodec { "unsupported size: " + mWidth + "x" + mHeight); } int bitDepth = info.getInt(); - if (bitDepth != 8) { + if (bitDepth != 8 && bitDepth != 10) { throw new UnsupportedOperationException("unsupported bit depth: " + bitDepth); } int bitDepthAllocated = info.getInt(); - if (bitDepthAllocated != 8) { + if (bitDepthAllocated != 8 && bitDepthAllocated != 16) { throw new UnsupportedOperationException( "unsupported allocated bit depth: " + bitDepthAllocated); } + if (bitDepth == 8 && bitDepthAllocated == 8) { + mFormat = ImageFormat.YUV_420_888; + planeOffsetInc = 1; + pixelStride = 2; + } else if (bitDepth == 10 && bitDepthAllocated == 16) { + mFormat = ImageFormat.YCBCR_P010; + planeOffsetInc = 2; + pixelStride = 4; + } else { + throw new UnsupportedOperationException("couldn't infer ImageFormat" + + " bitDepth: " + bitDepth + " bitDepthAllocated: " + bitDepthAllocated); + } + mPlanes = new MediaPlane[numPlanes]; for (int ix = 0; ix < numPlanes; ix++) { int planeOffset = info.getInt(); @@ -5165,12 +5182,31 @@ final public class MediaCodec { buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8) + (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc); mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc); + if ((mFormat == ImageFormat.YUV_420_888 || mFormat == ImageFormat.YCBCR_P010) + && ix == 1) { + cbPlaneOffset = planeOffset; + } else if ((mFormat == ImageFormat.YUV_420_888 + || mFormat == ImageFormat.YCBCR_P010) && ix == 2) { + crPlaneOffset = planeOffset; + } } } else { throw new UnsupportedOperationException( "unsupported info length: " + info.remaining()); } + // Validate chroma semiplanerness. + if (mFormat == ImageFormat.YCBCR_P010) { + if (crPlaneOffset != cbPlaneOffset + planeOffsetInc) { + throw new UnsupportedOperationException("Invalid plane offsets" + + " cbPlaneOffset: " + cbPlaneOffset + " crPlaneOffset: " + crPlaneOffset); + } + if (mPlanes[1].getPixelStride() != pixelStride + || mPlanes[2].getPixelStride() != pixelStride) { + throw new UnsupportedOperationException("Invalid pixelStride"); + } + } + if (cropRect == null) { cropRect = new Rect(0, 0, mWidth, mHeight); } diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java index 6077f0478674..4d8eda1a100b 100644 --- a/media/java/android/media/tv/BroadcastInfoRequest.java +++ b/media/java/android/media/tv/BroadcastInfoRequest.java @@ -22,12 +22,21 @@ import android.os.Parcelable; import android.annotation.NonNull; /** @hide */ -public final class BroadcastInfoRequest implements Parcelable { +public abstract class BroadcastInfoRequest implements Parcelable { + protected static final int PARCEL_TOKEN_TS_REQUEST = 1; + public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR = new Parcelable.Creator<BroadcastInfoRequest>() { @Override public BroadcastInfoRequest createFromParcel(Parcel source) { - return new BroadcastInfoRequest(source); + int token = source.readInt(); + switch (token) { + case PARCEL_TOKEN_TS_REQUEST: + return TsRequest.createFromParcelBody(source); + default: + throw new IllegalStateException( + "Unexpected broadcast info request type token in parcel."); + } } @Override @@ -42,10 +51,14 @@ public final class BroadcastInfoRequest implements Parcelable { this.requestId = requestId; } - private BroadcastInfoRequest(Parcel source) { + protected BroadcastInfoRequest(Parcel source) { requestId = source.readInt(); } + public int getRequestId() { + return requestId; + } + @Override public int describeContents() { return 0; diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java index 64c884ec4b7f..fe4e8b7f1d0a 100644 --- a/media/java/android/media/tv/BroadcastInfoResponse.java +++ b/media/java/android/media/tv/BroadcastInfoResponse.java @@ -46,6 +46,10 @@ public final class BroadcastInfoResponse implements Parcelable { requestId = source.readInt(); } + public int getRequestId() { + return requestId; + } + @Override public int describeContents() { return 0; diff --git a/media/java/android/media/tv/TsRequest.aidl b/media/java/android/media/tv/TsRequest.aidl new file mode 100644 index 000000000000..0abc7ecec769 --- /dev/null +++ b/media/java/android/media/tv/TsRequest.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +parcelable TsRequest; diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java new file mode 100644 index 000000000000..3690d05e9b95 --- /dev/null +++ b/media/java/android/media/tv/TsRequest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class TsRequest extends BroadcastInfoRequest implements Parcelable { + public static final @NonNull Parcelable.Creator<TsRequest> CREATOR = + new Parcelable.Creator<TsRequest>() { + @Override + public TsRequest createFromParcel(Parcel source) { + source.readInt(); + return createFromParcelBody(source); + } + + @Override + public TsRequest[] newArray(int size) { + return new TsRequest[size]; + } + }; + + int tsPid; + + public static TsRequest createFromParcelBody(Parcel in) { + return new TsRequest(in); + } + + public TsRequest(int requestId, int tsPid) { + super(requestId); + this.tsPid = tsPid; + } + + protected TsRequest(Parcel source) { + super(source); + tsPid = source.readInt(); + } + + public int getTsPid() { + return tsPid; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(PARCEL_TOKEN_TS_REQUEST); + super.writeToParcel(dest, flags); + dest.writeInt(tsPid); + } +} diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 0461f0a6f61c..bafb03bc53eb 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -657,13 +657,6 @@ public final class TvInputManager { */ void onError(Session session, @TvInputManager.RecordingError int error) { } - - /** - * @param session - * @param response - */ - public void onBroadcastInfoResponse(Session session, BroadcastInfoResponse response) { - } } private static final class SessionCallbackRecord { @@ -848,7 +841,7 @@ public final class TvInputManager { mHandler.post(new Runnable() { @Override public void run() { - mSessionCallback.onBroadcastInfoResponse(mSession, response); + mSession.getIAppSession().notifyBroadcastInfoResponse(response); } }); } diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl index 0dd64b83df5f..39c438a09311 100644 --- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl @@ -15,6 +15,9 @@ */ package android.media.tv.interactive; +import android.media.tv.BroadcastInfoRequest; + +import android.view.InputChannel; /** * Interface a client of the ITvIAppManager implements, to identify itself and receive information @@ -22,7 +25,8 @@ package android.media.tv.interactive; * @hide */ oneway interface ITvIAppClient { - void onSessionCreated(in String iAppServiceId, IBinder token, int seq); + void onSessionCreated(in String iAppServiceId, IBinder token, in InputChannel channel, int seq); void onSessionReleased(int seq); void onLayoutSurface(int left, int top, int right, int bottom, int seq); + void onBroadcastInfoRequest(in BroadcastInfoRequest request, int seq); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl index 104efe6d1881..25e1acea226d 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl @@ -19,6 +19,7 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManagerCallback; import android.media.tv.interactive.TvIAppInfo; +import android.media.tv.BroadcastInfoResponse; import android.view.Surface; /** @@ -34,6 +35,8 @@ interface ITvIAppManager { void setSurface(in IBinder sessionToken, in Surface surface, int userId); void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height, int userId); + void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response, + int UserId); void registerCallback(in ITvIAppManagerCallback callback, int userId); void unregisterCallback(in ITvIAppManagerCallback callback, int userId); diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl index 2f165f0de7e5..1dee9cc4ed28 100644 --- a/media/java/android/media/tv/interactive/ITvIAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl @@ -18,6 +18,7 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppServiceCallback; import android.media.tv.interactive.ITvIAppSessionCallback; +import android.view.InputChannel; /** * Top-level interface to a TV IApp component (implemented in a Service). It's used for @@ -27,5 +28,6 @@ import android.media.tv.interactive.ITvIAppSessionCallback; oneway interface ITvIAppService { void registerCallback(in ITvIAppServiceCallback callback); void unregisterCallback(in ITvIAppServiceCallback callback); - void createSession(in ITvIAppSessionCallback callback, in String iAppServiceId, int type); + void createSession(in InputChannel channel, in ITvIAppSessionCallback callback, + in String iAppServiceId, int type); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppSession.aidl b/media/java/android/media/tv/interactive/ITvIAppSession.aidl index 0afa9716a783..440b3d30c068 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSession.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppSession.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.view.Surface; +import android.media.tv.BroadcastInfoResponse; /** * Sub-interface of ITvIAppService.aidl which is created per session and has its own context. @@ -27,4 +28,5 @@ oneway interface ITvIAppSession { void release(); void setSurface(in Surface surface); void dispatchSurfaceChanged(int format, int width, int height); + void notifyBroadcastInfoResponse(in BroadcastInfoResponse response); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl index 0873aad8f5c6..d308463cda4d 100644 --- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl +++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl @@ -17,6 +17,7 @@ package android.media.tv.interactive; import android.media.tv.interactive.ITvIAppSession; +import android.media.tv.BroadcastInfoRequest; /** * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when @@ -26,4 +27,5 @@ import android.media.tv.interactive.ITvIAppSession; oneway interface ITvIAppSessionCallback { void onSessionCreated(in ITvIAppSession session); void onLayoutSurface(int left, int top, int right, int bottom); + void onBroadcastInfoRequest(in BroadcastInfoRequest request); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java index 093e1be6ca5e..ae35edc7ef8a 100644 --- a/media/java/android/media/tv/interactive/TvIAppManager.java +++ b/media/java/android/media/tv/interactive/TvIAppManager.java @@ -20,12 +20,20 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; +import android.media.tv.BroadcastInfoRequest; +import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvInputManager; 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 android.util.Pools; import android.util.SparseArray; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventSender; import android.view.Surface; import com.android.internal.util.Preconditions; @@ -67,8 +75,8 @@ public final class TvIAppManager { mUserId = userId; mClient = new ITvIAppClient.Stub() { @Override - public void onSessionCreated(String iAppServiceId, IBinder token, int seq) { - // TODO: use InputChannel for input events + public void onSessionCreated(String iAppServiceId, IBinder token, InputChannel channel, + int seq) { synchronized (mSessionCallbackRecordMap) { SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); if (record == null) { @@ -77,7 +85,7 @@ public final class TvIAppManager { } Session session = null; if (token != null) { - session = new Session(token, mService, mUserId, seq, + session = new Session(token, channel, mService, mUserId, seq, mSessionCallbackRecordMap); } else { mSessionCallbackRecordMap.delete(seq); @@ -111,6 +119,18 @@ public final class TvIAppManager { record.postLayoutSurface(left, top, right, bottom); } } + + @Override + public void onBroadcastInfoRequest(BroadcastInfoRequest request, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postBroadcastInfoRequest(request); + } + } }; ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() { // TODO: handle IApp service state changes @@ -351,18 +371,33 @@ public final class TvIAppManager { * @hide */ public static final class Session { + static final int DISPATCH_IN_PROGRESS = -1; + static final int DISPATCH_NOT_HANDLED = 0; + static final int DISPATCH_HANDLED = 1; + + private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500; + private final ITvIAppManager mService; private final int mUserId; private final int mSeq; private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap; - private IBinder mToken; + // For scheduling input event handling on the main thread. This also serves as a lock to + // protect pending input events and the input channel. + private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper()); private TvInputManager.Session mInputSession; + private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20); + private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); + + private IBinder mToken; + private TvInputEventSender mSender; + private InputChannel mInputChannel; - private Session(IBinder token, ITvIAppManager service, int userId, int seq, - SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { + private Session(IBinder token, InputChannel channel, ITvIAppManager service, int userId, + int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) { mToken = token; + mInputChannel = channel; mService = service; mUserId = userId; mSeq = seq; @@ -428,6 +463,60 @@ public final class TvIAppManager { } /** + * Dispatches an input event to this session. + * + * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}. + * @param token A token used to identify the input event later in the callback. + * @param callback A callback used to receive the dispatch result. Cannot be {@code null}. + * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be + * {@code null}. + * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns + * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns + * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will + * be invoked later. + * @hide + */ + public int dispatchInputEvent(@NonNull InputEvent event, Object token, + @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(handler); + synchronized (mHandler) { + if (mInputChannel == null) { + return DISPATCH_NOT_HANDLED; + } + PendingEvent p = obtainPendingEventLocked(event, token, callback, handler); + if (Looper.myLooper() == Looper.getMainLooper()) { + // Already running on the main thread so we can send the event immediately. + return sendInputEventOnMainLooperLocked(p); + } + + // Post the event to the main thread. + Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p); + msg.setAsynchronous(true); + mHandler.sendMessage(msg); + return DISPATCH_IN_PROGRESS; + } + } + + /** + * Notifies of any broadcast info response passed in from TIS. + * + * @param response response passed in from TIS. + */ + public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.notifyBroadcastInfoResponse(mToken, response, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Releases this session. */ public void release() { @@ -444,12 +533,208 @@ public final class TvIAppManager { releaseInternal(); } + private void flushPendingEventsLocked() { + mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT); + + final int count = mPendingEvents.size(); + for (int i = 0; i < count; i++) { + int seq = mPendingEvents.keyAt(i); + Message msg = mHandler.obtainMessage( + InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + } + private void releaseInternal() { mToken = null; + synchronized (mHandler) { + if (mInputChannel != null) { + if (mSender != null) { + flushPendingEventsLocked(); + mSender.dispose(); + mSender = null; + } + mInputChannel.dispose(); + mInputChannel = null; + } + } synchronized (mSessionCallbackRecordMap) { mSessionCallbackRecordMap.delete(mSeq); } } + + private PendingEvent obtainPendingEventLocked(InputEvent event, Object token, + FinishedInputEventCallback callback, Handler handler) { + PendingEvent p = mPendingEventPool.acquire(); + if (p == null) { + p = new PendingEvent(); + } + p.mEvent = event; + p.mEventToken = token; + p.mCallback = callback; + p.mEventHandler = handler; + return p; + } + + // Assumes the event has already been removed from the queue. + void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) { + p.mHandled = handled; + if (p.mEventHandler.getLooper().isCurrentThread()) { + // Already running on the callback handler thread so we can send the callback + // immediately. + p.run(); + } else { + // Post the event to the callback handler thread. + // In this case, the callback will be responsible for recycling the event. + Message msg = Message.obtain(p.mEventHandler, p); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + } + + // Must be called on the main looper + private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) { + synchronized (mHandler) { + int result = sendInputEventOnMainLooperLocked(p); + if (result == DISPATCH_IN_PROGRESS) { + return; + } + } + + invokeFinishedInputEventCallback(p, false); + } + + private int sendInputEventOnMainLooperLocked(PendingEvent p) { + if (mInputChannel != null) { + if (mSender == null) { + mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper()); + } + + final InputEvent event = p.mEvent; + final int seq = event.getSequenceNumber(); + if (mSender.sendInputEvent(seq, event)) { + mPendingEvents.put(seq, p); + Message msg = mHandler.obtainMessage( + InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT); + return DISPATCH_IN_PROGRESS; + } + + Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:" + + event); + } + return DISPATCH_NOT_HANDLED; + } + + void finishedInputEvent(int seq, boolean handled, boolean timeout) { + final PendingEvent p; + synchronized (mHandler) { + int index = mPendingEvents.indexOfKey(seq); + if (index < 0) { + return; // spurious, event already finished or timed out + } + + p = mPendingEvents.valueAt(index); + mPendingEvents.removeAt(index); + + if (timeout) { + Log.w(TAG, "Timeout waiting for session to handle input event after " + + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken); + } else { + mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p); + } + } + + invokeFinishedInputEventCallback(p, handled); + } + + private void recyclePendingEventLocked(PendingEvent p) { + p.recycle(); + mPendingEventPool.release(p); + } + + /** + * Callback that is invoked when an input event that was dispatched to this session has been + * finished. + * + * @hide + */ + public interface FinishedInputEventCallback { + /** + * Called when the dispatched input event is finished. + * + * @param token A token passed to {@link #dispatchInputEvent}. + * @param handled {@code true} if the dispatched input event was handled properly. + * {@code false} otherwise. + */ + void onFinishedInputEvent(Object token, boolean handled); + } + + private final class InputEventHandler extends Handler { + public static final int MSG_SEND_INPUT_EVENT = 1; + public static final int MSG_TIMEOUT_INPUT_EVENT = 2; + public static final int MSG_FLUSH_INPUT_EVENT = 3; + + InputEventHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SEND_INPUT_EVENT: { + sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj); + return; + } + case MSG_TIMEOUT_INPUT_EVENT: { + finishedInputEvent(msg.arg1, false, true); + return; + } + case MSG_FLUSH_INPUT_EVENT: { + finishedInputEvent(msg.arg1, false, false); + return; + } + } + } + } + + private final class TvInputEventSender extends InputEventSender { + TvInputEventSender(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEventFinished(int seq, boolean handled) { + finishedInputEvent(seq, handled, false); + } + } + + private final class PendingEvent implements Runnable { + public InputEvent mEvent; + public Object mEventToken; + public FinishedInputEventCallback mCallback; + public Handler mEventHandler; + public boolean mHandled; + + public void recycle() { + mEvent = null; + mEventToken = null; + mCallback = null; + mEventHandler = null; + mHandled = false; + } + + @Override + public void run() { + mCallback.onFinishedInputEvent(mEventToken, mHandled); + + synchronized (mEventHandler) { + recyclePendingEventLocked(this); + } + } + } } private static final class SessionCallbackRecord { @@ -490,6 +775,15 @@ public final class TvIAppManager { } }); } + + void postBroadcastInfoRequest(final BroadcastInfoRequest request) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSession.getInputSession().requestBroadcastInfo(request); + } + }); + } } /** diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java index 78b8173e0af7..fe087ca564d0 100644 --- a/media/java/android/media/tv/interactive/TvIAppService.java +++ b/media/java/android/media/tv/interactive/TvIAppService.java @@ -23,13 +23,21 @@ import android.annotation.SuppressLint; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.media.tv.BroadcastInfoRequest; +import android.media.tv.BroadcastInfoResponse; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.Surface; import com.android.internal.os.SomeArgs; @@ -85,14 +93,16 @@ public abstract class TvIAppService extends Service { } @Override - public void createSession(ITvIAppSessionCallback cb, String iAppServiceId, int type) { + public void createSession(InputChannel channel, ITvIAppSessionCallback cb, + String iAppServiceId, int type) { if (cb == null) { return; } SomeArgs args = SomeArgs.obtain(); - args.arg1 = cb; - args.arg2 = iAppServiceId; - args.arg3 = type; + args.arg1 = channel; + args.arg2 = cb; + args.arg3 = iAppServiceId; + args.arg4 = type; mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args) .sendToTarget(); } @@ -122,6 +132,8 @@ public abstract class TvIAppService extends Service { * @hide */ public abstract static class Session implements KeyEvent.Callback { + private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); + private final Object mLock = new Object(); // @GuardedBy("mLock") private ITvIAppSessionCallback mSessionCallback; @@ -175,6 +187,14 @@ public abstract class TvIAppService extends Service { } /** + * Called when a broadcast info response is received from TIS. + * + * @param response response received from TIS. + */ + public void onNotifyBroadcastInfoResponse(BroadcastInfoResponse response) { + } + + /** * Releases TvIAppService session. * @hide */ @@ -182,6 +202,60 @@ public abstract class TvIAppService extends Service { } /** + * TODO: JavaDoc of APIs related to input events. + * @hide + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return false; + } + + /** + * @hide + */ + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + /** + * @hide + */ + @Override + public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { + return false; + } + + /** + * @hide + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + /** + * @hide + */ + public boolean onTouchEvent(MotionEvent event) { + return false; + } + + /** + * @hide + */ + public boolean onTrackballEvent(MotionEvent event) { + return false; + } + + /** + * @hide + */ + public boolean onGenericMotionEvent(MotionEvent event) { + return false; + } + + /** * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position * is relative to the overlay view that sits on top of this surface. * @@ -214,6 +288,26 @@ public abstract class TvIAppService extends Service { }); } + public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) { + Log.d(TAG, "requestBroadcastInfo (requestId=" + + request.getRequestId() + ")"); + } + if (mSessionCallback != null) { + mSessionCallback.onBroadcastInfoRequest(request); + } + } catch (RemoteException e) { + Log.w(TAG, "error in requestBroadcastInfo", e); + } + } + }); + } + void startIApp() { onStartIApp(); } @@ -226,6 +320,39 @@ public abstract class TvIAppService extends Service { } } + /** + * Takes care of dispatching incoming input events and tells whether the event was handled. + */ + int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { + if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); + if (event instanceof KeyEvent) { + KeyEvent keyEvent = (KeyEvent) event; + if (keyEvent.dispatch(this, mDispatcherState, this)) { + return TvIAppManager.Session.DISPATCH_HANDLED; + } + + // TODO: special handlings of navigation keys and media keys + } else if (event instanceof MotionEvent) { + MotionEvent motionEvent = (MotionEvent) event; + final int source = motionEvent.getSource(); + if (motionEvent.isTouchEvent()) { + if (onTouchEvent(motionEvent)) { + return TvIAppManager.Session.DISPATCH_HANDLED; + } + } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + if (onTrackballEvent(motionEvent)) { + return TvIAppManager.Session.DISPATCH_HANDLED; + } + } else { + if (onGenericMotionEvent(motionEvent)) { + return TvIAppManager.Session.DISPATCH_HANDLED; + } + } + } + // TODO: handle overlay view + return TvIAppManager.Session.DISPATCH_NOT_HANDLED; + } + private void initialize(ITvIAppSessionCallback callback) { synchronized (mLock) { mSessionCallback = callback; @@ -259,6 +386,18 @@ public abstract class TvIAppService extends Service { onSurfaceChanged(format, width, height); } + /** + * + * Calls {@link #notifyBroadcastInfoResponse}. + */ + void notifyBroadcastInfoResponse(BroadcastInfoResponse response) { + if (DEBUG) { + Log.d(TAG, "notifyBroadcastInfoResponse (requestId=" + + response.getRequestId() + ")"); + } + onNotifyBroadcastInfoResponse(response); + } + private void executeOrPostRunnableOnMainThread(Runnable action) { synchronized (mLock) { if (mSessionCallback == null) { @@ -281,10 +420,17 @@ public abstract class TvIAppService extends Service { * @hide */ public static class ITvIAppSessionWrapper extends ITvIAppSession.Stub { + // TODO: put ITvIAppSessionWrapper in a separate Java file private final Session mSessionImpl; + private InputChannel mChannel; + private TvIAppEventReceiver mReceiver; - public ITvIAppSessionWrapper(Session mSessionImpl) { + public ITvIAppSessionWrapper(Context context, Session mSessionImpl, InputChannel channel) { this.mSessionImpl = mSessionImpl; + mChannel = channel; + if (channel != null) { + mReceiver = new TvIAppEventReceiver(channel, context.getMainLooper()); + } } @Override @@ -306,6 +452,31 @@ public abstract class TvIAppService extends Service { public void dispatchSurfaceChanged(int format, int width, int height) { mSessionImpl.dispatchSurfaceChanged(format, width, height); } + + @Override + public void notifyBroadcastInfoResponse(BroadcastInfoResponse response) { + mSessionImpl.notifyBroadcastInfoResponse(response); + } + + private final class TvIAppEventReceiver extends InputEventReceiver { + TvIAppEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { + if (mSessionImpl == null) { + // The session has been finished. + finishInputEvent(event, false); + return; + } + + int handled = mSessionImpl.dispatchInputEvent(event, this); + if (handled != TvIAppManager.Session.DISPATCH_IN_PROGRESS) { + finishInputEvent(event, handled == TvIAppManager.Session.DISPATCH_HANDLED); + } + } + } } @SuppressLint("HandlerLeak") @@ -318,9 +489,10 @@ public abstract class TvIAppService extends Service { switch (msg.what) { case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs) msg.obj; - ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg1; - String iAppServiceId = (String) args.arg2; - int type = (int) args.arg3; + InputChannel channel = (InputChannel) args.arg1; + ITvIAppSessionCallback cb = (ITvIAppSessionCallback) args.arg2; + String iAppServiceId = (String) args.arg3; + int type = (int) args.arg4; args.recycle(); Session sessionImpl = onCreateSession(iAppServiceId, type); if (sessionImpl == null) { @@ -332,7 +504,8 @@ public abstract class TvIAppService extends Service { } return; } - ITvIAppSession stub = new ITvIAppSessionWrapper(sessionImpl); + ITvIAppSession stub = new ITvIAppSessionWrapper( + TvIAppService.this, sessionImpl, channel); SomeArgs someArgs = SomeArgs.obtain(); someArgs.arg1 = sessionImpl; diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java index 8bcf3d268c3f..ed0475436504 100644 --- a/media/java/android/media/tv/tuner/filter/AvSettings.java +++ b/media/java/android/media/tv/tuner/filter/AvSettings.java @@ -186,9 +186,10 @@ public class AvSettings extends Settings { private final boolean mIsPassthrough; private int mAudioStreamType = AUDIO_STREAM_TYPE_UNDEFINED; private int mVideoStreamType = VIDEO_STREAM_TYPE_UNDEFINED; + private final boolean mUseSecureMemory; - private AvSettings(int mainType, boolean isAudio, boolean isPassthrough, - int audioStreamType, int videoStreamType) { + private AvSettings(int mainType, boolean isAudio, boolean isPassthrough, int audioStreamType, + int videoStreamType, boolean useSecureMemory) { super(TunerUtils.getFilterSubtype( mainType, isAudio @@ -197,6 +198,7 @@ public class AvSettings extends Settings { mIsPassthrough = isPassthrough; mAudioStreamType = audioStreamType; mVideoStreamType = videoStreamType; + mUseSecureMemory = useSecureMemory; } /** @@ -223,6 +225,16 @@ public class AvSettings extends Settings { } /** + * Checks whether secure memory is used. + * + * <p>This query is only supported by Tuner HAL 2.0 or higher. The return value on HAL 1.1 and + * lower is undefined. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. + */ + public boolean useSecureMemory() { + return mUseSecureMemory; + } + + /** * Creates a builder for {@link AvSettings}. * * @param mainType the filter main type. @@ -239,9 +251,10 @@ public class AvSettings extends Settings { public static class Builder { private final int mMainType; private final boolean mIsAudio; - private boolean mIsPassthrough; + private boolean mIsPassthrough = false; private int mAudioStreamType = AUDIO_STREAM_TYPE_UNDEFINED; private int mVideoStreamType = VIDEO_STREAM_TYPE_UNDEFINED; + boolean mUseSecureMemory = false; private Builder(int mainType, boolean isAudio) { mMainType = mainType; @@ -250,6 +263,8 @@ public class AvSettings extends Settings { /** * Sets whether it's passthrough. + * + * <p>Default value is {@code false}. */ @NonNull public Builder setPassthrough(boolean isPassthrough) { @@ -263,6 +278,8 @@ public class AvSettings extends Settings { * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. * + * <p>Default is {@link #AUDIO_STREAM_TYPE_UNDEFINED}. + * * @param audioStreamType the audio stream type to set. */ @NonNull @@ -281,6 +298,8 @@ public class AvSettings extends Settings { * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. * + * <p>Default value is {@link #VIDEO_STREAM_TYPE_UNDEFINED}. + * * @param videoStreamType the video stream type to set. */ @NonNull @@ -294,12 +313,29 @@ public class AvSettings extends Settings { } /** + * Sets whether secure memory should be used. + * + * <p>This API is only supported by Tuner HAL 2.0 or higher. Unsupported version would cause + * no-op. Use {@link TunerVersionChecker#getTunerVersion()} to check the version. + * + * <p>Default value is {@code false}. + */ + @NonNull + public Builder setUseSecureMemory(boolean useSecureMemory) { + if (TunerVersionChecker.checkHigherOrEqualVersionTo( + TunerVersionChecker.TUNER_VERSION_2_0, "setSecureMemory")) { + mUseSecureMemory = useSecureMemory; + } + return this; + } + + /** * Builds a {@link AvSettings} object. */ @NonNull public AvSettings build() { - return new AvSettings(mMainType, mIsAudio, mIsPassthrough, - mAudioStreamType, mVideoStreamType); + return new AvSettings(mMainType, mIsAudio, mIsPassthrough, mAudioStreamType, + mVideoStreamType, mUseSecureMemory); } } } diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java index ae2711200afd..f0f576e90d91 100644 --- a/media/java/android/media/tv/tuner/filter/Filter.java +++ b/media/java/android/media/tv/tuner/filter/Filter.java @@ -251,8 +251,8 @@ public class Filter implements AutoCloseable { private native int nativeFlushFilter(); private native int nativeRead(byte[] buffer, long offset, long size); private native int nativeClose(); - private native String nativeCreateSharedFilter(); - private native void nativeReleaseSharedFilter(String token); + private native String nativeAcquireSharedFilterToken(); + private native void nativeFreeSharedFilterToken(String token); // Called by JNI private Filter(long id) { @@ -562,20 +562,20 @@ public class Filter implements AutoCloseable { } /** - * Creates a shared filter. + * Acquires a shared filter token. * * @return a string shared filter token. */ @Nullable - public String createSharedFilter() { + public String acquireSharedFilterToken() { synchronized (mLock) { TunerUtils.checkResourceState(TAG, mIsClosed); if (mIsStarted || mIsShared) { - Log.d(TAG, "Create shared filter in a wrong state, started: " + + Log.d(TAG, "Acquire shared filter in a wrong state, started: " + mIsStarted + "shared: " + mIsShared); return null; } - String token = nativeCreateSharedFilter(); + String token = nativeAcquireSharedFilterToken(); if (token != null) { mIsShared = true; } @@ -584,17 +584,17 @@ public class Filter implements AutoCloseable { } /** - * Releases a shared filter. + * Frees a shared filter token. * * @param filterToken the token of the shared filter being released. */ - public void releaseSharedFilter(@NonNull String filterToken) { + public void freeSharedFilterToken(@NonNull String filterToken) { synchronized (mLock) { TunerUtils.checkResourceState(TAG, mIsClosed); if (!mIsShared) { return; } - nativeReleaseSharedFilter(filterToken); + nativeFreeSharedFilterToken(filterToken); mIsShared = false; } } diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java index cd703651f2fa..d34581da29cb 100644 --- a/media/java/android/media/tv/tuner/filter/RecordSettings.java +++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java @@ -20,11 +20,11 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.hardware.tv.tuner.DemuxRecordScIndexType; +import android.hardware.tv.tuner.DemuxScAvcIndex; import android.hardware.tv.tuner.DemuxScHevcIndex; import android.hardware.tv.tuner.DemuxScIndex; import android.hardware.tv.tuner.DemuxTsIndex; import android.media.tv.tuner.TunerUtils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -139,7 +139,7 @@ public class RecordSettings extends Settings { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "INDEX_TYPE_", value = - {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC}) + {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC, INDEX_TYPE_SC_AVC}) public @interface ScIndexType {} /** @@ -154,6 +154,10 @@ public class RecordSettings extends Settings { * Start Code index for HEVC. */ public static final int INDEX_TYPE_SC_HEVC = DemuxRecordScIndexType.SC_HEVC; + /** + * Start Code index for AVC. + */ + public static final int INDEX_TYPE_SC_AVC = DemuxRecordScIndexType.SC_AVC; /** * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream) @@ -187,23 +191,23 @@ public class RecordSettings extends Settings { /** * All blocks are coded as I blocks. */ - public static final int SC_INDEX_I_SLICE = DemuxScIndex.I_SLICE; + public static final int SC_INDEX_I_SLICE = DemuxScAvcIndex.I_SLICE << 4; /** * Blocks are coded as I or P blocks. */ - public static final int SC_INDEX_P_SLICE = DemuxScIndex.P_SLICE; + public static final int SC_INDEX_P_SLICE = DemuxScAvcIndex.P_SLICE << 4; /** * Blocks are coded as I, P or B blocks. */ - public static final int SC_INDEX_B_SLICE = DemuxScIndex.B_SLICE; + public static final int SC_INDEX_B_SLICE = DemuxScAvcIndex.B_SLICE << 4; /** * A so-called switching I slice that is coded. */ - public static final int SC_INDEX_SI_SLICE = DemuxScIndex.SI_SLICE; + public static final int SC_INDEX_SI_SLICE = DemuxScAvcIndex.SI_SLICE << 4; /** * A so-called switching P slice that is coded. */ - public static final int SC_INDEX_SP_SLICE = DemuxScIndex.SP_SLICE; + public static final int SC_INDEX_SP_SLICE = DemuxScAvcIndex.SP_SLICE << 4; /** * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2. diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java index fc6451fd96bc..ff8f796455c6 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java @@ -18,6 +18,7 @@ package android.media.tv.tuner.filter; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.hardware.tv.tuner.Constant; /** * Table information for Section Filter. @@ -26,6 +27,12 @@ import android.annotation.SystemApi; */ @SystemApi public class SectionSettingsWithTableInfo extends SectionSettings { + /** + * The invalid version number of {@link SectionSettingsWithTableInfo}. Tuner HAL will ignore the + * {@link SectionSettingsWithTableInfo} version number if this invalid version is set. + */ + public static final int INVALID_TABLE_INFO_VERSION = Constant.INVALID_TABINFO_VERSION; + private final int mTableId; private final int mVersion; @@ -64,7 +71,7 @@ public class SectionSettingsWithTableInfo extends SectionSettings { */ public static class Builder extends SectionSettings.Builder<Builder> { private int mTableId; - private int mVersion; + private int mVersion = INVALID_TABLE_INFO_VERSION; private Builder(int mainType) { super(mainType); diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index c4dfee42c3cd..4c1ed45c5f3d 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -693,6 +693,10 @@ void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int siz sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scIndex>(); } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scHevc) { sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scHevc>(); + } else if (tsRecordEvent.scIndexMask.getTag() == DemuxFilterScIndexMask::Tag::scAvc) { + sc = tsRecordEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scAvc>(); + // Java uses the values defined by HIDL HAL. Left shift 4 bits. + sc = sc << 4; } jint ts = tsRecordEvent.tsIndexMask; @@ -3391,8 +3395,11 @@ static DemuxFilterAvSettings getFilterAvSettings(JNIEnv *env, const jobject& set jclass clazz = env->FindClass("android/media/tv/tuner/filter/AvSettings"); bool isPassthrough = env->GetBooleanField(settings, env->GetFieldID(clazz, "mIsPassthrough", "Z")); - DemuxFilterAvSettings filterAvSettings { - .isPassthrough = isPassthrough, + bool isSecureMemory = + env->GetBooleanField(settings, env->GetFieldID(clazz, "mUseSecureMemory", "Z")); + DemuxFilterAvSettings filterAvSettings{ + .isPassthrough = isPassthrough, + .isSecureMemory = isSecureMemory, }; return filterAvSettings; } @@ -3440,6 +3447,11 @@ static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobj env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexType", "I"))); jint scIndexMask = env->GetIntField(settings, env->GetFieldID(clazz, "mScIndexMask", "I")); + // Backward compatibility for S- apps. + if (scIndexType == DemuxRecordScIndexType::SC && + scIndexMask > static_cast<int32_t>(DemuxScIndex::SEQUENCE)) { + scIndexType = DemuxRecordScIndexType::SC_AVC; + } DemuxFilterRecordSettings filterRecordSettings { .tsIndexMask = tsIndexMask, .scIndexType = scIndexType, @@ -3448,6 +3460,9 @@ static DemuxFilterRecordSettings getFilterRecordSettings(JNIEnv *env, const jobj filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scIndex>(scIndexMask); } else if (scIndexType == DemuxRecordScIndexType::SC_HEVC) { filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scHevc>(scIndexMask); + } else if (scIndexType == DemuxRecordScIndexType::SC_AVC) { + // Java uses the values defined by HIDL HAL. Right shift 4 bits. + filterRecordSettings.scIndexMask.set<DemuxFilterScIndexMask::Tag::scAvc>(scIndexMask >> 4); } return filterRecordSettings; } @@ -3890,22 +3905,22 @@ static jint android_media_tv_Tuner_close_filter(JNIEnv *env, jobject filter) { return (jint)r; } -static jstring android_media_tv_Tuner_create_shared_filter(JNIEnv *env, jobject filter) { +static jstring android_media_tv_Tuner_acquire_shared_filter_token(JNIEnv *env, jobject filter) { sp<FilterClient> filterClient = getFilterClient(env, filter); if (filterClient == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", - "Failed to create shared filter: filter client not found"); + "Failed to acquire shared filter token: filter client not found"); return nullptr; } - string token = filterClient->createSharedFilter(); + string token = filterClient->acquireSharedFilterToken(); if (token.empty()) { return nullptr; } return env->NewStringUTF(token.data()); } -static void android_media_tv_Tuner_release_shared_filter( +static void android_media_tv_Tuner_free_shared_filter_token( JNIEnv *env, jobject filter, jstring token) { sp<FilterClient> filterClient = getFilterClient(env, filter); if (filterClient == nullptr) { @@ -3915,7 +3930,7 @@ static void android_media_tv_Tuner_release_shared_filter( } std::string filterToken(env->GetStringUTFChars(token, nullptr)); - filterClient->releaseSharedFilter(filterToken); + filterClient->freeSharedFilterToken(filterToken); } static sp<TimeFilterClient> getTimeFilterClient(JNIEnv *env, jobject filter) { @@ -4408,10 +4423,10 @@ static const JNINativeMethod gFilterMethods[] = { { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter }, { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter }, - {"nativeCreateSharedFilter", "()Ljava/lang/String;", - (void *)android_media_tv_Tuner_create_shared_filter}, - {"nativeReleaseSharedFilter", "(Ljava/lang/String;)V", - (void *)android_media_tv_Tuner_release_shared_filter}, + {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;", + (void *)android_media_tv_Tuner_acquire_shared_filter_token}, + {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V", + (void *)android_media_tv_Tuner_free_shared_filter_token}, }; static const JNINativeMethod gSharedFilterMethods[] = { diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index e8b3de82f284..8568383385ac 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -196,10 +196,10 @@ Result FilterClient::close() { return Result::INVALID_STATE; } -string FilterClient::createSharedFilter() { +string FilterClient::acquireSharedFilterToken() { if (mTunerFilter != nullptr) { string filterToken; - if (mTunerFilter->createSharedFilter(&filterToken).isOk()) { + if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) { return filterToken; } } @@ -207,9 +207,9 @@ string FilterClient::createSharedFilter() { return ""; } -Result FilterClient::releaseSharedFilter(const string& filterToken) { +Result FilterClient::freeSharedFilterToken(const string& filterToken) { if (mTunerFilter != nullptr) { - Status s = mTunerFilter->releaseSharedFilter(filterToken); + Status s = mTunerFilter->freeSharedFilterToken(filterToken); return ClientHelper::getServiceSpecificErrorCode(s); } @@ -346,4 +346,13 @@ void FilterClient::closeAvSharedMemory() { mAvSharedMemSize = 0; mAvSharedHandle = nullptr; } + +Result FilterClient::setDelayHint(const FilterDelayHint& hint) { + if (mTunerFilter) { + Status s = mTunerFilter->setDelayHint(hint); + return ClientHelper::getServiceSpecificErrorCode(s); + } + return Result::INVALID_STATE; +} + } // namespace android diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h index c031b2ac0b5b..20e56102a909 100644 --- a/media/jni/tuner/FilterClient.h +++ b/media/jni/tuner/FilterClient.h @@ -33,6 +33,7 @@ using ::aidl::android::hardware::tv::tuner::DemuxFilterEvent; using ::aidl::android::hardware::tv::tuner::DemuxFilterSettings; using ::aidl::android::hardware::tv::tuner::DemuxFilterStatus; using ::aidl::android::hardware::tv::tuner::DemuxFilterType; +using ::aidl::android::hardware::tv::tuner::FilterDelayHint; using ::aidl::android::media::tv::tuner::BnTunerFilterCallback; using ::aidl::android::media::tv::tuner::ITunerFilter; using ::android::hardware::EventFlag; @@ -143,14 +144,19 @@ public: Result close(); /** - * Create a new SharedFiler. + * Accquire a new SharedFiler token. */ - string createSharedFilter(); + string acquireSharedFilterToken(); /** - * Release SharedFiler. + * Release SharedFiler token. */ - Result releaseSharedFilter(const string& filterToken); + Result freeSharedFilterToken(const string& filterToken); + + /** + * Set a filter delay hint. + */ + Result setDelayHint(const FilterDelayHint& hint); private: Result getFilterMq(); diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp new file mode 100644 index 000000000000..2b81200f0079 --- /dev/null +++ b/omapi/aidl/Android.bp @@ -0,0 +1,35 @@ +// Copyright 2020, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.se.omapi", + vendor_available: true, + srcs: ["android/se/omapi/*.aidl"], + stability: "vintf", + backend: { + java: { + sdk_version: "module_current", + }, + rust: { + enabled: true, + }, + ndk: { + separate_platform_variant: false, + }, + }, +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl new file mode 100644 index 000000000000..725013a35cde --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementChannel { + void close(); + boolean isClosed(); + boolean isBasicChannel(); + byte[] getSelectResponse(); + byte[] transmit(in byte[] command); + boolean selectNext(); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl new file mode 100644 index 000000000000..77e1c53f47ac --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl @@ -0,0 +1,40 @@ +/* + * 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. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementListener { +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl new file mode 100644 index 000000000000..2b10c473c902 --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl @@ -0,0 +1,44 @@ +/* + * 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. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementReader { + boolean isSecureElementPresent(); + android.se.omapi.ISecureElementSession openSession(); + void closeSessions(); + boolean reset(); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl new file mode 100644 index 000000000000..0c8e431d18a1 --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl @@ -0,0 +1,45 @@ +/* + * 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. + *//* + * Copyright (c) 2015-2017, The Linux Foundation. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementService { + String[] getReaders(); + android.se.omapi.ISecureElementReader getReader(in String reader); + boolean[] isNfcEventAllowed(in String reader, in byte[] aid, in String[] packageNames, in int userId); +} diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl new file mode 100644 index 000000000000..06287c551f5c --- /dev/null +++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl @@ -0,0 +1,48 @@ +/* + * 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. + *//* + * Copyright (c) 2015-2017, The Linux Foundation. + *//* + * Contributed by: Giesecke & Devrient GmbH. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m <name>-update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.se.omapi; +/* @hide */ +@VintfStability +interface ISecureElementSession { + byte[] getAtr(); + void close(); + void closeChannels(); + boolean isClosed(); + android.se.omapi.ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener); + android.se.omapi.ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener); +} diff --git a/core/java/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl index 4ae57ab829cb..bbd3c148caaf 100644 --- a/core/java/android/se/omapi/ISecureElementChannel.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl @@ -22,6 +22,7 @@ package android.se.omapi; import android.se.omapi.ISecureElementSession; /** @hide */ +@VintfStability interface ISecureElementChannel { /** @@ -58,6 +59,9 @@ interface ISecureElementChannel { * Transmits the specified command APDU and returns the response APDU. * MANAGE channel commands are not supported. * Selection of applets is not supported in logical channels. + * + * @param command Command APDU, its structure is defined in ISO/IEC 7816-4 + * in Standard byte format */ byte[] transmit(in byte[] command); diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl index e9dd18181c56..479dcd7d5acf 100644 --- a/core/java/android/se/omapi/ISecureElementListener.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl @@ -23,5 +23,6 @@ package android.se.omapi; * Interface to receive call-backs when the service is connected. * @hide */ +@VintfStability interface ISecureElementListener { } diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl index 41244ab058e0..a6979face61f 100644 --- a/core/java/android/se/omapi/ISecureElementReader.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl @@ -22,6 +22,7 @@ package android.se.omapi; import android.se.omapi.ISecureElementSession; /** @hide */ +@VintfStability interface ISecureElementReader { /** @@ -34,7 +35,7 @@ interface ISecureElementReader { * Connects to a secure element in this reader. <br> * This method prepares (initialises) the Secure Element for communication * before the Session object is returned (e.g. powers the Secure Element by - * ICC ON if its not already on). There might be multiple sessions opened at + * ICC ON if it is not already on). There might be multiple sessions opened at * the same time on the same reader. The system ensures the interleaving of * APDUs between the respective sessions. * diff --git a/core/java/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl index 4fa799e78757..13707ec22b5c 100644 --- a/core/java/android/se/omapi/ISecureElementService.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl @@ -28,23 +28,31 @@ import android.se.omapi.ISecureElementReader; * SecureElement service interface. * @hide */ +@VintfStability interface ISecureElementService { /** * Returns the friendly names of available Secure Element readers. + * <ul> + * <li>If the reader is a SIM reader, then its name must be "SIM[Slot]".</li> + * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li> + * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li> + * </ul> + * Slot is a decimal number without leading zeros. The Numbering must start with 1 + * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...). */ String[] getReaders(); /** * Returns SecureElement Service reader object to the given name. */ - ISecureElementReader getReader(String reader); + ISecureElementReader getReader(in String reader); /** * Checks if the application defined by the package name is allowed to * receive NFC transaction events for the defined AID. */ - boolean[] isNFCEventAllowed(String reader, in byte[] aid, - in String[] packageNames); + boolean[] isNfcEventAllowed(in String reader, in byte[] aid, + in String[] packageNames, in int userId); } diff --git a/core/java/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl index 8ea599f2e866..129ecc4ddaa3 100644 --- a/core/java/android/se/omapi/ISecureElementSession.aidl +++ b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl @@ -27,6 +27,7 @@ import android.se.omapi.ISecureElementReader; import android.se.omapi.ISecureElementListener; /** @hide */ +@VintfStability interface ISecureElementSession { /** @@ -45,7 +46,6 @@ interface ISecureElementSession { */ void closeChannels(); - /** * Tells if this session is closed. * @@ -59,15 +59,19 @@ interface ISecureElementSession { * applet if aid != null. * Logical channels cannot be opened with this connection. * Use interface method openLogicalChannel() to open a logical channel. + * Listener is passed to secure element service and used to monitor whether + * the client application that uses OMAPI is still alive or not. */ ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, - ISecureElementListener listener); + in ISecureElementListener listener); /** * Opens a connection using the next free logical channel of the card in the * specified reader. Selects the specified applet. * Selection of other applets with this connection is not supported. + * Listener is passed to secure element service and used to monitor whether + * the client application that uses OMAPI is still alive or not. */ ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, - ISecureElementListener listener); + in ISecureElementListener listener); } diff --git a/omapi/aidl/vts/functional/AccessControlApp/Android.bp b/omapi/aidl/vts/functional/AccessControlApp/Android.bp new file mode 100644 index 000000000000..f03c3f6eb647 --- /dev/null +++ b/omapi/aidl/vts/functional/AccessControlApp/Android.bp @@ -0,0 +1,54 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "VtsHalOmapiSeAccessControlTestCases", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalOmapiSeAccessControlTestCases.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libhidlbase", + "libnativehelper", + "libutils", + "libbinder_ndk", + ], + static_libs: [ + "VtsHalHidlTargetTestBase", + "android.se.omapi-V1-ndk", + ], + cflags: [ + "-O0", + "-g", + "-Wall", + "-Werror", + ], + require_root: true, + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp new file mode 100644 index 000000000000..9ea65431417a --- /dev/null +++ b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <aidl/android/se/omapi/BnSecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementChannel.h> +#include <aidl/android/se/omapi/ISecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementReader.h> +#include <aidl/android/se/omapi/ISecureElementService.h> +#include <aidl/android/se/omapi/ISecureElementSession.h> + +#include <VtsCoreUtil.h> +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <cutils/properties.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> +#include <utils/String16.h> + +using namespace std; +using namespace ::testing; +using namespace android; + +int main(int argc, char** argv) { + InitGoogleTest(&argc, argv); + int status = RUN_ALL_TESTS(); + return status; +} + +namespace { + +class OMAPISEAccessControlTest : public TestWithParam<std::string> { + protected: + + class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + + /** + * Verifies TLV data + * + * @return true if the data is tlv formatted, false otherwise + */ + bool verifyBerTlvData(std::vector<uint8_t> tlv) { + if (tlv.size() == 0) { + LOG(ERROR) << "Invalid tlv, null"; + return false; + } + int i = 0; + if ((tlv[i++] & 0x1F) == 0x1F) { + // extra byte for TAG field + i++; + } + + int len = tlv[i++] & 0xFF; + if (len > 127) { + // more than 1 byte for length + int bytesLength = len - 128; + len = 0; + for (int j = bytesLength; j > 0; j--) { + len += (len << 8) + (tlv[i++] & 0xFF); + } + } + // Additional 2 bytes for the SW + return (tlv.size() == (i + len + 2)); + } + + void testSelectableAid( + std::vector<std::vector<uint8_t>> authorizedAids) { + for (auto aid : authorizedAids) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + } + } + } + } + + void testUnauthorisedAid( + std::vector<std::vector<uint8_t>> unAuthorizedAids) { + for (auto aid : unAuthorizedAids) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (!res.isOk()) { + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + } + } + } + } + + void testTransmitAPDU( + std::vector<uint8_t> aid, + std::vector<std::vector<uint8_t>> apdus) { + for (auto apdu : apdus) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + bool status = false; + std::vector<uint8_t> selectResponse = {}; + std::vector<uint8_t> transmitResponse = {}; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + + res = channel->transmit(apdu, &transmitResponse); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + ASSERT_TRUE(res.isOk()) << "failed to transmit"; + } + } + } + } + + void testUnauthorisedAPDU( + std::vector<uint8_t> aid, + std::vector<std::vector<uint8_t>> apdus) { + for (auto apdu : apdus) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + ASSERT_NE(reader, nullptr) << "reader is null"; + bool status = false; + std::vector<uint8_t> selectResponse = {}; + std::vector<uint8_t> transmitResponse = {}; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + ASSERT_TRUE( + verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + + res = channel->transmit(apdu, &transmitResponse); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + if (!res.isOk()) { + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + } + } + } + } + + bool supportOMAPIReaders() { + return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())); + } + + void getFirstApiLevel(int32_t* outApiLevel) { + int32_t firstApiLevel = property_get_int32(FEATURE_SE_API_LEVEL.c_str(), -1); + if (firstApiLevel < 0) { + firstApiLevel = property_get_int32(FEATURE_SE_SDK_VERSION.c_str(), -1); + } + ASSERT_GT(firstApiLevel, 0); // first_api_level must exist + *outApiLevel = firstApiLevel; + return; + } + + bool supportsHardware() { + bool lowRamDevice = property_get_bool(FEATURE_SE_LOW_RAM.c_str(), true); + return !lowRamDevice || deviceSupportsFeature(FEATURE_SE_HARDWARE_WATCH.c_str()) || + deviceSupportsFeature(FEATURE_SE_OMAPI_SERVICE.c_str()); // android.se.omapi + } + + void SetUp() override { + ASSERT_TRUE(supportsHardware()); + int32_t apiLevel; + getFirstApiLevel(&apiLevel); + ASSERT_TRUE(apiLevel > 27); + ASSERT_TRUE(supportOMAPIReaders()); + LOG(INFO) << "get OMAPI service with name:" << GetParam(); + ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str())); + mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + ASSERT_TRUE(mOmapiSeService); + + std::vector<std::string> readers = {}; + + if (mOmapiSeService != NULL) { + auto status = mOmapiSeService->getReaders(&readers); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + for (auto readerName : readers) { + // Filter eSE readers only + if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = mOmapiSeService->getReader(readerName, &reader); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + mVSReaders[readerName] = reader; + } + } + } + } + + void TearDown() override { + if (mOmapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + } + } + } + + static inline std::string const ESE_READER_PREFIX = "eSE"; + static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese"; + static inline std::string const FEATURE_SE_LOW_RAM = "ro.config.low_ram"; + static inline std::string const FEATURE_SE_HARDWARE_WATCH = "android.hardware.type.watch"; + static inline std::string const FEATURE_SE_OMAPI_SERVICE = "com.android.se"; + static inline std::string const FEATURE_SE_SDK_VERSION = "ro.build.version.sdk"; + static inline std::string const FEATURE_SE_API_LEVEL = "ro.product.first_api_level"; + + std::vector<uint8_t> AID_40 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x40}; + std::vector<uint8_t> AID_41 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x41}; + std::vector<uint8_t> AID_42 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x42}; + std::vector<uint8_t> AID_43 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x43}; + std::vector<uint8_t> AID_44 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x44}; + std::vector<uint8_t> AID_45 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x45}; + std::vector<uint8_t> AID_46 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x46}; + std::vector<uint8_t> AID_47 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x47}; + std::vector<uint8_t> AID_48 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x48}; + std::vector<uint8_t> AID_49 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x49}; + std::vector<uint8_t> AID_4A = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4A}; + std::vector<uint8_t> AID_4B = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4B}; + std::vector<uint8_t> AID_4C = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4C}; + std::vector<uint8_t> AID_4D = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4D}; + std::vector<uint8_t> AID_4E = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4E}; + std::vector<uint8_t> AID_4F = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4F}; + + std::vector<std::vector<uint8_t>> AUTHORIZED_AID = {AID_40, AID_41, AID_42, AID_44, AID_45, + AID_47, AID_48, AID_49, AID_4A, AID_4B, + AID_4C, AID_4D, AID_4E, AID_4F}; + std::vector<std::vector<uint8_t>> UNAUTHORIZED_AID = {AID_43, AID_46}; + + /* Authorized APDU for AID_40 */ + std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_40 = { + {0x00, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + }; + /* Unauthorized APDU for AID_40 */ + std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_40 = { + {0x00, 0x08, 0x00, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x06, 0x00, 0x00, 0x00}, + }; + + /* Authorized APDU for AID_41 */ + std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_41 = { + {0x94, 0x06, 0x00, 0x00}, + {0x94, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}}; + /* Unauthorized APDU for AID_41 */ + std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_41 = { + {0x00, 0x06, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + {0x00, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x08, 0x00, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + }; + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService; + + std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> + mVSReaders = {}; +}; + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAID) { + testSelectableAid(AUTHORIZED_AID); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorizedAID) { + testUnauthorisedAid(UNAUTHORIZED_AID); +} + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID40) { + testTransmitAPDU(AID_40, AUTHORIZED_APDU_AID_40); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID40) { + testUnauthorisedAPDU(AID_40, UNAUTHORIZED_APDU_AID_40); +} + +TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID41) { + testTransmitAPDU(AID_41, AUTHORIZED_APDU_AID_41); +} + +TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID41) { + testUnauthorisedAPDU(AID_41, UNAUTHORIZED_APDU_AID_41); +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEAccessControlTest, + testing::ValuesIn(::android::getAidlHalInstanceNames( + aidl::android::se::omapi::ISecureElementService::descriptor)), + android::hardware::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEAccessControlTest); + +} // namespace diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp new file mode 100644 index 000000000000..c3ab8d13920c --- /dev/null +++ b/omapi/aidl/vts/functional/omapi/Android.bp @@ -0,0 +1,54 @@ +// +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "VtsHalOmapiSeServiceV1_TargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "VtsHalOmapiSeServiceV1_TargetTest.cpp", + ], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libhidlbase", + "libnativehelper", + "libutils", + "libbinder_ndk", + ], + static_libs: [ + "VtsHalHidlTargetTestBase", + "android.se.omapi-V1-ndk", + ], + cflags: [ + "-O0", + "-g", + "-Wall", + "-Werror", + ], + require_root: true, + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp new file mode 100644 index 000000000000..319cb7e70884 --- /dev/null +++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <aidl/android/se/omapi/BnSecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementChannel.h> +#include <aidl/android/se/omapi/ISecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementReader.h> +#include <aidl/android/se/omapi/ISecureElementService.h> +#include <aidl/android/se/omapi/ISecureElementSession.h> + +#include <VtsCoreUtil.h> +#include <aidl/Gtest.h> +#include <aidl/Vintf.h> +#include <android-base/logging.h> +#include <android/binder_manager.h> +#include <binder/IServiceManager.h> +#include <cutils/properties.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> +#include <utils/String16.h> + +using namespace std; +using namespace ::testing; +using namespace android; + +int main(int argc, char** argv) { + InitGoogleTest(&argc, argv); + int status = RUN_ALL_TESTS(); + return status; +} + +namespace { + +class OMAPISEServiceHalTest : public TestWithParam<std::string> { + protected: + class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + + void testSelectableAid( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> aid, std::vector<uint8_t>& selectResponse) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + } + + void testNonSelectableAid( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> aid) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(aid, 0x00, seListener, &channel); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + LOG(ERROR) << res.getMessage(); + ASSERT_FALSE(res.isOk()) << "expected to fail to open channel for this test"; + } + + /** + * Verifies TLV data + * + * @return true if the data is tlv formatted, false otherwise + */ + bool verifyBerTlvData(std::vector<uint8_t> tlv) { + if (tlv.size() == 0) { + LOG(ERROR) << "Invalid tlv, null"; + return false; + } + int i = 0; + if ((tlv[i++] & 0x1F) == 0x1F) { + // extra byte for TAG field + i++; + } + + int len = tlv[i++] & 0xFF; + if (len > 127) { + // more than 1 byte for length + int bytesLength = len - 128; + len = 0; + for (int j = bytesLength; j > 0; j--) { + len += (len << 8) + (tlv[i++] & 0xFF); + } + } + // Additional 2 bytes for the SW + return (tlv.size() == (i + len + 2)); + } + + void internalTransmitApdu( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + std::vector<uint8_t> selectResponse = {}; + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + res = channel->transmit(apdu, &transmitResponse); + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + ASSERT_TRUE(res.isOk()) << "failed to transmit"; + } + + bool supportOMAPIReaders() { + return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())); + } + + void SetUp() override { + LOG(INFO) << "get OMAPI service with name:" << GetParam(); + ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str())); + mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + ASSERT_TRUE(mOmapiSeService); + + std::vector<std::string> readers = {}; + + if (omapiSecureService() != NULL) { + auto status = omapiSecureService()->getReaders(&readers); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + for (auto readerName : readers) { + // Filter eSE readers only + if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = omapiSecureService()->getReader(readerName, &reader); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + mVSReaders[readerName] = reader; + } + } + } + } + + void TearDown() override { + if (mOmapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + } + } + } + + bool isDebuggableBuild() { + char value[PROPERTY_VALUE_MAX] = {0}; + property_get("ro.system.build.type", value, ""); + if (strcmp(value, "userdebug") == 0) { + return true; + } + if (strcmp(value, "eng") == 0) { + return true; + } + return false; + } + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> omapiSecureService() { + return mOmapiSeService; + } + + static inline std::string const ESE_READER_PREFIX = "eSE"; + static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese"; + + std::vector<uint8_t> SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31}; + std::vector<uint8_t> LONG_SELECT_RESPONSE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, + 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, + 0x43, 0x54, 0x53, 0x32}; + std::vector<uint8_t> NON_SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, + 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0xFF}; + + std::vector<std::vector<uint8_t>> ILLEGAL_COMMANDS_TRANSMIT = { + {0x00, 0x70, 0x00, 0x00}, + {0x00, 0x70, 0x80, 0x00}, + {0x00, 0xA4, 0x04, 0x04, 0x10, 0x4A, 0x53, 0x52, 0x31, 0x37, 0x37, + 0x54, 0x65, 0x73, 0x74, 0x65, 0x72, 0x20, 0x31, 0x2E, 0x30}}; + + /* OMAPI APDU Test case 1 and 3 */ + std::vector<std::vector<uint8_t>> NO_DATA_APDU = {{0x00, 0x06, 0x00, 0x00}, + {0x80, 0x06, 0x00, 0x00}, + {0xA0, 0x06, 0x00, 0x00}, + {0x94, 0x06, 0x00, 0x00}, + {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA}, + {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}}; + + /* OMAPI APDU Test case 2 and 4 */ + std::vector<std::vector<uint8_t>> DATA_APDU = {{0x00, 0x08, 0x00, 0x00, 0x00}, + {0x80, 0x08, 0x00, 0x00, 0x00}, + {0xA0, 0x08, 0x00, 0x00, 0x00}, + {0x94, 0x08, 0x00, 0x00, 0x00}, + {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}, + {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}}; + + /* Case 2 APDU command expects the P2 received in the SELECT command as 1-byte outgoing data */ + std::vector<uint8_t> CHECK_SELECT_P2_APDU = {0x00, 0xF4, 0x00, 0x00, 0x00}; + + /* OMAPI APDU Test case 1 and 3 */ + std::vector<std::vector<uint8_t>> SW_62xx_NO_DATA_APDU = {{0x00, 0xF3, 0x00, 0x06}, + {0x00, 0xF3, 0x00, 0x0A, 0x01, 0xAA}}; + + /* OMAPI APDU Test case 2 and 4 */ + std::vector<uint8_t> SW_62xx_DATA_APDU = {0x00, 0xF3, 0x00, 0x08, 0x00}; + std::vector<uint8_t> SW_62xx_VALIDATE_DATA_APDU = {0x00, 0xF3, 0x00, 0x0C, 0x01, 0xAA, 0x00}; + std::vector<std::vector<uint8_t>> SW_62xx = { + {0x62, 0x00}, {0x62, 0x81}, {0x62, 0x82}, {0x62, 0x83}, {0x62, 0x85}, {0x62, 0xF1}, + {0x62, 0xF2}, {0x63, 0xF1}, {0x63, 0xF2}, {0x63, 0xC2}, {0x62, 0x02}, {0x62, 0x80}, + {0x62, 0x84}, {0x62, 0x86}, {0x63, 0x00}, {0x63, 0x81}}; + + std::vector<std::vector<uint8_t>> SEGMENTED_RESP_APDU = { + // Get response Case2 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC2, 0x08, 0x00, 0x00}, + // Get response Case4 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC4, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, + // Get response Case2 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC6, 0x08, 0x00, 0x00}, + // Get response Case4 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xC8, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, + // Test device buffer capacity 7FFF data + {0x00, 0xC2, 0x7F, 0xFF, 0x00}, + // Get response 6CFF+61XX with answer length (P1P2) of 0x0800, 2048 bytes + {0x00, 0xCF, 0x08, 0x00, 0x00}, + // Get response with another CLA with answer length (P1P2) of 0x0800, 2048 bytes + {0x94, 0xC2, 0x08, 0x00, 0x00}}; + long SERVICE_CONNECTION_TIME_OUT = 3000; + + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService; + + std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> + mVSReaders = {}; +}; + +/** Tests getReaders API */ +TEST_P(OMAPISEServiceHalTest, TestGetReaders) { + std::vector<std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> eseReaders = + {}; + + for (const auto& [name, reader] : mVSReaders) { + bool status = false; + LOG(INFO) << "Name of the reader: " << name; + + if (reader) { + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + } + ASSERT_TRUE(status); + + if (name.find(ESE_READER_PREFIX) == std::string::npos) { + LOG(ERROR) << "Incorrect Reader name"; + FAIL(); + } + + if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) { + eseReaders.push_back(reader); + } else { + LOG(INFO) << "Reader not supported: " << name; + FAIL(); + } + } + + if (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())) { + ASSERT_GE(eseReaders.size(), 1); + } else { + ASSERT_TRUE(eseReaders.size() == 0); + } +} + +/** Tests OpenBasicChannel API when aid is null */ +TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNullAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + std::vector<uint8_t> aid = {}; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + bool result = false; + + auto status = reader->openSession(&session); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + if (!session) { + LOG(ERROR) << "Could not open session"; + FAIL(); + } + + status = session->openBasicChannel(aid, 0x00, seListener, &channel); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (channel != nullptr) { + status = channel->isBasicChannel(&result); + ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened"; + } + } + } +} + +/** Tests OpenBasicChannel API when aid is provided */ +TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNonNullAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + bool result = false; + + auto status = reader->openSession(&session); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + if (!session) { + LOG(ERROR) << "Could not open session"; + FAIL(); + } + + status = session->openBasicChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + + if (channel != nullptr) { + status = channel->isBasicChannel(&result); + ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened"; + } + } + } +} + +/** Tests Select API */ +TEST_P(OMAPISEServiceHalTest, TestSelectableAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + testSelectableAid(reader, SELECTABLE_AID, selectResponse); + } + } +} + +/** Tests Select API */ +TEST_P(OMAPISEServiceHalTest, TestLongSelectResponse) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> selectResponse = {}; + testSelectableAid(reader, LONG_SELECT_RESPONSE_AID, selectResponse); + ASSERT_TRUE(verifyBerTlvData(selectResponse)) << "Select Response is not complete"; + } + } +} + +/** Test to fail open channel with wrong aid */ +TEST_P(OMAPISEServiceHalTest, TestWrongAid) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + testNonSelectableAid(reader, NON_SELECTABLE_AID); + } + } +} + +/** Tests with invalid cmds in Transmit */ +TEST_P(OMAPISEServiceHalTest, TestSecurityExceptionInTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>(); + std::vector<uint8_t> selectResponse = {}; + + ASSERT_NE(reader, nullptr) << "reader is null"; + + bool status = false; + auto res = reader->isSecureElementPresent(&status); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_TRUE(status); + + res = reader->openSession(&session); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(session, nullptr) << "Could not open session"; + + res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel); + ASSERT_TRUE(res.isOk()) << res.getMessage(); + ASSERT_NE(channel, nullptr) << "Could not open channel"; + + res = channel->getSelectResponse(&selectResponse); + ASSERT_TRUE(res.isOk()) << "failed to get Select Response"; + ASSERT_GE(selectResponse.size(), 2); + + ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90)); + + for (auto cmd : ILLEGAL_COMMANDS_TRANSMIT) { + std::vector<uint8_t> response = {}; + res = channel->transmit(cmd, &response); + ASSERT_EQ(res.getExceptionCode(), EX_SECURITY); + ASSERT_FALSE(res.isOk()) << "expected failed status for this test"; + } + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + } + } +} + +/** + * Tests Transmit API for all readers. + * + * Checks the return status and verifies the size of the + * response. + */ +TEST_P(OMAPISEServiceHalTest, TestTransmitApdu) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : NO_DATA_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + ASSERT_GE(response.size(), 2); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + } + + for (auto apdu : DATA_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + /* 256 byte data and 2 bytes of status word */ + ASSERT_GE(response.size(), 258); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + } + } + } +} + +/** + * Tests if underlying implementations returns the correct Status Word + * + * TO verify that : + * - the device does not modify the APDU sent to the Secure Element + * - the warning code is properly received by the application layer as SW answer + * - the verify that the application layer can fetch the additionnal data (when present) + */ +TEST_P(OMAPISEServiceHalTest, testStatusWordTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : SW_62xx_NO_DATA_APDU) { + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_GE(response.size(), 2); + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + } + + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + std::vector<uint8_t> apdu = SW_62xx_DATA_APDU; + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_GE(response.size(), 3); + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + + for (uint8_t i = 0x00; i < SW_62xx.size(); i++) { + std::vector<uint8_t> apdu = SW_62xx_VALIDATE_DATA_APDU; + apdu[2] = i + 1; + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + ASSERT_GE(response.size(), apdu.size() + 2); + std::vector<uint8_t> responseSubstring((response.begin() + 0), + (response.begin() + apdu.size())); + // We should not care about which channel number is actually assigned. + responseSubstring[0] = apdu[0]; + ASSERT_TRUE((responseSubstring == apdu)); + std::vector<uint8_t> SW = SW_62xx[i]; + ASSERT_EQ(response[response.size() - 1], SW[1]); + ASSERT_EQ(response[response.size() - 2], SW[0]); + } + } + } +} + +/** Test if the responses are segmented by the underlying implementation */ +TEST_P(OMAPISEServiceHalTest, TestSegmentedResponseTransmit) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + for (auto apdu : SEGMENTED_RESP_APDU) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, apdu, response); + int expectedLength = (0x00 << 24) | (0x00 << 16) | (apdu[2] << 8) | apdu[3]; + ASSERT_EQ(response.size(), (expectedLength + 2)); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + ASSERT_EQ((response[response.size() - 3] & 0xFF), (0xFF)); + } + } + } +} + +/** + * Tests the P2 value of the select command. + * + * Verifies that the default P2 value (0x00) is not modified by the underlying implementation. + */ +TEST_P(OMAPISEServiceHalTest, TestP2Value) { + ASSERT_TRUE(supportOMAPIReaders() == true); + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + std::vector<uint8_t> response = {}; + internalTransmitApdu(reader, CHECK_SELECT_P2_APDU, response); + ASSERT_GE(response.size(), 3); + ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00)); + ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90)); + ASSERT_EQ((response[response.size() - 3] & 0xFF), (0x00)); + } + } +} + +INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest, + testing::ValuesIn(::android::getAidlHalInstanceNames( + aidl::android::se::omapi::ISecureElementService::descriptor)), + android::hardware::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEServiceHalTest); + +} // namespace diff --git a/omapi/java/Android.bp b/omapi/java/Android.bp new file mode 100644 index 000000000000..8d38da048d9b --- /dev/null +++ b/omapi/java/Android.bp @@ -0,0 +1,17 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "framework-omapi-sources", + srcs: [ + "**/*.java", + "**/*.aidl", + ], + visibility: ["//frameworks/base"], +} diff --git a/core/java/android/se/OWNERS b/omapi/java/android/se/OWNERS index 5682fd3281f4..5682fd3281f4 100644 --- a/core/java/android/se/OWNERS +++ b/omapi/java/android/se/OWNERS diff --git a/core/java/android/se/omapi/Channel.java b/omapi/java/android/se/omapi/Channel.java index 90ce11ae0313..90ce11ae0313 100644 --- a/core/java/android/se/omapi/Channel.java +++ b/omapi/java/android/se/omapi/Channel.java diff --git a/core/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS index 5682fd3281f4..5682fd3281f4 100644 --- a/core/java/android/se/omapi/OWNERS +++ b/omapi/java/android/se/omapi/OWNERS diff --git a/core/java/android/se/omapi/Reader.java b/omapi/java/android/se/omapi/Reader.java index 90c934d189fa..3c2135d9bc9d 100644 --- a/core/java/android/se/omapi/Reader.java +++ b/omapi/java/android/se/omapi/Reader.java @@ -170,7 +170,9 @@ public final class Reader { try { closeSessions(); return mReader.reset(); - } catch (RemoteException ignore) {return false;} + } catch (RemoteException ignore) { + return false; + } } } } diff --git a/core/java/android/se/omapi/SEService.java b/omapi/java/android/se/omapi/SEService.java index 333af91ac872..f42ca364b6d9 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/omapi/java/android/se/omapi/SEService.java @@ -230,20 +230,20 @@ public final class SEService { * is not exist. * @return A Reader object for this uicc slot. */ - public @NonNull Reader getUiccReader(int slotNumber) { - if (slotNumber < 1) { - throw new IllegalArgumentException("slotNumber should be larger than 0"); - } - loadReaders(); + public @NonNull Reader getUiccReader(int slotNumber) { + if (slotNumber < 1) { + throw new IllegalArgumentException("slotNumber should be larger than 0"); + } + loadReaders(); - String readerName = UICC_TERMINAL + slotNumber; - Reader reader = mReaders.get(readerName); + String readerName = UICC_TERMINAL + slotNumber; + Reader reader = mReaders.get(readerName); - if (reader == null) { + if (reader == null) { throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist"); - } + } - return reader; + return reader; } /** diff --git a/core/java/android/se/omapi/Session.java b/omapi/java/android/se/omapi/Session.java index d5f8c82bf47e..d5f8c82bf47e 100644 --- a/core/java/android/se/omapi/Session.java +++ b/omapi/java/android/se/omapi/Session.java diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java deleted file mode 100644 index 84fb0e62dbca..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java +++ /dev/null @@ -1,63 +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.server.backup.encryption; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; - -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport; -import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager; - -/** - * This service provides encryption of backup data. For an intent used to bind to this service, it - * provides an {@link IntermediateEncryptingTransport} which is an implementation of {@link - * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from the - * real {@link IBackupTransport}. - */ -public class BackupEncryptionService extends Service { - public static final String TAG = "BackupEncryption"; - private static IntermediateEncryptingTransportManager sTransportManager = null; - - @Override - public void onCreate() { - Log.i(TAG, "onCreate:" + this); - if (sTransportManager == null) { - Log.i(TAG, "Creating IntermediateEncryptingTransportManager"); - sTransportManager = new IntermediateEncryptingTransportManager(this); - } - } - - @Override - public void onDestroy() { - Log.i(TAG, "onDestroy:" + this); - } - - @Override - public IBinder onBind(Intent intent) { - // TODO (b141536117): Check connection with TransportClient.connect and return null on fail. - return sTransportManager.get(intent); - } - - @Override - public boolean onUnbind(Intent intent) { - sTransportManager.cleanup(intent); - return false; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java index 1d841b4a2c8f..db2dd2f34ec1 100644 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java @@ -16,8 +16,6 @@ package com.android.server.backup.encryption; -import static com.android.server.backup.encryption.BackupEncryptionService.TAG; - import android.content.Context; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -36,6 +34,8 @@ import java.security.SecureRandom; import java.util.Map; public class KeyValueEncrypter { + private static final String TAG = "KeyValueEncrypter"; + private final Context mContext; private final EncryptionKeyHelper mKeyHelper; diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java deleted file mode 100644 index c3cb335db89e..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java +++ /dev/null @@ -1,211 +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.server.backup.encryption.transport; - -import static com.android.server.backup.encryption.BackupEncryptionService.TAG; - -import android.app.backup.BackupTransport; -import android.app.backup.RestoreDescription; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.encryption.KeyValueEncrypter; -import com.android.server.backup.transport.DelegatingTransport; -import com.android.server.backup.transport.TransportClient; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.atomic.AtomicReference; - -/** - * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when - * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link - * TransportClient.connect(String)}. - */ -public class IntermediateEncryptingTransport extends DelegatingTransport { - private static final String BACKUP_TEMP_DIR = "backup"; - private static final String RESTORE_TEMP_DIR = "restore"; - - private final TransportClient mTransportClient; - private final Object mConnectLock = new Object(); - private final Context mContext; - private volatile IBackupTransport mRealTransport; - private AtomicReference<String> mNextRestorePackage = new AtomicReference<>(); - private final KeyValueEncrypter mKeyValueEncrypter; - private final boolean mShouldEncrypt; - - IntermediateEncryptingTransport( - TransportClient transportClient, Context context, boolean shouldEncrypt) { - this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt); - } - - @VisibleForTesting - IntermediateEncryptingTransport( - TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter, - boolean shouldEncrypt) { - mTransportClient = transportClient; - mContext = context; - mKeyValueEncrypter = keyValueEncrypter; - mShouldEncrypt = shouldEncrypt; - } - - @Override - protected IBackupTransport getDelegate() throws RemoteException { - if (mRealTransport == null) { - connect(); - } - Log.d(TAG, "real transport = " + mRealTransport.name()); - return mRealTransport; - } - - @Override - public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) - throws RemoteException { - if (!mShouldEncrypt) { - return super.performBackup(packageInfo, inFd, flags); - } - - File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName); - if (encryptedStorageFile == null) { - return BackupTransport.TRANSPORT_ERROR; - } - - // Encrypt the backup data and write it into a temp file. - try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) { - mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd, - encryptedOutput); - } catch (Throwable e) { - Log.e(TAG, "Failed to encrypt backup data: ", e); - return BackupTransport.TRANSPORT_ERROR; - } - - // Pass the temp file to the real transport for backup. - try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) { - return super.performBackup( - packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags); - } catch (IOException e) { - Log.e(TAG, "Failed to read encrypted data from temp storage: ", e); - return BackupTransport.TRANSPORT_ERROR; - } - } - - @Override - public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { - if (!mShouldEncrypt) { - return super.getRestoreData(outFd); - } - - String nextRestorePackage = mNextRestorePackage.get(); - if (nextRestorePackage == null) { - Log.e(TAG, "No next restore package set"); - return BackupTransport.TRANSPORT_ERROR; - } - - File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage); - if (encryptedStorageFile == null) { - return BackupTransport.TRANSPORT_ERROR; - } - - // Get encrypted restore data from the real transport and write it into a temp file. - try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) { - int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD())); - if (status != BackupTransport.TRANSPORT_OK) { - Log.e(TAG, "Failed to read restore data from transport, status = " + status); - return status; - } - } catch (IOException e) { - Log.e(TAG, "Failed to write encrypted data to temp storage: ", e); - return BackupTransport.TRANSPORT_ERROR; - } - - // Decrypt the data and write it into the fd given by the real transport. - try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) { - mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd); - encryptedStorageFile.delete(); - } catch (Exception e) { - Log.e(TAG, "Failed to decrypt restored data: ", e); - return BackupTransport.TRANSPORT_ERROR; - } - - return BackupTransport.TRANSPORT_OK; - } - - @Override - public RestoreDescription nextRestorePackage() throws RemoteException { - if (!mShouldEncrypt) { - return super.nextRestorePackage(); - } - - RestoreDescription restoreDescription = super.nextRestorePackage(); - mNextRestorePackage.set(restoreDescription.getPackageName()); - - return restoreDescription; - } - - @VisibleForTesting - protected File getBackupTempStorage(String packageName) { - return getTempStorage(packageName, BACKUP_TEMP_DIR); - } - - @VisibleForTesting - protected File getRestoreTempStorage(String packageName) { - return getTempStorage(packageName, RESTORE_TEMP_DIR); - } - - private File getTempStorage(String packageName, String operationType) { - File encryptedDir = new File(mContext.getFilesDir(), operationType); - encryptedDir.mkdir(); - File encryptedFile = new File(encryptedDir, packageName); - try { - encryptedFile.createNewFile(); - } catch (IOException e) { - Log.e(TAG, "Failed to create temp file for encrypted data: ", e); - } - return encryptedFile; - } - - private void connect() throws RemoteException { - Log.i(TAG, "connecting " + mTransportClient); - synchronized (mConnectLock) { - if (mRealTransport == null) { - mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport"); - if (mRealTransport == null) { - throw new RemoteException("Could not connect: " + mTransportClient); - } - } - } - } - - @VisibleForTesting - TransportClient getClient() { - return mTransportClient; - } - - @VisibleForTesting - void setNextRestorePackage(String nextRestorePackage) { - mNextRestorePackage.set(nextRestorePackage); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java deleted file mode 100644 index 7c4082c2a54d..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java +++ /dev/null @@ -1,110 +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.server.backup.encryption.transport; - -import static com.android.server.backup.encryption.BackupEncryptionService.TAG; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.UserHandle; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.backup.IBackupTransport; -import com.android.internal.widget.LockPatternUtils; -import com.android.server.backup.transport.TransportClientManager; -import com.android.server.backup.transport.TransportStats; - -import java.util.HashMap; -import java.util.Map; - -/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */ -public class IntermediateEncryptingTransportManager { - private static final String CALLER = "IntermediateEncryptingTransportManager"; - private final TransportClientManager mTransportClientManager; - private final Object mTransportsLock = new Object(); - private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>(); - private Context mContext; - - @VisibleForTesting - IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) { - mTransportClientManager = transportClientManager; - } - - public IntermediateEncryptingTransportManager(Context context) { - this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats())); - mContext = context; - } - - /** - * Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and - * provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link - * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from - * the real {@link IBackupTransport}. - * - * @param intent {@link Intent} created with a call to {@link - * TransportClientManager.getEncryptingTransportIntent(ComponentName)}. - * @return - */ - public IntermediateEncryptingTransport get(Intent intent) { - Intent transportIntent = TransportClientManager.getRealTransportIntent(intent); - Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent); - synchronized (mTransportsLock) { - return mTransports.computeIfAbsent( - transportIntent.getComponent(), c -> create(transportIntent)); - } - } - - /** Create an instance of {@link IntermediateEncryptingTransport}. */ - private IntermediateEncryptingTransport create(Intent realTransportIntent) { - Log.d(TAG, "create: intent:" + realTransportIntent); - - LockPatternUtils patternUtils = new LockPatternUtils(mContext); - boolean shouldEncrypt = - realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport") - && (patternUtils.isLockPatternEnabled(UserHandle.myUserId()) - || patternUtils.isLockPasswordEnabled(UserHandle.myUserId())); - - return new IntermediateEncryptingTransport( - mTransportClientManager.getTransportClient( - realTransportIntent.getComponent(), - realTransportIntent.getExtras(), - CALLER), - mContext, - shouldEncrypt); - } - - /** - * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link - * #get(Intent)} with this {@link Intent}. - */ - public void cleanup(Intent intent) { - Intent transportIntent = TransportClientManager.getRealTransportIntent(intent); - Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent); - - IntermediateEncryptingTransport transport; - synchronized (mTransportsLock) { - transport = mTransports.remove(transportIntent.getComponent()); - } - if (transport != null) { - mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER); - } else { - Log.i(TAG, "Could not find IntermediateEncryptingTransport"); - } - } -} diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java deleted file mode 100644 index 0d43a190cd07..000000000000 --- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java +++ /dev/null @@ -1,120 +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.server.backup.encryption.transport; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotSame; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.content.ComponentName; -import android.content.Intent; -import android.os.Bundle; -import android.platform.test.annotations.Presubmit; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.backup.transport.TransportClient; -import com.android.server.backup.transport.TransportClientManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@Presubmit -@RunWith(AndroidJUnit4.class) -public class IntermediateEncryptingTransportManagerTest { - @Mock private TransportClient mTransportClient; - @Mock private TransportClientManager mTransportClientManager; - - private final ComponentName mTransportComponent = new ComponentName("pkg", "class"); - private final Bundle mExtras = new Bundle(); - private Intent mEncryptingTransportIntent; - private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mExtras.putInt("test", 1); - mEncryptingTransportIntent = - TransportClientManager.getEncryptingTransportIntent(mTransportComponent) - .putExtras(mExtras); - mIntermediateEncryptingTransportManager = - new IntermediateEncryptingTransportManager(mTransportClientManager); - } - - @Test - public void testGet_createsClientWithRealTransportComponentAndExtras() { - when(mTransportClientManager.getTransportClient(any(), any(), any())) - .thenReturn(mTransportClient); - - IntermediateEncryptingTransport intermediateEncryptingTransport = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - - assertEquals(mTransportClient, intermediateEncryptingTransport.getClient()); - verify(mTransportClientManager, times(1)) - .getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any()); - verifyNoMoreInteractions(mTransportClientManager); - } - - @Test - public void testGet_callTwice_returnsSameTransport() { - IntermediateEncryptingTransport transport1 = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - IntermediateEncryptingTransport transport2 = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - - assertEquals(transport1, transport2); - } - - @Test - public void testCleanup_disposesTransportClient() { - when(mTransportClientManager.getTransportClient(any(), any(), any())) - .thenReturn(mTransportClient); - - IntermediateEncryptingTransport transport = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent); - - verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any()); - verify(mTransportClientManager, times(1)) - .disposeOfTransportClient(eq(mTransportClient), any()); - verifyNoMoreInteractions(mTransportClientManager); - } - - @Test - public void testCleanup_removesCachedTransport() { - when(mTransportClientManager.getTransportClient(any(), any(), any())) - .thenReturn(mTransportClient); - - IntermediateEncryptingTransport transport1 = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent); - IntermediateEncryptingTransport transport2 = - mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent); - - assertNotSame(transport1, transport2); - } -} diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java deleted file mode 100644 index a85b2e4498a6..000000000000 --- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java +++ /dev/null @@ -1,180 +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.server.backup.encryption.transport; - -import static junit.framework.Assert.assertEquals; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import android.app.backup.BackupTransport; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; -import android.platform.test.annotations.Presubmit; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.encryption.KeyValueEncrypter; -import com.android.server.backup.transport.TransportClient; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; - -@Presubmit -@RunWith(AndroidJUnit4.class) -public class IntermediateEncryptingTransportTest { - private static final String TEST_PACKAGE_NAME = "test_package"; - - private IntermediateEncryptingTransport mIntermediateEncryptingTransport; - private final PackageInfo mTestPackage = new PackageInfo(); - - @Mock private IBackupTransport mRealTransport; - @Mock private TransportClient mTransportClient; - @Mock private ParcelFileDescriptor mParcelFileDescriptor; - @Mock private KeyValueEncrypter mKeyValueEncrypter; - @Mock private Context mContext; - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private File mTempFile; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mIntermediateEncryptingTransport = - new IntermediateEncryptingTransport( - mTransportClient, mContext, mKeyValueEncrypter, true); - mTestPackage.packageName = TEST_PACKAGE_NAME; - mTempFile = mTemporaryFolder.newFile(); - - when(mTransportClient.connect(anyString())).thenReturn(mRealTransport); - when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK); - } - - @Test - public void testGetDelegate_callsConnect() throws Exception { - IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate(); - - assertEquals(mRealTransport, ret); - verify(mTransportClient, times(1)).connect(anyString()); - verifyNoMoreInteractions(mTransportClient); - } - - @Test - public void testGetDelegate_callTwice_callsConnectOnce() throws Exception { - when(mTransportClient.connect(anyString())).thenReturn(mRealTransport); - - IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate(); - IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate(); - - assertEquals(mRealTransport, ret1); - assertEquals(mRealTransport, ret2); - verify(mTransportClient, times(1)).connect(anyString()); - verifyNoMoreInteractions(mTransportClient); - } - - @Test - public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate() - throws Exception { - mIntermediateEncryptingTransport = - new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true); - - mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0); - - verify(mKeyValueEncrypter, times(1)) - .encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any()); - verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0)); - } - - @Test - public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate() - throws Exception { - mIntermediateEncryptingTransport = - new TestIntermediateTransport( - mTransportClient, mContext, mKeyValueEncrypter, false); - - mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0); - - verifyZeroInteractions(mKeyValueEncrypter); - verify(mRealTransport, times(1)) - .performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0)); - } - - @Test - public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate() - throws Exception { - mIntermediateEncryptingTransport = - new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true); - mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME); - - mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor); - - verify(mKeyValueEncrypter, times(1)) - .decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor)); - verify(mRealTransport, times(1)).getRestoreData(any()); - } - - @Test - public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate() - throws Exception { - mIntermediateEncryptingTransport = - new TestIntermediateTransport( - mTransportClient, mContext, mKeyValueEncrypter, false); - mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME); - - mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor); - - verifyZeroInteractions(mKeyValueEncrypter); - verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor)); - } - - private final class TestIntermediateTransport extends IntermediateEncryptingTransport { - TestIntermediateTransport( - TransportClient transportClient, - Context context, - KeyValueEncrypter keyValueEncrypter, - boolean shouldEncrypt) { - super(transportClient, context, keyValueEncrypter, shouldEncrypt); - } - - @Override - protected File getBackupTempStorage(String packageName) { - return mTempFile; - } - - @Override - protected File getRestoreTempStorage(String packageName) { - return mTempFile; - } - } -} diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index 9f07317bee37..126b823ab271 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -42,8 +42,8 @@ import android.companion.AssociationRequest; import android.companion.BluetoothDeviceFilter; import android.companion.BluetoothLeDeviceFilter; import android.companion.DeviceFilter; +import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceDiscoveryService; -import android.companion.IFindDeviceCallback; import android.companion.WifiDeviceFilter; import android.content.BroadcastReceiver; import android.content.Context; @@ -92,7 +92,7 @@ public class CompanionDeviceDiscoveryService extends Service { AssociationRequest mRequest; List<DeviceFilterPair> mDevicesFound; DeviceFilterPair mSelectedDevice; - IFindDeviceCallback mFindCallback; + IAssociationRequestCallback mApplicationCallback; AndroidFuture<String> mServiceCallback; boolean mIsScanning = false; @@ -104,13 +104,13 @@ public class CompanionDeviceDiscoveryService extends Service { @Override public void startDiscovery(AssociationRequest request, String callingPackage, - IFindDeviceCallback findCallback, + IAssociationRequestCallback appCallback, AndroidFuture<String> serviceCallback) { Log.i(LOG_TAG, "startDiscovery() called with: filter = [" + request - + "], findCallback = [" + findCallback + "]" + + "], appCallback = [" + appCallback + "]" + "], serviceCallback = [" + serviceCallback + "]"); - mFindCallback = findCallback; + mApplicationCallback = appCallback; mServiceCallback = serviceCallback; Handler.getMain().sendMessage(obtainMessage( CompanionDeviceDiscoveryService::startDiscovery, @@ -299,7 +299,7 @@ public class CompanionDeviceDiscoveryService extends Service { //TODO also, on timeout -> call onFailure private void onReadyToShowUI() { try { - mFindCallback.onSuccess(PendingIntent.getActivity( + mApplicationCallback.onAssociationPending(PendingIntent.getActivity( this, 0, new Intent(this, CompanionDeviceActivity.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 801b490406cc..a3eb0eccad9d 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -251,7 +251,7 @@ public class ExternalStorageProvider extends FileSystemProvider { if (volume.getType() == VolumeInfo.TYPE_PUBLIC) { root.flags |= Root.FLAG_HAS_SETTINGS; } - if (volume.isVisibleForRead(userId)) { + if (volume.isVisibleForUser(userId)) { root.visiblePath = volume.getPathForUser(userId); } else { root.visiblePath = null; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java index b8ad321d6dd3..0b436a9a8df5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; @@ -118,8 +119,8 @@ public final class BluetoothDeviceFilter { return true; } } else if (btClass != null) { - if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || - btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP) + || doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return true; } } @@ -137,7 +138,7 @@ public final class BluetoothDeviceFilter { } } return btClass != null - && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP); + && doesClassMatch(btClass, BluetoothClass.PROFILE_OPP); } } @@ -151,7 +152,7 @@ public final class BluetoothDeviceFilter { } } return btClass != null - && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU); + && doesClassMatch(btClass, BluetoothClass.PROFILE_PANU); } } @@ -165,7 +166,12 @@ public final class BluetoothDeviceFilter { } } return btClass != null - && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP); + && doesClassMatch(btClass, BluetoothClass.PROFILE_NAP); } } + + @SuppressLint("NewApi") // Hidden API made public + private static boolean doesClassMatch(BluetoothClass btClass, int classId) { + return btClass.doesClassMatch(classId); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 1df1bce014cb..389892ed15e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -307,7 +307,8 @@ public class BluetoothEventManager { CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); if (cachedDevice == null) { cachedDevice = mDeviceManager.addDevice(device); - Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice"); + Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice " + + cachedDevice.getDevice().getAnonymizedAddress()); } else if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED && !cachedDevice.getDevice().isConnected()) { // Dispatch device add callback to show bonded but diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 253629c8d128..c9af4d53f9a8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -2,6 +2,7 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; @@ -70,6 +71,12 @@ public class BluetoothUtils { void onShowError(Context context, String name, int messageResId); } + /** + * @param context to access resources from + * @param cachedDevice to get class from + * @return pair containing the drawable and the description of the Bluetooth class + * of the device. + */ public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, CachedBluetoothDevice cachedDevice) { BluetoothClass btClass = cachedDevice.getBtClass(); @@ -110,13 +117,13 @@ public class BluetoothUtils { } } if (btClass != null) { - if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headset_hfp), context.getString(R.string.bluetooth_talkback_headset)); } - if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) { return new Pair<>( getBluetoothDrawable(context, com.android.internal.R.drawable.ic_bt_headphones_a2dp), @@ -376,4 +383,9 @@ public class BluetoothUtils { } return Uri.parse(data); } + + @SuppressLint("NewApi") // Hidden API made public + private static boolean doesClassMatch(BluetoothClass btClass, int classId) { + return btClass.doesClassMatch(classId); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 912b4689c88d..a9011601f32b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -194,7 +194,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { if (BluetoothUtils.D) { Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device " - + mDevice.getAlias() + ", newProfileState " + newProfileState); + + mDevice.getAnonymizedAddress() + ", newProfileState " + newProfileState); } if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) { @@ -745,7 +745,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } if (BluetoothUtils.D) { - Log.d(TAG, "updating profiles for " + mDevice.getAlias()); + Log.d(TAG, "updating profiles for " + mDevice.getAnonymizedAddress()); BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index 80b03a43b055..e7a6b327bacd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -19,11 +19,13 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.le.BluetoothLeScanner; import android.content.Context; import android.os.ParcelUuid; import android.util.Log; +import java.time.Duration; import java.util.List; import java.util.Set; @@ -140,7 +142,7 @@ public class LocalBluetoothAdapter { } public void setDiscoverableTimeout(int timeout) { - mAdapter.setDiscoverableTimeout(timeout); + mAdapter.setDiscoverableTimeout(Duration.ofSeconds(timeout)); } public long getDiscoveryEndMillis() { @@ -156,7 +158,9 @@ public class LocalBluetoothAdapter { } public boolean setScanMode(int mode, int duration) { - return mAdapter.setScanMode(mode, duration); + return (mAdapter.setDiscoverableTimeout(Duration.ofSeconds(duration)) + == BluetoothStatusCodes.SUCCESS + && mAdapter.setScanMode(mode) == BluetoothStatusCodes.SUCCESS); } public void startScanning(boolean force) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index 9d4669a5a37d..cd5c78d19c8c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -62,7 +62,7 @@ public class BluetoothMediaDevice extends MediaDevice { if (!(drawable instanceof BitmapDrawable)) { setColorFilter(drawable); } - return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); + return drawable; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index 949b2456042c..c34f65cfdbf6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -29,7 +29,6 @@ import android.media.MediaRouter2Manager; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.BluetoothUtils; import java.util.List; @@ -61,7 +60,7 @@ public class InfoMediaDevice extends MediaDevice { public Drawable getIcon() { final Drawable drawable = getIconWithoutBackground(); setColorFilter(drawable); - return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); + return drawable; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index b6c0b30b3bd4..1139d33d440d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -32,7 +32,6 @@ import android.media.MediaRouter2Manager; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; -import com.android.settingslib.bluetooth.BluetoothUtils; /** * PhoneMediaDevice extends MediaDevice to represents Phone device. @@ -87,7 +86,7 @@ public class PhoneMediaDevice extends MediaDevice { public Drawable getIcon() { final Drawable drawable = getIconWithoutBackground(); setColorFilter(drawable); - return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); + return drawable; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index a210e90a3cfc..8b17be1e8ec9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -19,7 +19,6 @@ package com.android.settingslib.notification; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.AlertDialog; -import android.app.Dialog; import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; @@ -85,6 +84,7 @@ public class EnableZenModeDialog { @VisibleForTesting protected Context mContext; + private final int mThemeResId; @VisibleForTesting protected TextView mZenAlarmWarning; @VisibleForTesting @@ -97,10 +97,15 @@ public class EnableZenModeDialog { protected LayoutInflater mLayoutInflater; public EnableZenModeDialog(Context context) { + this(context, 0); + } + + public EnableZenModeDialog(Context context, int themeResId) { mContext = context; + mThemeResId = themeResId; } - public Dialog createDialog() { + public AlertDialog createDialog() { mNotificationManager = (NotificationManager) mContext. getSystemService(Context.NOTIFICATION_SERVICE); mForeverId = Condition.newId(mContext).appendPath("forever").build(); @@ -108,7 +113,7 @@ public class EnableZenModeDialog { mUserId = mContext.getUserId(); mAttached = false; - final AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId) .setTitle(R.string.zen_mode_settings_turn_on_dialog_title) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 0fe4efefc2cb..71accc416416 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -47,6 +47,7 @@ public class SystemSettings { Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Settings.System.SCREEN_BRIGHTNESS_FOR_VR, Settings.System.ADAPTIVE_SLEEP, // moved to secure + Settings.System.APPLY_RAMPING_RINGER, Settings.System.VIBRATE_INPUT_DEVICES, Settings.System.MODE_RINGER_STREAMS_AFFECTED, Settings.System.TEXT_AUTO_REPLACE, @@ -80,6 +81,7 @@ public class SystemSettings { Settings.System.NOTIFICATION_VIBRATION_INTENSITY, Settings.System.RING_VIBRATION_INTENSITY, Settings.System.HAPTIC_FEEDBACK_INTENSITY, + Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE Settings.System.DISPLAY_COLOR_MODE, Settings.System.ALARM_ALERT, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 462c3a5bba03..84e9d2809205 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -117,9 +117,11 @@ public class SystemSettingsValidators { VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.MUTE_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(System.VIBRATE_ON, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); + VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR); VALIDATORS.put(System.RINGTONE, URI_VALIDATOR); VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR); VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 8c669d21db81..86ee3b3ddcfb 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -98,6 +98,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config"; private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config"; private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings"; + // Restoring sim-specific data backed up from newer Android version to Android 12 was causing a + // fatal crash. Creating a backup with a different key will prevent Android 12 versions from + // restoring this data. + private static final String KEY_SIM_SPECIFIC_SETTINGS_2 = "sim_specific_settings_2"; // Versioning of the state file. Increment this version // number any time the set of state items is altered. @@ -253,7 +257,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { deviceSpecificInformation, data); stateChecksums[STATE_SIM_SPECIFIC_SETTINGS] = writeIfChanged(stateChecksums[STATE_SIM_SPECIFIC_SETTINGS], - KEY_SIM_SPECIFIC_SETTINGS, simSpecificSettingsData, data); + KEY_SIM_SPECIFIC_SETTINGS_2, simSpecificSettingsData, data); writeNewChecksums(stateChecksums, newState); } @@ -284,10 +288,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { // versionCode of com.android.providers.settings corresponds to SDK_INT mRestoredFromSdkInt = (int) appVersionCode; - HashSet<String> movedToGlobal = new HashSet<String>(); - Settings.System.getMovedToGlobalSettings(movedToGlobal); - Settings.Secure.getMovedToGlobalSettings(movedToGlobal); + Set<String> movedToGlobal = getMovedToGlobalSettings(); Set<String> movedToSecure = getMovedToSecureSettings(); + Set<String> movedToSystem = getMovedToSystemSettings(); Set<String> preservedGlobalSettings = getSettingsToPreserveInRestore( Settings.Global.CONTENT_URI); @@ -318,32 +321,23 @@ public class SettingsBackupAgent extends BackupAgentHelper { switch (key) { case KEY_SYSTEM : restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal, - movedToSecure, R.array.restore_blocked_system_settings, - dynamicBlockList, + movedToSecure, /* movedToSystem= */ null, + R.array.restore_blocked_system_settings, dynamicBlockList, preservedSystemSettings); mSettingsHelper.applyAudioSettings(); break; case KEY_SECURE : - restoreSettings( - data, - Settings.Secure.CONTENT_URI, - movedToGlobal, - null, - R.array.restore_blocked_secure_settings, - dynamicBlockList, + restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, + /* movedToSecure= */ null, movedToSystem, + R.array.restore_blocked_secure_settings, dynamicBlockList, preservedSecureSettings); break; case KEY_GLOBAL : - restoreSettings( - data, - Settings.Global.CONTENT_URI, - null, - movedToSecure, - R.array.restore_blocked_global_settings, - dynamicBlockList, - preservedGlobalSettings); + restoreSettings(data, Settings.Global.CONTENT_URI, /* movedToGlobal= */ null, + movedToSecure, movedToSystem, R.array.restore_blocked_global_settings, + dynamicBlockList, preservedGlobalSettings); break; case KEY_WIFI_SUPPLICANT : @@ -395,6 +389,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { break; case KEY_SIM_SPECIFIC_SETTINGS: + // Intentional fall through so that sim-specific backups from Android 12 will + // also be restored on newer Android versions. + case KEY_SIM_SPECIFIC_SETTINGS_2: byte[] restoredSimSpecificSettings = new byte[size]; data.readEntityData(restoredSimSpecificSettings, 0, size); restoreSimSpecificSettings(restoredSimSpecificSettings); @@ -435,10 +432,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (DEBUG_BACKUP) Log.d(TAG, "Flattened data version " + version); if (version <= FULL_BACKUP_VERSION) { // Generate the moved-to-global lookup table - HashSet<String> movedToGlobal = new HashSet<String>(); - Settings.System.getMovedToGlobalSettings(movedToGlobal); - Settings.Secure.getMovedToGlobalSettings(movedToGlobal); + Set<String> movedToGlobal = getMovedToGlobalSettings(); Set<String> movedToSecure = getMovedToSecureSettings(); + Set<String> movedToSystem = getMovedToSystemSettings(); // system settings data first int nBytes = in.readInt(); @@ -446,22 +442,19 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal, - movedToSecure, R.array.restore_blocked_system_settings, - Collections.emptySet(), Collections.emptySet()); + movedToSecure, /* movedToSystem= */ null, + R.array.restore_blocked_system_settings, Collections.emptySet(), + Collections.emptySet()); // secure settings nBytes = in.readInt(); if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data"); if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); - restoreSettings( - buffer, - nBytes, - Settings.Secure.CONTENT_URI, - movedToGlobal, - null, - R.array.restore_blocked_secure_settings, - Collections.emptySet(), Collections.emptySet()); + restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, + /* movedToSecure= */ null, movedToSystem, + R.array.restore_blocked_secure_settings, Collections.emptySet(), + Collections.emptySet()); // Global only if sufficiently new if (version >= FULL_BACKUP_ADDED_GLOBAL) { @@ -469,10 +462,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of global settings data"); if (nBytes > buffer.length) buffer = new byte[nBytes]; in.readFully(buffer, 0, nBytes); - movedToGlobal.clear(); // no redirection; this *is* the global namespace - restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal, - movedToSecure, R.array.restore_blocked_global_settings, - Collections.emptySet(), Collections.emptySet()); + restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, + /* movedToGlobal= */ null, movedToSecure, movedToSystem, + R.array.restore_blocked_global_settings, Collections.emptySet(), + Collections.emptySet()); } // locale @@ -543,6 +536,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + private Set<String> getMovedToGlobalSettings() { + HashSet<String> movedToGlobalSettings = new HashSet<String>(); + Settings.System.getMovedToGlobalSettings(movedToGlobalSettings); + Settings.Secure.getMovedToGlobalSettings(movedToGlobalSettings); + return movedToGlobalSettings; + } + private Set<String> getMovedToSecureSettings() { Set<String> movedToSecureSettings = new HashSet<>(); Settings.Global.getMovedToSecureSettings(movedToSecureSettings); @@ -550,6 +550,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { return movedToSecureSettings; } + private Set<String> getMovedToSystemSettings() { + Set<String> movedToSystemSettings = new HashSet<>(); + Settings.Global.getMovedToSystemSettings(movedToSystemSettings); + Settings.Secure.getMovedToSystemSettings(movedToSystemSettings); + return movedToSystemSettings; + } + private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException { long[] stateChecksums = new long[STATE_SIZE]; @@ -710,8 +717,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { private void restoreSettings( BackupDataInput data, Uri contentUri, - HashSet<String> movedToGlobal, + Set<String> movedToGlobal, Set<String> movedToSecure, + Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, Set<String> settingsToPreserve) { @@ -728,6 +736,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { contentUri, movedToGlobal, movedToSecure, + movedToSystem, blockedSettingsArrayId, dynamicBlockList, settingsToPreserve); @@ -737,8 +746,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { byte[] settings, int bytes, Uri contentUri, - HashSet<String> movedToGlobal, + Set<String> movedToGlobal, Set<String> movedToSecure, + Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, Set<String> settingsToPreserve) { @@ -749,6 +759,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { contentUri, movedToGlobal, movedToSecure, + movedToSystem, blockedSettingsArrayId, dynamicBlockList, settingsToPreserve); @@ -760,8 +771,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { int pos, int bytes, Uri contentUri, - HashSet<String> movedToGlobal, + Set<String> movedToGlobal, Set<String> movedToSecure, + Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, Set<String> settingsToPreserve) { @@ -842,6 +854,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { destination = Settings.Global.CONTENT_URI; } else if (movedToSecure != null && movedToSecure.contains(key)) { destination = Settings.Secure.CONTENT_URI; + } else if (movedToSystem != null && movedToSystem.contains(key)) { + destination = Settings.System.CONTENT_URI; } else { destination = contentUri; } @@ -1192,6 +1206,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Settings.Secure.CONTENT_URI, null, null, + null, blockedSettingsArrayId, dynamicBlocklist, preservedSettings); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 5d75d4f2c7da..a67b56502394 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1620,7 +1620,11 @@ class SettingsProtoDumpUtil { p.end(token); // Please insert new settings using the same order as in GlobalSettingsProto. + // The rest of the settings were moved to Settings.Secure or Settings.System, and are thus + // excluded here since they're deprecated from Settings.Global. + // Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated. + // Settings.Global.APPLY_RAMPING_RINGER intentionally excluded since it's deprecated. } private static void dumpProtoConfigSettingsLocked( @@ -2953,6 +2957,10 @@ class SettingsProtoDumpUtil { Settings.System.WHEN_TO_MAKE_WIFI_CALLS, SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS); + dumpSetting(s, p, + Settings.System.APPLY_RAMPING_RINGER, + SystemSettingsProto.APPLY_RAMPING_RINGER); + // Please insert new settings using the same order as in SecureSettingsProto. // The rest of the settings were moved to Settings.Secure, and are thus excluded here since diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ce7517f14e77..11e491687fb2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -289,6 +289,12 @@ public class SettingsProvider extends ContentProvider { Settings.Global.getMovedToSecureSettings(sGlobalMovedToSecureSettings); } + // Per all users global settings that moved to the per user system settings. + static final Set<String> sGlobalMovedToSystemSettings = new ArraySet<>(); + static { + Settings.Global.getMovedToSystemSettings(sGlobalMovedToSystemSettings); + } + // Per user secure settings that are cloned for the managed profiles of the user. private static final Set<String> sSecureCloneToManagedSettings = new ArraySet<>(); static { @@ -2604,6 +2610,10 @@ public class SettingsProvider extends ContentProvider { if (sGlobalMovedToSecureSettings.contains(name)) { table = TABLE_SECURE; } + + if (sGlobalMovedToSystemSettings.contains(name)) { + table = TABLE_SYSTEM; + } } return table; @@ -3594,7 +3604,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 206; + private static final int SETTINGS_VERSION = 207; private final int mUserId; @@ -4611,18 +4621,7 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 174) { // Version 174: Set the default value for Global Settings: APPLY_RAMPING_RINGER - - final SettingsState globalSettings = getGlobalSettingsLocked(); - - Setting currentRampingRingerSetting = globalSettings.getSettingLocked( - Settings.Global.APPLY_RAMPING_RINGER); - if (currentRampingRingerSetting.isNull()) { - globalSettings.insertSettingOverrideableByRestoreLocked( - Settings.Global.APPLY_RAMPING_RINGER, - getContext().getResources().getBoolean( - R.bool.def_apply_ramping_ringer) ? "1" : "0", null, - true, SettingsState.SYSTEM_PACKAGE_NAME); - } + // Removed. Moved APPLY_RAMPING_RINGER to System Settings, set in version 206. currentVersion = 175; } @@ -5435,6 +5434,34 @@ public class SettingsProvider extends ContentProvider { currentVersion = 206; } + if (currentVersion == 206) { + // Version 206: APPLY_RAMPING_RINGER moved to System settings. Use the old value + // for the newly inserted system setting and keep it to be restored to other + // users. Set default value if global value is not set. + final SettingsState systemSettings = getSystemSettingsLocked(userId); + Setting globalValue = getGlobalSettingsLocked() + .getSettingLocked(Global.APPLY_RAMPING_RINGER); + Setting currentValue = systemSettings + .getSettingLocked(Settings.System.APPLY_RAMPING_RINGER); + if (currentValue.isNull()) { + if (!globalValue.isNull()) { + // Recover settings from Global. + systemSettings.insertSettingOverrideableByRestoreLocked( + Settings.System.APPLY_RAMPING_RINGER, globalValue.getValue(), + globalValue.getTag(), globalValue.isDefaultFromSystem(), + SettingsState.SYSTEM_PACKAGE_NAME); + } else { + // Set default value. + systemSettings.insertSettingOverrideableByRestoreLocked( + Settings.System.APPLY_RAMPING_RINGER, + getContext().getResources().getBoolean( + R.bool.def_apply_ramping_ringer) ? "1" : "0", + null /* tag */, true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + currentVersion = 207; + } // vXXX: Add new settings above this point. diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 4aee1641fc63..aa6661b2514c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -345,7 +345,6 @@ final class SettingsState { } // The settings provider must hold its lock when calling here. - @GuardedBy("mLock") public Setting getSettingLocked(String name) { if (TextUtils.isEmpty(name)) { return mNullSetting; @@ -385,7 +384,6 @@ final class SettingsState { } // The settings provider must hold its lock when calling here. - @GuardedBy("mLock") public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag, boolean makeDefault, String packageName) { return insertSettingLocked(name, value, tag, makeDefault, false, packageName, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index f5334fb7e6ea..433aac7abc45 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -205,8 +205,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mAgentUnderTest.mSettingsHelper = settingsHelper; byte[] backupData = generateBackupData(TEST_VALUES); - mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, new HashSet<>(), - Collections.emptySet(), /* blockedSettingsArrayId */ 0, Collections.emptySet(), + mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, + null, null, null, /* blockedSettingsArrayId */ 0, Collections.emptySet(), new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI)))); assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING)); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index abd010db7cee..2b311ee25c31 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -90,7 +90,6 @@ <uses-permission android:name="android.permission.RESTART_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" /> <uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" /> - <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" /> <uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" /> <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> @@ -205,6 +204,7 @@ <uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> + <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" /> <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" /> <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" /> <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" /> @@ -297,9 +297,13 @@ <!-- Permission needed to wipe the device for Test Harness Mode --> <uses-permission android:name="android.permission.ENABLE_TEST_HARNESS_MODE" /> - <!-- Permissions required to test CompanionDeviceManager teses in CTS --> - <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" /> + <!-- Permission needed for CTS test - CompanionDeviceManagerTest --> <uses-permission android:name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" /> + <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" /> + <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" /> + <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" /> + <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" /> + <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 8c7011253c8a..ee9d4301770a 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -150,6 +150,7 @@ public class BugreportProgressService extends Service { // Internal intents used on notification actions. static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL"; static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE"; + static final String INTENT_BUGREPORT_DONE = "android.intent.action.BUGREPORT_DONE"; static final String INTENT_BUGREPORT_INFO_LAUNCH = "android.intent.action.BUGREPORT_INFO_LAUNCH"; static final String INTENT_BUGREPORT_SCREENSHOT = @@ -555,6 +556,8 @@ public class BugreportProgressService extends Service { case INTENT_BUGREPORT_SHARE: shareBugreport(id, (BugreportInfo) intent.getParcelableExtra(EXTRA_INFO)); break; + case INTENT_BUGREPORT_DONE: + maybeShowWarningMessageAndCloseNotification(id); case INTENT_BUGREPORT_CANCEL: cancel(id); break; @@ -809,10 +812,30 @@ public class BugreportProgressService extends Service { } /** - * Finalizes the progress on a given bugreport and cancel its notification. + * Creates a {@link PendingIntent} for a notification action used to show warning about the + * sensitivity of bugreport data and then close bugreport notification. + * + * Note that, the warning message may not be shown if the user has chosen not to see the + * message anymore. */ + private static PendingIntent newBugreportDoneIntent(Context context, BugreportInfo info) { + final Intent intent = new Intent(INTENT_BUGREPORT_DONE); + intent.setClass(context, BugreportProgressService.class); + intent.putExtra(EXTRA_ID, info.id); + return PendingIntent.getService(context, info.id, intent, + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + } + @GuardedBy("mLock") private void stopProgressLocked(int id) { + stopProgressLocked(id, /* cancelNotification */ true); + } + + /** + * Finalizes the progress on a given bugreport and cancel its notification. + */ + @GuardedBy("mLock") + private void stopProgressLocked(int id, boolean cancelNotification) { if (mBugreportInfos.indexOfKey(id) < 0) { Log.w(TAG, "ID not watched: " + id); } else { @@ -821,8 +844,13 @@ public class BugreportProgressService extends Service { } // Must stop foreground service first, otherwise notif.cancel() will fail below. stopForegroundWhenDoneLocked(id); - Log.d(TAG, "stopProgress(" + id + "): cancel notification"); - NotificationManager.from(mContext).cancel(id); + + if (cancelNotification) { + Log.d(TAG, "stopProgress(" + id + "): cancel notification"); + NotificationManager.from(mContext).cancel(id); + } else { + Log.d(TAG, "stopProgress(" + id + ")"); + } stopSelfWhenDoneLocked(); } @@ -1039,7 +1067,8 @@ public class BugreportProgressService extends Service { } /** - * Wraps up bugreport generation and triggers a notification to share the bugreport. + * Wraps up bugreport generation and triggers a notification to either share the bugreport or + * just notify the ending of the bugreport generation, according to the device type. */ private void onBugreportFinished(BugreportInfo info) { if (!TextUtils.isEmpty(info.shareTitle)) { @@ -1054,25 +1083,46 @@ public class BugreportProgressService extends Service { stopForegroundWhenDoneLocked(info.id); } - triggerLocalNotification(mContext, info); - } - - /** - * Responsible for triggering a notification that allows the user to start a "share" intent with - * the bugreport. On watches we have other methods to allow the user to start this intent - * (usually by triggering it on another connected device); we don't need to display the - * notification in this case. - */ - private void triggerLocalNotification(final Context context, final BugreportInfo info) { if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) { Log.e(TAG, "Could not read bugreport file " + info.bugreportFile); - Toast.makeText(context, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show(); + Toast.makeText(mContext, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show(); synchronized (mLock) { stopProgressLocked(info.id); } return; } + if (mIsWatch) { + // Wear wants to send the notification directly and not wait for the user to tap on the + // notification. + triggerShareBugreportAndLocalNotification(info); + } else { + triggerLocalNotification(info); + } + } + + /** + * Responsible for starting the bugerport sharing process and posting a notification which + * shows that the bugreport has been taken and that the sharing process has kicked-off. + */ + private void triggerShareBugreportAndLocalNotification(final BugreportInfo info) { + boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt"); + if (!isPlainText) { + // Already zipped, share it right away. + shareBugreport(info.id, info, /* showWarning */ false, + /* cancelNotificationWhenStoppingProgress */ false); + sendBugreportNotification(info, mTakingScreenshot); + } else { + // Asynchronously zip the file first, then share it. + shareAndPostNotificationForZippedBugreport(info, mTakingScreenshot); + } + } + + /** + * Responsible for triggering a notification that allows the user to start a "share" intent with + * the bugreport. + */ + private void triggerLocalNotification(final BugreportInfo info) { boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt"); if (!isPlainText) { // Already zipped, send it right away. @@ -1083,9 +1133,11 @@ public class BugreportProgressService extends Service { } } - private static Intent buildWarningIntent(Context context, Intent sendIntent) { + private static Intent buildWarningIntent(Context context, @Nullable Intent sendIntent) { final Intent intent = new Intent(context, BugreportWarningActivity.class); - intent.putExtra(Intent.EXTRA_INTENT, sendIntent); + if (sendIntent != null) { + intent.putExtra(Intent.EXTRA_INTENT, sendIntent); + } return intent; } @@ -1163,11 +1215,30 @@ public class BugreportProgressService extends Service { return intent; } + private boolean hasUserDecidedNotToGetWarningMessage() { + return getWarningState(mContext, STATE_UNKNOWN) == STATE_HIDE; + } + + private void maybeShowWarningMessageAndCloseNotification(int id) { + if (!hasUserDecidedNotToGetWarningMessage()) { + Intent warningIntent = buildWarningIntent(mContext, /* sendIntent */ null); + warningIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(warningIntent); + } + NotificationManager.from(mContext).cancel(id); + } + + private void shareBugreport(int id, BugreportInfo sharedInfo) { + shareBugreport(id, sharedInfo, !hasUserDecidedNotToGetWarningMessage(), + /* cancelNotificationWhenStoppingProgress */ true); + } + /** * Shares the bugreport upon user's request by issuing a {@link Intent#ACTION_SEND_MULTIPLE} * intent, but issuing a warning dialog the first time. */ - private void shareBugreport(int id, BugreportInfo sharedInfo) { + private void shareBugreport(int id, BugreportInfo sharedInfo, boolean showWarning, + boolean cancelNotificationWhenStoppingProgress) { MetricsLogger.action(this, MetricsEvent.ACTION_BUGREPORT_NOTIFICATION_ACTION_SHARE); BugreportInfo info; synchronized (mLock) { @@ -1199,7 +1270,7 @@ public class BugreportProgressService extends Service { boolean useChooser = true; // Send through warning dialog by default - if (getWarningState(mContext, STATE_UNKNOWN) != STATE_HIDE) { + if (showWarning) { notifIntent = buildWarningIntent(mContext, sendIntent); // No need to show a chooser in this case. useChooser = false; @@ -1216,7 +1287,7 @@ public class BugreportProgressService extends Service { } synchronized (mLock) { // ... and stop watching this process. - stopProgressLocked(id); + stopProgressLocked(id, cancelNotificationWhenStoppingProgress); } } @@ -1240,12 +1311,6 @@ public class BugreportProgressService extends Service { // Since adding the details can take a while, do it before notifying user. addDetailsToZipFile(info); - final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE); - shareIntent.setClass(mContext, BugreportProgressService.class); - shareIntent.setAction(INTENT_BUGREPORT_SHARE); - shareIntent.putExtra(EXTRA_ID, info.id); - shareIntent.putExtra(EXTRA_INFO, info); - String content; content = takingScreenshot ? mContext.getString(R.string.bugreport_finished_pending_screenshot_text) @@ -1263,11 +1328,32 @@ public class BugreportProgressService extends Service { final Notification.Builder builder = newBaseNotification(mContext) .setContentTitle(title) .setTicker(title) - .setContentText(content) - .setContentIntent(PendingIntent.getService(mContext, info.id, shareIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) .setOnlyAlertOnce(false) - .setDeleteIntent(newCancelIntent(mContext, info)); + .setContentText(content); + + if (!mIsWatch) { + final Intent shareIntent = new Intent(INTENT_BUGREPORT_SHARE); + shareIntent.setClass(mContext, BugreportProgressService.class); + shareIntent.setAction(INTENT_BUGREPORT_SHARE); + shareIntent.putExtra(EXTRA_ID, info.id); + shareIntent.putExtra(EXTRA_INFO, info); + + builder.setContentIntent(PendingIntent.getService(mContext, info.id, shareIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .setDeleteIntent(newCancelIntent(mContext, info)); + } else { + // Device is a watch. + if (hasUserDecidedNotToGetWarningMessage()) { + // No action button needed for the notification. User can swipe to dimiss. + builder.setActions(new Action[0]); + } else { + // Add action button to lead user to the warning screen. + builder.setActions( + new Action.Builder( + null, mContext.getString(R.string.bugreport_info_action), + newBugreportDoneIntent(mContext, info)).build()); + } + } if (!TextUtils.isEmpty(info.getName())) { builder.setSubText(info.getName()); @@ -1327,6 +1413,24 @@ public class BugreportProgressService extends Service { } /** + * Zips a bugreport, shares it, and sends for it a bugreport notification. + */ + private void shareAndPostNotificationForZippedBugreport(final BugreportInfo info, + final boolean takingScreenshot) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + Looper.prepare(); + zipBugreport(info); + shareBugreport(info.id, info, /* showWarning */ false, + /* cancelNotificationWhenStoppingProgress */ false); + sendBugreportNotification(info, mTakingScreenshot); + return null; + } + }.execute(); + } + + /** * Zips a bugreport file, returning the path to the new file (or to the * original in case of failure). */ diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java index ecd1369817b4..a44e23603f52 100644 --- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java +++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java @@ -54,9 +54,11 @@ public class BugreportWarningActivity extends AlertActivity mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT); - // We need to touch the extras to unpack them so they get migrated to - // ClipData correctly. - mSendIntent.hasExtra(Intent.EXTRA_STREAM); + if (mSendIntent != null) { + // We need to touch the extras to unpack them so they get migrated to + // ClipData correctly. + mSendIntent.hasExtra(Intent.EXTRA_STREAM); + } final AlertController.AlertParams ap = mAlertParams; ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null); @@ -84,7 +86,9 @@ public class BugreportWarningActivity extends AlertActivity if (which == AlertDialog.BUTTON_POSITIVE) { // Remember confirm state, and launch target setWarningState(this, mConfirmRepeat.isChecked() ? STATE_HIDE : STATE_SHOW); - sendShareIntent(this, mSendIntent); + if (mSendIntent != null) { + sendShareIntent(this, mSendIntent); + } } finish(); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 9aad2783ba2d..e5726b08aff4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -27,7 +27,6 @@ import android.os.Looper import android.util.Log import android.util.MathUtils import android.view.GhostView -import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnPreDrawListener @@ -87,10 +86,11 @@ class DialogLaunchAnimator( // If the parent of the view we are launching from is the background of some other animated // dialog, then this means the caller intent is to launch a dialog from another dialog. In // this case, we also animate the parent (which is the dialog background). - val animatedParent = openedDialogs - .firstOrNull { it.dialogContentParent == view.parent } - val parentHostDialog = animatedParent?.hostDialog - val animateFrom = animatedParent?.dialogContentParent ?: view + val animatedParent = openedDialogs.firstOrNull { + it.dialogContentWithBackground == view || it.dialogContentWithBackground == view.parent + } + val dialogContentWithBackground = animatedParent?.dialogContentWithBackground + val animateFrom = dialogContentWithBackground ?: view // Make sure we don't run the launch animation from the same view twice at the same time. if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { @@ -109,7 +109,7 @@ class DialogLaunchAnimator( onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog, animateBackgroundBoundsChange, - openedDialogs.firstOrNull { it.hostDialog == parentHostDialog } + animatedParent ) val hostDialog = animatedDialog.hostDialog openedDialogs.add(animatedDialog) @@ -288,13 +288,12 @@ private class AnimatedDialog( private val hostDialogRoot = FrameLayout(context) /** - * The parent of the original dialog content view, that serves as a fake window that will have - * the same size as the original dialog window and to which we will set the original dialog - * window background. + * The dialog content with its background. When animating a fullscreen dialog, this is just the + * first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen) + * dialog, this is an additional view that serves as a fake window that will have the same size + * as the original dialog window and to which we will set the original dialog window background. */ - val dialogContentParent = FrameLayout(context).apply { - id = DIALOG_CONTENT_PARENT_ID - } + var dialogContentWithBackground: ViewGroup? = null /** * The background color of [originalDialogView], taking into consideration the [originalDialog] @@ -451,59 +450,87 @@ private class AnimatedDialog( hostDialogRoot.setOnClickListener { hostDialog.dismiss() } dialogView.isClickable = true - // Set the background of the window dialog to the dialog itself. - // TODO(b/193634619): Support dialog windows without background. - // TODO(b/193634619): Support dialog whose background comes from the content view instead of - // the window. - val typedArray = - originalDialog.context.obtainStyledAttributes(com.android.internal.R.styleable.Window) - val backgroundRes = - typedArray.getResourceId(com.android.internal.R.styleable.Window_windowBackground, 0) - typedArray.recycle() - if (backgroundRes == 0) { - throw IllegalStateException("Dialogs with no backgrounds on window are not supported") - } + // Remove the original dialog view from its parent. + (dialogView.parent as? ViewGroup)?.removeView(dialogView) - // Add a parent view to the original dialog view to which we will set the original dialog - // window background. This View serves as a fake window with background, so that we are sure - // that we don't override the dialog view paddings with the window background that usually - // has insets. - dialogContentParent.setBackgroundResource(backgroundRes) - hostDialogRoot.addView( - dialogContentParent, - - // We give it the size of its original dialog window. - FrameLayout.LayoutParams( - originalDialog.window.attributes.width, - originalDialog.window.attributes.height, - Gravity.CENTER + val originalDialogWindow = originalDialog.window!! + val isOriginalWindowFullScreen = + originalDialogWindow.attributes.width == ViewGroup.LayoutParams.MATCH_PARENT && + originalDialogWindow.attributes.height == ViewGroup.LayoutParams.MATCH_PARENT + if (isOriginalWindowFullScreen) { + // If the original dialog window is fullscreen, then we look for the first ViewGroup + // that has a background and animate towards that ViewGroup given that this is probably + // what represents the actual dialog view. + dialogContentWithBackground = findFirstViewGroupWithBackground(dialogView) + ?: throw IllegalStateException("Unable to find ViewGroup with background") + + hostDialogRoot.addView( + dialogView, + + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) ) - ) + } else { + // Add a parent view to the original dialog view to which we will set the original + // dialog window background. This View serves as a fake window with background, so that + // we are sure that we don't override the original dialog content view paddings with the + // window background that usually has insets. + dialogContentWithBackground = FrameLayout(context).apply { + id = DIALOG_CONTENT_PARENT_ID + + // TODO(b/193634619): Support dialog windows without background. + background = originalDialogWindow.decorView?.background + ?: throw IllegalStateException( + "Dialogs with no backgrounds on window are not supported") + + addView( + dialogView, + + // It should match its parent size, which is sized the same as the original + // dialog window. + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + } + + // Add the parent (that has the background) to the host window. + hostDialogRoot.addView( + dialogContentWithBackground, - // Make the dialog view parent invisible for now, to make sure it's not drawn yet. - dialogContentParent.visibility = View.INVISIBLE + // We give it the size and gravity of its original dialog window. + FrameLayout.LayoutParams( + originalDialogWindow.attributes.width, + originalDialogWindow.attributes.height, + originalDialogWindow.attributes.gravity + ) + ) + } + + val dialogContentWithBackground = this.dialogContentWithBackground!! + + // Make the dialog and its background invisible for now, to make sure it's not drawn yet. + dialogContentWithBackground.visibility = View.INVISIBLE - val background = dialogContentParent.background!! + val background = dialogContentWithBackground.background!! originalDialogBackgroundColor = GhostedViewLaunchAnimatorController.findGradientDrawable(background) ?.color ?.defaultColor ?: Color.BLACK - // Add the dialog view to its parent (that has the original window background). - (dialogView.parent as? ViewGroup)?.removeView(dialogView) - dialogContentParent.addView( - dialogView, - - // It should match its parent size, which is sized the same as the original dialog - // window. - FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) + if (isOriginalWindowFullScreen) { + // If the original window is full screen, the ViewGroup with background might already be + // correctly laid out. Make sure we relayout and that the layout listener below is still + // called. + dialogContentWithBackground.layout(0, 0, 0, 0) + dialogContentWithBackground.requestLayout() + } // Start the animation when the dialog is laid out in the center of the host dialog. - dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { + dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view: View, left: Int, @@ -515,7 +542,7 @@ private class AnimatedDialog( oldRight: Int, oldBottom: Int ) { - dialogContentParent.removeOnLayoutChangeListener(this) + dialogContentWithBackground.removeOnLayoutChangeListener(this) isOriginalDialogViewLaidOut = true maybeStartLaunchAnimation() @@ -523,6 +550,25 @@ private class AnimatedDialog( }) } + private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { + if (view !is ViewGroup) { + return null + } + + if (view.background != null) { + return view + } + + for (i in 0 until view.childCount) { + val match = findFirstViewGroupWithBackground(view.getChildAt(i)) + if (match != null) { + return match + } + } + + return null + } + fun onOriginalDialogSizeChanged() { // The dialog is the single child of the root. if (hostDialogRoot.childCount != 1) { @@ -571,7 +617,8 @@ private class AnimatedDialog( // at the end of the launch animation, because the lauch animation already correctly // handles bounds changes. if (backgroundLayoutListener != null) { - dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground!! + .addOnLayoutChangeListener(backgroundLayoutListener) } } ) @@ -638,10 +685,12 @@ private class AnimatedDialog( (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false) touchSurface.visibility = View.VISIBLE - dialogContentParent.visibility = View.INVISIBLE + val dialogContentWithBackground = this.dialogContentWithBackground!! + dialogContentWithBackground.visibility = View.INVISIBLE if (backgroundLayoutListener != null) { - dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground + .removeOnLayoutChangeListener(backgroundLayoutListener) } // The animated ghost was just removed. We create a temporary ghost that will be @@ -674,8 +723,8 @@ private class AnimatedDialog( ) { // Create 2 ghost controllers to animate both the dialog and the touch surface in the host // dialog. - val startView = if (isLaunching) touchSurface else dialogContentParent - val endView = if (isLaunching) dialogContentParent else touchSurface + val startView = if (isLaunching) touchSurface else dialogContentWithBackground!! + val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface val startViewController = GhostedViewLaunchAnimatorController(startView) val endViewController = GhostedViewLaunchAnimatorController(endView) startViewController.launchContainer = hostDialogRoot @@ -736,7 +785,9 @@ private class AnimatedDialog( } private fun shouldAnimateDialogIntoView(): Boolean { - if (exitAnimationDisabled) { + // Don't animate if the dialog was previously hidden using hide() (either on the host dialog + // or on the original dialog) or if we disabled the exit animation. + if (exitAnimationDisabled || !hostDialog.isShowing) { return false } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 3ccf5e4fbdd0..5fec4cccda6c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -53,8 +53,12 @@ open class GhostedViewLaunchAnimatorController( private val ghostedView: View, /** The [InteractionJankMonitor.CujType] associated to this animation. */ - private val cujType: Int? = null + private val cujType: Int? = null, + private var interactionJankMonitor: InteractionJankMonitor? = null ) : ActivityLaunchAnimator.Controller { + + constructor(view: View, type: Int) : this(view, type, null) + /** The container to which we will add the ghost view and expanding background. */ override var launchContainer = ghostedView.rootView as ViewGroup private val launchContainerOverlay: ViewGroupOverlay @@ -170,7 +174,7 @@ open class GhostedViewLaunchAnimatorController( val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX matrix.getValues(initialGhostViewMatrixValues) - cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) } + cujType?.let { interactionJankMonitor?.begin(ghostedView, it) } } override fun onLaunchAnimationProgress( @@ -251,7 +255,7 @@ open class GhostedViewLaunchAnimatorController( return } - cujType?.let { InteractionJankMonitor.getInstance().end(it) } + cujType?.let { interactionJankMonitor?.end(it) } backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha diff --git a/packages/SystemUI/docs/dialogs.md b/packages/SystemUI/docs/dialogs.md new file mode 100644 index 000000000000..e5b4edde39e3 --- /dev/null +++ b/packages/SystemUI/docs/dialogs.md @@ -0,0 +1,79 @@ +# Dialogs in SystemUI + +### Creating a dialog + +### Styling + +In order to have uniform styling in dialogs, use [SystemUIDialog][1] with its default theme. +If not possible, use [AlertDialog][2] with the SystemUI theme `R.style.Theme_SystemUI_Dialog`. +If needed, consider extending this theme instead of creating a new one. + +### Setting the internal elements + +The internal elements of the dialog are laid out using the following resources: + +* [@layout/alert_dialog_systemui][3] +* [@layout/alert_dialog_title_systemui][4] +* [@layout/alert_dialog_button_bar_systemui][5] + +Use the default components of the layout by calling the appropriate setters (in the dialog or +[AlertDialog.Builder][2]). The supported styled setters are: + +* `setIcon`: tinted using `attr/colorAccentPrimaryVariant`. +* `setTitle`: this will use `R.style.TextAppearance_Dialog_Title`. +* `setMessage`: this will use `R.style.TextAppearance_Dialog_Body_Message`. +* `SystemUIDialog.set<Positive|Negative|Neutral>Button` or `AlertDialog.setButton`: this will use + the following styles for buttons. + * `R.style.Widget_Dialog_Button` for the positive button. + * `R.style.Widget_Dialog_Button_BorderButton` for the negative and neutral buttons. + + If needed to use the same style for all three buttons, the style attributes + `?android:attr/buttonBar<Positive|NegativeNeutral>Button` can be overriden in a theme that extends + from `R.style.Theme_SystemUI_Dialog`. +* `setView`: to set a custom view in the dialog instead of using `setMessage`. + +Using `setContentView` is discouraged as this replaces the content completely. + +All these calls should be made before `Dialog#create` or `Dialog#show` (which internally calls +`create`) are called, as that's when the content is installed. + +## Showing the dialog + +When showing a dialog triggered by clicking on a `View`, you should use [DialogLaunchAnimator][6] to +nicely animate the dialog from/to that `View`, instead of calling `Dialog.show`. + +This animator provides the following methods: + +* `showFromView`: animates the dialog show from a view , and the dialog dismissal/cancel/hide to the + same view. +* `showFromDialog`: animates the dialog show from a currently showing dialog, and the dialog + dismissal/cancel/hide back to that dialog. The originating dialog must have been shown using + `DialogLaunchAnimator`. +* `dismissStack`: dismisses a stack of dialogs that were launched using `showFromDialog` animating + the top-most dialog back into the view that was used in the initial `showFromView`. + +## Example + +Here's a short code snippet with an example on how to use the guidelines. + +```kotlin +val dialog = SystemUIDialog(context).apply { + setIcon(R.drawable.my_icon) + setTitle(context.getString(R.string.title)) + setMessage(context.getString(R.string.message)) + // Alternatively to setMessage: + // val content = LayoutManager.from(context).inflate(R.layout.content, null) + // setView(content) + setPositiveButton(R.string.positive_button_text, ::onPositiveButton) + setNegativeButton(R.string.negative_button_text, ::onNegativeButton) + setNeutralButton(R.string.neutral_button_text, ::onNeutralButton) +} +dialogLaunchAnimator.showFromView(dialog, view) +``` + +[1]: /packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +[2]: /core/java/android/app/AlertDialog.java +[3]: /packages/SystemUI/res/layout/alert_dialog_systemui.xml +[4]: /packages/SystemUI/res/layout/alert_dialog_title_systemui.xml +[5]: /packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml +[6]: /packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
\ No newline at end of file diff --git a/packages/SystemUI/docs/keyguard/bouncer.md b/packages/SystemUI/docs/keyguard/bouncer.md index 51f851608a34..4bfe7340db30 100644 --- a/packages/SystemUI/docs/keyguard/bouncer.md +++ b/packages/SystemUI/docs/keyguard/bouncer.md @@ -2,15 +2,24 @@ [KeyguardBouncer][1] is the component responsible for displaying the security method set by the user (password, PIN, pattern) as well as SIM-related security methods, allowing the user to unlock the device or SIM. +## Supported States + +1. Phone, portrait mode - The default and typically only way to view the bouncer. Screen cannot rotate. +1. Phone, landscape - Can only get into this state via lockscreen activities. Launch camera, rotate to landscape, tap lock icon is one example. +1. Foldables - Both landscape and portrait are supported. In landscape, the bouncer can appear on either of the hinge and can be dragged to the other side. Also refered to as "OneHandedMode in [KeyguardSecurityContainerController][3] +1. Tablets - The bouncer is supplemented with user icons and a multi-user switcher, when available. + ## Components The bouncer contains a hierarchy of controllers/views to render the user's security method and to manage the authentication attempts. 1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility. 1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item. - 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use + 1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, determines the correct security view layout, which may include a user switcher or enable one-handed use. 1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc. +Fun fact: Naming comes from the concept of a bouncer at a bar or nightclub, who prevent troublemakers from entering or eject them from the premises. + [1]: /frameworks/base/packages/SystemUI/com/android/systemui/statusbar/phone/KeyguardBouncer [2]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardHostViewController [3]: /frameworks/base/packages/SystemUI/com/android/keyguard/KeyguardSecurityContainerController diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index 07c2ac426391..3517ebae10bf 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -36,4 +36,42 @@ -keep class com.android.systemui.dagger.GlobalRootComponent { *; } -keep class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { *; } -keep class com.android.systemui.dagger.Dagger** { *; } --keep class com.android.systemui.tv.Dagger** { *; }
\ No newline at end of file +-keep class com.android.systemui.tv.Dagger** { *; } + +# Removes runtime checks added through Kotlin to JVM code genereration to +# avoid linear growth as more Kotlin code is converted / added to the codebase. +# These checks are generally applied to Java platform types (values returned +# from Java code that don't have nullness annotations), but we remove them to +# avoid code size increases. +# +# See also https://kotlinlang.org/docs/reference/java-interop.html +# +# TODO(b/199941987): Consider standardizing these rules in a central place as +# Kotlin gains adoption with other platform targets. +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + # Remove check for method parameters being null + static void checkParameterIsNotNull(java.lang.Object, java.lang.String); + + # When a Java platform type is returned and passed to Kotlin NonNull method, + # remove the null check + static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String); + static void checkNotNullExpressionValue(java.lang.Object, java.lang.String); + + # Remove check that final value returned from method is null, if passing + # back Java platform type. + static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String); + static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String); + + # Null check for accessing a field from a parent class written in Java. + static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String); + static void checkFieldIsNotNull(java.lang.Object, java.lang.String); + + # Removes code generated from !! operator which converts Nullable type to + # NonNull type. These would throw an NPE immediate after on access. + static void checkNotNull(java.lang.Object, java.lang.String); + static void checkNotNullParameter(java.lang.Object, java.lang.String); + + # Removes lateinit var check being used before being set. Check is applied + # on every field access without this. + static void throwUninitializedPropertyAccessException(java.lang.String); +} diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml index e09bf7e37ed0..625ce1fe2f1d 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml @@ -16,5 +16,7 @@ --> <resources> + <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to + switch sides --> <bool name="can_use_one_handed_bouncer">true</bool> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml index e09bf7e37ed0..4daa6488a660 100644 --- a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml +++ b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml @@ -16,5 +16,11 @@ --> <resources> + <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to + switch sides --> <bool name="can_use_one_handed_bouncer">true</bool> + + <!-- Will display the bouncer on one side of the display, and the current user icon and + user switcher on the other side --> + <bool name="bouncer_display_user_switcher">true</bool> </resources> diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index 6176f7c1dd0a..6194aa095610 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,5 +22,11 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] --> <bool name="config_disableMenuKeyInLockScreen">false</bool> + <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to + switch sides --> <bool name="can_use_one_handed_bouncer">false</bool> + <!-- Will display the bouncer on one side of the display, and the current user icon and + user switcher on the other side --> + <bool name="bouncer_display_user_switcher">false</bool> + </resources> diff --git a/packages/SystemUI/res/drawable/ic_list.xml b/packages/SystemUI/res/drawable/ic_list.xml new file mode 100644 index 000000000000..7ef52991234a --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_list.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Remove when Fgs manager tile is removed --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M2,4h4v4h-4z" + android:fillColor="#000000"/> + <path + android:pathData="M8,4h14v4h-14z" + android:fillColor="#000000"/> + <path + android:pathData="M2,10h4v4h-4z" + android:fillColor="#000000"/> + <path + android:pathData="M8,10h14v4h-14z" + android:fillColor="#000000"/> + <path + android:pathData="M2,16h4v4h-4z" + android:fillColor="#000000"/> + <path + android:pathData="M8,16h14v4h-14z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml new file mode 100644 index 000000000000..3a228d512fa4 --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@android:id/background"> + <shape> + <corners android:radius="28dp" /> + <solid android:color="@android:color/transparent" /> + <size + android:height="64dp"/> + </shape> + </item> + <item android:id="@android:id/progress"> + <clip> + <shape> + <corners + android:radius="28dp"/> + <size + android:height="64dp"/> + <solid android:color="@*android:color/system_accent1_200" /> + </shape> + </clip> + </item> +</layer-list> diff --git a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml new file mode 100644 index 000000000000..86f8b42b971c --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml @@ -0,0 +1,29 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <stroke + android:color="@android:color/transparent" + android:width="1dp"/> + <corners android:radius="20dp"/> + <padding + android:left="@dimen/media_output_dialog_button_padding_horizontal" + android:right="@dimen/media_output_dialog_button_padding_horizontal" + android:top="@dimen/media_output_dialog_button_padding_vertical" + android:bottom="@dimen/media_output_dialog_button_padding_vertical" /> + <solid android:color="?androidprv:attr/colorAccentPrimaryVariant" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_item_background.xml b/packages/SystemUI/res/drawable/media_output_item_background.xml new file mode 100644 index 000000000000..8c23659eb461 --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_item_background.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners + android:radius="16dp"/> + <solid android:color="?androidprv:attr/colorAccentSecondary" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_item_background_active.xml b/packages/SystemUI/res/drawable/media_output_item_background_active.xml new file mode 100644 index 000000000000..09dee95ce7bd --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_item_background_active.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners + android:radius="50dp"/> + <solid android:color="?androidprv:attr/colorAccentSecondary" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/media_output_status_check.xml b/packages/SystemUI/res/drawable/media_output_status_check.xml new file mode 100644 index 000000000000..4e17e48b07ff --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_status_check.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" + android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/media_output_status_failed.xml b/packages/SystemUI/res/drawable/media_output_status_failed.xml new file mode 100644 index 000000000000..81fb92c8161a --- /dev/null +++ b/packages/SystemUI/res/drawable/media_output_status_failed.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" + android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml index 1a128dfe8b10..14cb1de9fa2d 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -16,8 +16,8 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:insetTop="@dimen/qs_dialog_button_vertical_inset" - android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> @@ -29,10 +29,10 @@ <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> <solid android:color="?androidprv:attr/colorAccentPrimary"/> - <padding android:left="@dimen/qs_dialog_button_horizontal_padding" - android:top="@dimen/qs_dialog_button_vertical_padding" - android:right="@dimen/qs_dialog_button_horizontal_padding" - android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml index 467c20f3ffcd..665b4564ebf1 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -16,8 +16,8 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:insetTop="@dimen/qs_dialog_button_vertical_inset" - android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + android:insetTop="@dimen/dialog_button_vertical_inset" + android:insetBottom="@dimen/dialog_button_vertical_inset"> <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> @@ -32,10 +32,10 @@ <stroke android:color="?androidprv:attr/colorAccentPrimary" android:width="1dp" /> - <padding android:left="@dimen/qs_dialog_button_horizontal_padding" - android:top="@dimen/qs_dialog_button_vertical_padding" - android:right="@dimen/qs_dialog_button_horizontal_padding" - android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + <padding android:left="@dimen/dialog_button_horizontal_padding" + android:top="@dimen/dialog_button_vertical_padding" + android:right="@dimen/dialog_button_horizontal_padding" + android:bottom="@dimen/dialog_button_vertical_padding"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml new file mode 100644 index 000000000000..a3e289a42d05 --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/buttonPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scrollbarAlwaysDrawVerticalTrack="true" + android:scrollIndicators="top|bottom" + android:fillViewport="true" + android:paddingTop="@dimen/dialog_button_bar_top_padding" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + android:paddingBottom="@dimen/dialog_bottom_padding" + style="?android:attr/buttonBarStyle"> + <com.android.internal.widget.ButtonBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layoutDirection="locale" + android:orientation="horizontal" + android:gravity="bottom"> + + <Button + android:id="@android:id/button3" + style="?android:attr/buttonBarNeutralButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <Space + android:id="@*android:id/spacer" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" + android:visibility="invisible" /> + + <Button + android:id="@android:id/button2" + style="?android:attr/buttonBarNegativeButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <Button + android:id="@android:id/button1" + style="?android:attr/buttonBarPositiveButtonStyle" + android:layout_marginStart="8dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </com.android.internal.widget.ButtonBarLayout> +</ScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml new file mode 100644 index 000000000000..f280cbd16a0f --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.internal.widget.AlertDialogLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/parentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal|top" + android:orientation="vertical" + android:paddingTop="@dimen/dialog_top_padding" + > + + <include layout="@layout/alert_dialog_title_systemui" /> + + <FrameLayout + android:id="@*android:id/contentPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > + + <ScrollView + android:id="@*android:id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <Space + android:id="@*android:id/textSpacerNoTitle" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.Dialog.Body.Message" /> + + <Space + android:id="@*android:id/textSpacerNoButtons" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="6dp" /> + </LinearLayout> + </ScrollView> + </FrameLayout> + + <FrameLayout + android:id="@*android:id/customPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > + + <FrameLayout + android:id="@*android:id/custom" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </FrameLayout> + + <include + android:layout_width="match_parent" + android:layout_height="wrap_content" + layout="@layout/alert_dialog_button_bar_systemui" /> + +</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml new file mode 100644 index 000000000000..480ba001fae1 --- /dev/null +++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:id="@*android:id/topPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" +> + + <!-- If the client uses a customTitle, it will be added here. --> + + <LinearLayout + android:id="@*android:id/title_template" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal|top"> + + <ImageView + android:id="@*android:id/icon" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginBottom="16dp" + android:scaleType="fitCenter" + android:src="@null" + android:tint="?androidprv:attr/colorAccentPrimaryVariant" + /> + + <com.android.internal.widget.DialogTitle + android:id="@*android:id/alertTitle" + android:singleLine="true" + android:ellipsize="end" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + style="@style/TextAppearance.Dialog.Title" /> + </LinearLayout> + + <Space + android:id="@*android:id/titleDividerNoCustom" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="0dp" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml new file mode 100644 index 000000000000..d034f4e512a3 --- /dev/null +++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="32dp" + android:orientation="horizontal" + android:gravity="center"> + + <ImageView + android:id="@+id/fgs_manager_app_item_icon" + android:layout_width="28dp" + android:layout_height="28dp" + android:layout_marginRight="12dp" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:orientation="vertical"> + <TextView + android:id="@+id/fgs_manager_app_item_label" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + style="@style/TextAppearance.Dialog.Body" /> + <TextView + android:id="@+id/fgs_manager_app_item_duration" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="start" + style="@style/FgsManagerAppDuration" /> + </LinearLayout> + + <Button + android:id="@+id/fgs_manager_app_item_stop_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/fgs_manager_app_item_stop_button_label" + android:layout_marginLeft="12dp" + style="?android:attr/buttonBarNeutralButtonStyle" /> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 86e2661f9534..4a5b637a05d9 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -156,6 +156,14 @@ style="@style/InternetDialog.NetworkSummary"/> </LinearLayout> + <View + android:id="@+id/mobile_toggle_divider" + android:layout_width="1dp" + android:layout_height="28dp" + android:layout_marginEnd="16dp" + android:layout_gravity="center_vertical" + android:background="?android:attr/textColorSecondary"/> + <FrameLayout android:layout_width="@dimen/settingslib_switch_track_width" android:layout_height="48dp" @@ -177,7 +185,7 @@ <LinearLayout android:id="@+id/turn_on_wifi_layout" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:gravity="center" android:clickable="false" android:focusable="false"> @@ -219,7 +227,7 @@ <LinearLayout android:id="@+id/wifi_connected_layout" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:paddingStart="20dp" android:paddingEnd="24dp" android:background="@drawable/settingslib_switch_bar_bg_on" @@ -241,7 +249,7 @@ android:orientation="vertical" android:clickable="false" android:layout_width="wrap_content" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:layout_marginEnd="30dp" android:layout_weight="1" android:gravity="start|center_vertical"> @@ -367,8 +375,9 @@ android:id="@+id/done_layout" android:layout_width="67dp" android:layout_height="48dp" + android:layout_marginTop="8dp" android:layout_marginEnd="24dp" - android:layout_marginBottom="40dp" + android:layout_marginBottom="34dp" android:layout_gravity="end|center_vertical" android:clickable="true" android:focusable="true"> diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml index 868331ec830f..f6a213662a18 100644 --- a/packages/SystemUI/res/layout/internet_list_item.xml +++ b/packages/SystemUI/res/layout/internet_list_item.xml @@ -25,7 +25,7 @@ <LinearLayout android:id="@+id/wifi_list" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:paddingStart="20dp" android:paddingEnd="24dp"> <FrameLayout @@ -45,7 +45,7 @@ android:orientation="vertical" android:clickable="false" android:layout_width="wrap_content" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:layout_marginEnd="30dp" android:layout_weight="1" android:gravity="start|center_vertical"> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index cfba83b9a3b3..8f8993f3c8d9 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -17,7 +17,6 @@ <com.android.systemui.statusbar.phone.KeyguardBottomAreaView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:id="@+id/keyguard_bottom_area" android:layout_height="match_parent" android:layout_width="match_parent" @@ -128,7 +127,8 @@ android:layout_height="match_parent"> <include layout="@layout/keyguard_bottom_area_overlay" /> - </FrameLayout> + <include layout="@layout/ambient_indication" + android:id="@+id/ambient_indication_container" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml index a64ef3ea1cc6..4e2b4a64b1bc 100644 --- a/packages/SystemUI/res/layout/media_output_dialog.xml +++ b/packages/SystemUI/res/layout/media_output_dialog.xml @@ -24,24 +24,30 @@ <LinearLayout android:layout_width="match_parent" - android:layout_height="96dp" + android:layout_height="wrap_content" android:gravity="start|center_vertical" android:paddingStart="16dp" + android:paddingTop="16dp" + android:paddingEnd="16dp" + android:paddingBottom="24dp" android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" - android:layout_width="48dp" - android:layout_height="48dp" + android:layout_width="72dp" + android:layout_height="72dp" android:importantForAccessibility="no"/> <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingStart="16dp" - android:paddingTop="20dp" - android:paddingBottom="24dp" - android:paddingEnd="24dp" + android:layout_height="wrap_content" + android:paddingStart="12dp" android:orientation="vertical"> + <ImageView + android:id="@+id/app_source_icon" + android:layout_width="20dp" + android:layout_height="20dp" + android:gravity="center_vertical" + android:importantForAccessibility="no"/> <TextView android:id="@+id/header_title" android:layout_width="wrap_content" @@ -51,7 +57,7 @@ android:maxLines="1" android:textColor="?android:attr/textColorPrimary" android:fontFamily="@*android:string/config_headlineFontFamilyMedium" - android:textSize="20sp"/> + android:textSize="16sp"/> <TextView android:id="@+id/header_subtitle" android:layout_width="wrap_content" @@ -59,17 +65,12 @@ android:gravity="center_vertical" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorTertiary" + android:textColor="?android:attr/textColorSecondary" android:fontFamily="roboto-regular" - android:textSize="16sp"/> + android:textSize="14sp"/> </LinearLayout> </LinearLayout> - <View - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="?android:attr/listDivider"/> - <LinearLayout android:id="@+id/device_list" android:layout_width="match_parent" @@ -88,10 +89,10 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:layout_marginStart="24dp" - android:layout_marginBottom="18dp" - android:layout_marginEnd="24dp" + android:layout_marginTop="4dp" + android:layout_marginStart="16dp" + android:layout_marginBottom="24dp" + android:layout_marginEnd="16dp" android:orientation="horizontal"> <Button @@ -110,7 +111,7 @@ <Button android:id="@+id/done" - style="@style/MediaOutputRoundedOutlinedButton" + style="@style/MediaOutputRoundedButton" android:layout_width="wrap_content" android:layout_height="36dp" android:minWidth="0dp" diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml index a5a7efa24f47..2f5f8d6795c0 100644 --- a/packages/SystemUI/res/layout/media_output_list_item.xml +++ b/packages/SystemUI/res/layout/media_output_list_item.xml @@ -17,26 +17,43 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/device_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <FrameLayout android:layout_width="match_parent" - android:layout_height="88dp" - android:paddingTop="24dp" - android:paddingBottom="16dp" - android:paddingStart="24dp" - android:paddingEnd="8dp"> + android:layout_height="64dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="12dp"> + <FrameLayout + android:id="@+id/item_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/media_output_item_background" + android:layout_gravity="center_vertical|start"> + <SeekBar + android:id="@+id/volume_seekbar" + android:splitTrack="false" + android:visibility="gone" + android:paddingStart="0dp" + android:paddingEnd="0dp" + android:progressDrawable="@drawable/media_output_dialog_seekbar_background" + android:thumb="@null" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + </FrameLayout> <FrameLayout - android:layout_width="48dp" - android:layout_height="48dp" + android:layout_width="56dp" + android:layout_height="64dp" android:layout_gravity="center_vertical|start"> <ImageView android:id="@+id/title_icon" - android:layout_width="48dp" - android:layout_height="48dp" + android:layout_width="24dp" + android:layout_height="24dp" android:layout_gravity="center"/> </FrameLayout> @@ -45,55 +62,46 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" - android:layout_marginStart="64dp" + android:layout_marginStart="56dp" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/colorAccentPrimaryVariant" android:textSize="16sp"/> <RelativeLayout android:id="@+id/two_line_layout" android:layout_width="wrap_content" + android:layout_gravity="center_vertical|start" android:layout_height="48dp" - android:layout_marginStart="48dp"> + android:layout_marginStart="56dp"> <TextView android:id="@+id/two_line_title" + android:gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="48dp" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/colorAccentPrimaryVariant" android:textSize="16sp"/> <TextView android:id="@+id/subtitle" + android:layout_gravity="center_vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginEnd="15dp" android:layout_marginTop="4dp" android:layout_alignParentBottom="true" android:ellipsize="end" android:maxLines="1" - android:textColor="?android:attr/textColorSecondary" + android:textColor="?androidprv:attr/colorAccentPrimaryVariant" android:textSize="14sp" android:fontFamily="roboto-regular" android:visibility="gone"/> - <SeekBar - android:id="@+id/volume_seekbar" - android:layout_marginTop="16dp" - android:layout_marginEnd="8dp" - style="@*android:style/Widget.DeviceDefault.SeekBar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true"/> <ImageView android:id="@+id/add_icon" android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="right" - android:layout_marginEnd="24dp" + android:layout_marginEnd="16dp" android:layout_alignParentRight="true" android:src="@drawable/ic_add" android:tint="?android:attr/colorAccent" @@ -103,30 +111,33 @@ android:layout_width="24dp" android:layout_height="24dp" android:layout_gravity="right" - android:layout_marginEnd="24dp" + android:layout_marginEnd="16dp" android:layout_alignParentRight="true" android:button="@drawable/ic_check_box" android:visibility="gone" - /> + /> </RelativeLayout> <ProgressBar android:id="@+id/volume_indeterminate_progress" - style="@*android:style/Widget.Material.ProgressBar.Horizontal" - android:layout_width="258dp" - android:layout_height="18dp" - android:layout_marginStart="64dp" - android:layout_marginTop="28dp" + style="?android:attr/progressBarStyleSmallTitle" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="16dp" android:indeterminate="true" + android:layout_gravity="right|center" android:indeterminateOnly="true" android:visibility="gone"/> - </FrameLayout> - <View - android:id="@+id/bottom_divider" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_gravity="bottom" - android:background="?android:attr/listDivider" - android:visibility="gone"/> + <ImageView + android:id="@+id/media_output_item_status" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="16dp" + android:indeterminate="true" + android:layout_gravity="right|center" + android:indeterminateOnly="true" + android:importantForAccessibility="no" + android:visibility="gone"/> + </FrameLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml index ee4530cb4377..9368a6a0b7da 100644 --- a/packages/SystemUI/res/layout/privacy_dialog.xml +++ b/packages/SystemUI/res/layout/privacy_dialog.xml @@ -22,10 +22,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins" android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins" - android:layout_marginTop="8dp" android:orientation="vertical" android:paddingBottom="8dp" - android:paddingTop="12dp" + android:paddingTop="8dp" android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding" android:background="@drawable/qs_dialog_bg" /> diff --git a/packages/SystemUI/res/layout/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml index 91d3a53556a7..1aec296f4f5c 100644 --- a/packages/SystemUI/res/layout/qs_user_detail.xml +++ b/packages/SystemUI/res/layout/qs_user_detail.xml @@ -22,6 +22,6 @@ xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - sysui:verticalSpacing="4dp" + sysui:verticalSpacing="20dp" sysui:horizontalSpacing="4dp" style="@style/UserDetailView" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index cc6c5d343502..91b11fcc3c26 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -24,8 +24,6 @@ android:layout_height="wrap_content" android:orientation="vertical" android:gravity="top|center_horizontal" - android:paddingTop="16dp" - android:minHeight="112dp" android:clipChildren="false" android:clipToPadding="false" android:focusable="true" diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml index 9495ee6f3139..355df2c4448a 100644 --- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml +++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml @@ -15,75 +15,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<androidx.constraintlayout.widget.ConstraintLayout +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="24dp" - android:layout_marginStart="16dp" - android:layout_marginEnd="16dp" -> - <TextView - android:id="@+id/title" - android:layout_height="wrap_content" - android:layout_width="0dp" - android:textAlignment="center" - android:text="@string/qs_user_switch_dialog_title" - android:textAppearance="@style/TextAppearance.QSDialog.Title" - android:layout_marginBottom="32dp" - sysui:layout_constraintTop_toTopOf="parent" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toEndOf="parent" - sysui:layout_constraintBottom_toTopOf="@id/grid" - /> - + > <com.android.systemui.qs.PseudoGridView - android:id="@+id/grid" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="28dp" - sysui:verticalSpacing="4dp" - sysui:horizontalSpacing="4dp" - sysui:fixedChildWidth="80dp" - sysui:layout_constraintTop_toBottomOf="@id/title" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toEndOf="parent" - sysui:layout_constraintBottom_toTopOf="@id/barrier" - /> - - <androidx.constraintlayout.widget.Barrier - android:id="@+id/barrier" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - sysui:barrierDirection="top" - sysui:constraint_referenced_ids="settings,done" + android:id="@+id/grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + sysui:verticalSpacing="20dp" + sysui:horizontalSpacing="4dp" + sysui:fixedChildWidth="80dp" /> - - <Button - android:id="@+id/settings" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:text="@string/quick_settings_more_user_settings" - sysui:layout_constraintTop_toBottomOf="@id/barrier" - sysui:layout_constraintBottom_toBottomOf="parent" - sysui:layout_constraintStart_toStartOf="parent" - sysui:layout_constraintEnd_toStartOf="@id/done" - sysui:layout_constraintHorizontal_chainStyle="spread_inside" - style="@style/Widget.QSDialog.Button.BorderButton" - /> - - <Button - android:id="@+id/done" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:text="@string/quick_settings_done" - sysui:layout_constraintTop_toBottomOf="@id/barrier" - sysui:layout_constraintBottom_toBottomOf="parent" - sysui:layout_constraintStart_toEndOf="@id/settings" - sysui:layout_constraintEnd_toEndOf="parent" - style="@style/Widget.QSDialog.Button" - /> - -</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index b4c9a9395983..2290964eccd0 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -128,9 +128,6 @@ systemui:layout_constraintEnd_toEndOf="parent" /> - <include layout="@layout/ambient_indication" - android:id="@+id/ambient_indication_container" /> - <include layout="@layout/photo_preview_overlay" /> <include diff --git a/packages/SystemUI/res/layout/tile_service_request_dialog.xml b/packages/SystemUI/res/layout/tile_service_request_dialog.xml index b431d444e15a..3a8a69ce207f 100644 --- a/packages/SystemUI/res/layout/tile_service_request_dialog.xml +++ b/packages/SystemUI/res/layout/tile_service_request_dialog.xml @@ -30,7 +30,6 @@ android:layout_marginBottom="16dp" android:textDirection="locale" android:textAlignment="viewStart" - android:textAppearance="@style/TextAppearance.PrivacyDialog" - android:lineHeight="20sp" + android:textAppearance="@style/TextAppearance.Dialog.Body" /> </LinearLayout> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 07e28b6d7f20..cb963e6ffc89 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -16,9 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog"> - <item name="android:buttonCornerRadius">28dp</item> - </style> + <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Dialog"/> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" /> @@ -53,13 +51,17 @@ <style name="TextAppearance.InternetDialog.Active"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/connected_network_primary_color</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> <item name="android:textDirection">locale</item> </style> <style name="TextAppearance.InternetDialog.Secondary.Active"> <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/connected_network_secondary_color</item> + <item name="android:textColor">?android:attr/textColorSecondaryInverse</item> + </style> + + <style name="InternetDialog.Divider.Active"> + <item name="android:background">?android:attr/textColorSecondaryInverse</item> </style> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index c5b47d0e42fe..2b16ec218217 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -16,7 +16,8 @@ * limitations under the License. */ --> -<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <drawable name="notification_number_text_color">#ffffffff</drawable> <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable> <color name="system_bar_background_opaque">#ff000000</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 56464e46c392..8b59d86ee73a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -82,7 +82,7 @@ <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" --> <string name="quick_settings_tiles_stock" translatable="false"> - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded + internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,fgsmanager </string> <!-- The tiles to display in QuickSettings --> @@ -597,6 +597,17 @@ 280 </integer> + <!-- Haptic feedback intensity for ticks used for the udfps dwell time --> + <item name="config_udfpsTickIntensity" translatable="false" format="float" + type="dimen">.5</item> + + <!-- Haptic feedback delay between ticks used for udfps dwell time --> + <integer name="config_udfpsTickDelay" translatable="false">25</integer> + + <!-- Haptic feedback tick type - if true, uses VibrationEffect.Composition.PRIMITIVE_LOW_TICK + else uses VibrationEffect.Composition.PRIMITIVE_TICK --> + <bool name="config_udfpsUseLowTick">true</bool> + <!-- package name of a built-in camera app to use to restrict implicit intent resolution when the double-press power gesture is used. Ignored if empty. --> <string translatable="false" name="config_cameraGesturePackage"></string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 059aad72c5a9..a437ae616999 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1085,11 +1085,13 @@ <!-- Output switcher panel related dimensions --> <dimen name="media_output_dialog_list_margin">12dp</dimen> <dimen name="media_output_dialog_list_max_height">364dp</dimen> - <dimen name="media_output_dialog_header_album_icon_size">48dp</dimen> + <dimen name="media_output_dialog_header_album_icon_size">72dp</dimen> <dimen name="media_output_dialog_header_back_icon_size">32dp</dimen> <dimen name="media_output_dialog_header_icon_padding">16dp</dimen> - <dimen name="media_output_dialog_icon_corner_radius">8dp</dimen> + <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen> <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen> + <dimen name="media_output_dialog_button_padding_horizontal">16dp</dimen> + <dimen name="media_output_dialog_button_padding_vertical">8dp</dimen> <!-- Distance that the full shade transition takes in order for qs to fully transition to the shade --> @@ -1240,6 +1242,8 @@ <!-- Internet panel related dimensions --> <dimen name="internet_dialog_list_max_height">662dp</dimen> + <!-- The height of the WiFi network in Internet panel. --> + <dimen name="internet_dialog_wifi_network_height">72dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> <dimen name="large_dialog_width">@dimen/match_parent</dimen> @@ -1276,10 +1280,21 @@ <dimen name="qs_tile_service_request_tile_width">192dp</dimen> <dimen name="qs_tile_service_request_content_space">24dp</dimen> - <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen> - <dimen name="qs_dialog_button_vertical_padding">8dp</dimen> + <!-- Dimensions for unified SystemUI dialogs styling. Used by Theme.SystemUI.Dialog and + alert_dialog_systemui.xml + --> + <dimen name="dialog_button_horizontal_padding">16dp</dimen> + <dimen name="dialog_button_vertical_padding">8dp</dimen> <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> - <dimen name="qs_dialog_button_vertical_inset">6dp</dimen> + <dimen name="dialog_button_vertical_inset">6dp</dimen> + <dimen name="dialog_top_padding">24dp</dimen> + <dimen name="dialog_bottom_padding">18dp</dimen> + <dimen name="dialog_side_padding">24dp</dimen> + <dimen name="dialog_button_bar_top_padding">32dp</dimen> + + <!-- ************************************************************************* --> <dimen name="keyguard_unfold_translation_x">16dp</dimen> + + <dimen name="fgs_manager_min_width_minor">100%</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 21b4a4257cf8..300cb2d38dff 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2203,7 +2203,7 @@ <!-- Summary for disconnected status [CHAR LIMIT=50] --> <string name="media_output_dialog_disconnected">(disconnected)</string> <!-- Summary for connecting error message [CHAR LIMIT=NONE] --> - <string name="media_output_dialog_connect_failed">Couldn\'t connect. Try again.</string> + <string name="media_output_dialog_connect_failed">Can\'t switch. Tap to try again.</string> <!-- Title for pairing item [CHAR LIMIT=60] --> <string name="media_output_dialog_pairing_new">Pair new device</string> @@ -2358,4 +2358,9 @@ <!-- Title for User Switch dialog. [CHAR LIMIT=20] --> <string name="qs_user_switch_dialog_title">Select user</string> + + <!-- Title for dialog listing applications currently running in the backing [CHAR LIMIT=NONE]--> + <string name="fgs_manager_dialog_title">Apps running in the background</string> + <!-- Label of the button to stop the app from running in the background [CHAR LIMIT=12]--> + <string name="fgs_manager_app_item_stop_button_label">Stop</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index d972b7fc25a6..2c79919cfaa7 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -14,8 +14,8 @@ limitations under the License. --> -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- NOTE: Adding the androidprv: namespace to this file will break the studio build. --> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <!-- HybridNotification themes and styles --> @@ -351,11 +351,19 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> + <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> + + <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog"> <item name="android:buttonCornerRadius">28dp</item> - <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item> - <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> - <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item> + <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item> + <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> + <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> + <item name="android:colorBackground">?androidprv:attr/colorSurface</item> + <item name="android:alertDialogStyle">@style/AlertDialogStyle</item> + </style> + + <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault"> + <item name="android:layout">@layout/alert_dialog_systemui</item> </style> <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> @@ -365,11 +373,11 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> - <item name="android:windowIsFloating">true</item> + <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="Theme.SystemUI.Dialog"> + <!-- Settings windowFullscreen: true is necessary to be able to intercept touch events --> + <!-- that would otherwise be intercepted by the Shade. --> + <item name="android:windowFullscreen">true</item> <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:backgroundDimEnabled">true</item> - <item name="android:windowCloseOnTouchOutside">true</item> </style> <style name="QSBorderlessButton"> @@ -446,6 +454,12 @@ <item name="android:background">@drawable/media_output_dialog_button_background</item> </style> + <style name="MediaOutputRoundedButton" parent="@android:style/Widget.Material.Button"> + <item name="android:background">@drawable/media_output_dialog_solid_button_background</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> + <item name="android:textSize">14sp</item> + </style> + <style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings"> <item name="android:windowActionBar">false</item> <item name="preferenceTheme">@style/TunerPreferenceTheme</item> @@ -646,16 +660,6 @@ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> </style> - <!-- TileService request dialog --> - <style name="TileRequestDialog" parent="Theme.SystemUI.QuickSettings.Dialog"> - <item name="android:windowIsTranslucent">true</item> - <item name="android:windowBackground">@drawable/qs_dialog_bg</item> - <item name="android:windowIsFloating">true</item> - <item name="android:backgroundDimEnabled">true</item> - <item name="android:windowCloseOnTouchOutside">true</item> - <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> - </style> - <!-- USB Contaminant dialog --> <style name ="USBContaminant" /> @@ -839,7 +843,6 @@ <style name="Widget.SliceView.Panel"> <item name="titleSize">16sp</item> <item name="rowStyle">@style/SliceRow</item> - <item name="android:background">?android:attr/colorBackgroundFloating</item> </style> <style name="SliceRow"> @@ -863,24 +866,37 @@ <item name="actionDividerHeight">32dp</item> </style> - <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog"> + <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large"> <item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textSize">24sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">32sp</item> + <item name="android:gravity">center</item> + <item name="android:textAlignment">center</item> </style> - <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog"> + <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textSize">14sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:lineHeight">20sp</item> + </style> + + <style name="TextAppearance.Dialog.Body.Message"> + <item name="android:gravity">center</item> + <item name="android:textAlignment">center</item> + </style> + + <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog"> <item name="android:background">@drawable/qs_dialog_btn_filled</item> - <item name="android:textColor">@color/prv_text_color_on_accent</item> + <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> <item name="android:stateListAnimator">@null</item> - <item name="android:layout_marginHorizontal">4dp</item> </style> - <style name="Widget.QSDialog.Button.BorderButton"> + <style name="Widget.Dialog.Button.BorderButton"> <item name="android:background">@drawable/qs_dialog_btn_outline</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> @@ -957,4 +973,20 @@ <style name="TextAppearance.InternetDialog.Secondary.Active"/> + <style name="InternetDialog.Divider"> + <item name="android:background">?android:attr/textColorSecondary</item> + </style> + + <style name="InternetDialog.Divider.Active"/> + + <style name="FgsManagerDialogTitle"> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:textStyle">bold</item> + <item name="android:textDirection">locale</item> + </style> + + <style name="FgsManagerAppDuration"> + <item name="android:fontFamily">?android:attr/textAppearanceSmall</item> + <item name="android:textDirection">locale</item> + </style> </resources> diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml index ed29bc76b350..5fdb4978df73 100644 --- a/packages/SystemUI/res/values/tiles_states_strings.xml +++ b/packages/SystemUI/res/values/tiles_states_strings.xml @@ -298,4 +298,14 @@ <item>Off</item> <item>On</item> </string-array> + + <!-- State names for fgsmanager tile: unavailable, off, on. + This subtitle is shown when the tile is in that particular state but does not set its own + subtitle, so some of these may never appear on screen. They should still be translated as + if they could appear.[CHAR LIMIT=32] --> + <string-array name="tile_states_fgsmanager"> + <item>Unavailable</item> + <item>Off</item> + <item>On</item> + </string-array> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java index 07ad0c8a5120..8aa3abac831f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java +++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java @@ -51,6 +51,9 @@ public class KeyButtonRipple extends Drawable { private static final Interpolator ALPHA_OUT_INTERPOLATOR = new PathInterpolator(0f, 0f, 0.8f, 1f); + @DimenRes + private final int mMaxWidthResource; + private Paint mRipplePaint; private CanvasProperty<Float> mLeftProp; private CanvasProperty<Float> mTopProp; @@ -90,10 +93,17 @@ public class KeyButtonRipple extends Drawable { private Type mType = Type.ROUNDED_RECT; public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + mMaxWidthResource = maxWidthResource; mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); mTargetView = targetView; } + public void updateResources() { + mMaxWidth = mTargetView.getContext().getResources() + .getDimensionPixelSize(mMaxWidthResource); + invalidateSelf(); + } + public void setDarkIntensity(float darkIntensity) { mDark = darkIntensity >= 0.5f; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 8bd0f910dac3..01497516e0b1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -95,4 +95,9 @@ oneway interface IOverviewProxy { * Sent when screen turned on and ready to use (blocker scrim is hidden) */ void onScreenTurnedOn() = 21; + + /** + * Sent when the desired dark intensity of the nav buttons has changed + */ + void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 3128ffdbc67b..675dc9b533fb 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.ViewDebug; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.PrintWriter; @@ -50,6 +51,7 @@ public class Task { @ViewDebug.ExportedProperty(category="recents") public int windowingMode; @ViewDebug.ExportedProperty(category="recents") + @NonNull public final Intent baseIntent; @ViewDebug.ExportedProperty(category="recents") public final int userId; @@ -83,7 +85,7 @@ public class Task { updateHashCode(); } - public TaskKey(int id, int windowingMode, Intent intent, + public TaskKey(int id, int windowingMode, @NonNull Intent intent, ComponentName sourceComponent, int userId, long lastActiveTime) { this.id = id; this.windowingMode = windowingMode; @@ -95,7 +97,7 @@ public class Task { updateHashCode(); } - public TaskKey(int id, int windowingMode, Intent intent, + public TaskKey(int id, int windowingMode, @NonNull Intent intent, ComponentName sourceComponent, int userId, long lastActiveTime, int displayId) { this.id = id; this.windowingMode = windowingMode; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index cbf739732361..857cc4620ebd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -21,6 +21,8 @@ import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.StringRes; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.Config; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.drawable.AnimatedVectorDrawable; @@ -29,12 +31,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; import androidx.core.view.OneShotPreDrawListener; -import com.android.systemui.shared.R; import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position; /** @@ -48,7 +50,21 @@ public class FloatingRotationButton implements RotationButton { private final ViewGroup mKeyButtonContainer; private final FloatingRotationButtonView mKeyButtonView; - private final int mContainerSize; + private int mContainerSize; + private final Context mContext; + + @StringRes + private final int mContentDescriptionResource; + @DimenRes + private final int mMinMarginResource; + @DimenRes + private final int mRoundedContentPaddingResource; + @DimenRes + private final int mTaskbarLeftMarginResource; + @DimenRes + private final int mTaskbarBottomMarginResource; + @DimenRes + private final int mButtonDiameterResource; private AnimatedVectorDrawable mAnimatedDrawable; private boolean mIsShowing; @@ -58,13 +74,13 @@ public class FloatingRotationButton implements RotationButton { private boolean mIsTaskbarVisible = false; private boolean mIsTaskbarStashed = false; - private final FloatingRotationButtonPositionCalculator mPositionCalculator; + private FloatingRotationButtonPositionCalculator mPositionCalculator; private RotationButtonController mRotationButtonController; private RotationButtonUpdatesCallback mUpdatesCallback; private Position mPosition; - public FloatingRotationButton(Context context, @StringRes int contentDescription, + public FloatingRotationButton(Context context, @StringRes int contentDescriptionResource, @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin, @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin, @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, @@ -73,24 +89,37 @@ public class FloatingRotationButton implements RotationButton { mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null); mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); mKeyButtonView.setVisibility(View.VISIBLE); - mKeyButtonView.setContentDescription(context.getString(contentDescription)); + mKeyButtonView.setContentDescription(context.getString(contentDescriptionResource)); mKeyButtonView.setRipple(rippleMaxWidth); - Resources res = context.getResources(); + mContext = context; + + mContentDescriptionResource = contentDescriptionResource; + mMinMarginResource = minMargin; + mRoundedContentPaddingResource = roundedContentPadding; + mTaskbarLeftMarginResource = taskbarLeftMargin; + mTaskbarBottomMarginResource = taskbarBottomMargin; + mButtonDiameterResource = buttonDiameter; + + updateDimensionResources(); + } + + private void updateDimensionResources() { + Resources res = mContext.getResources(); int defaultMargin = Math.max( - res.getDimensionPixelSize(minMargin), - res.getDimensionPixelSize(roundedContentPadding)); + res.getDimensionPixelSize(mMinMarginResource), + res.getDimensionPixelSize(mRoundedContentPaddingResource)); int taskbarMarginLeft = - res.getDimensionPixelSize(taskbarLeftMargin); + res.getDimensionPixelSize(mTaskbarLeftMarginResource); int taskbarMarginBottom = - res.getDimensionPixelSize(taskbarBottomMargin); + res.getDimensionPixelSize(mTaskbarBottomMarginResource); mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin, taskbarMarginLeft, taskbarMarginBottom); - final int diameter = res.getDimensionPixelSize(buttonDiameter); + final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -119,32 +148,10 @@ public class FloatingRotationButton implements RotationButton { } mIsShowing = true; - int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - - // TODO(b/200103245): add new window type that has z-index above - // TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has - // the same window type - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - mContainerSize, - mContainerSize, - 0, 0, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags, - PixelFormat.TRANSLUCENT); - - lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - lp.setTitle("FloatingRotationButton"); - lp.setFitInsetsTypes(0 /*types */); - - mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation(); - mPosition = mPositionCalculator - .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed); - lp.gravity = mPosition.getGravity(); - ((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity = - mPosition.getGravity(); + final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); + mWindowManager.addView(mKeyButtonContainer, layoutParams); - updateTranslation(mPosition, /* animate */ false); - - mWindowManager.addView(mKeyButtonContainer, lp); if (mAnimatedDrawable != null) { mAnimatedDrawable.reset(); mAnimatedDrawable.start(); @@ -232,6 +239,53 @@ public class FloatingRotationButton implements RotationButton { } } + /** + * Updates resources that could be changed in runtime, should be called on configuration + * change with changes diff integer mask + * @param configurationChanges - configuration changes with flags from ActivityInfo e.g. + * {@link android.content.pm.ActivityInfo#CONFIG_DENSITY} + */ + public void onConfigurationChanged(@Config int configurationChanges) { + if ((configurationChanges & ActivityInfo.CONFIG_DENSITY) != 0 + || (configurationChanges & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { + updateDimensionResources(); + + if (mIsShowing) { + final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); + mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams); + } + } + + if ((configurationChanges & ActivityInfo.CONFIG_LOCALE) != 0) { + mKeyButtonView.setContentDescription(mContext.getString(mContentDescriptionResource)); + } + } + + private LayoutParams adjustViewPositionAndCreateLayoutParams() { + final LayoutParams lp = new LayoutParams( + mContainerSize, + mContainerSize, + /* xpos */ 0, /* ypos */ 0, LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + + lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("FloatingRotationButton"); + lp.setFitInsetsTypes(/* types */ 0); + + mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation(); + mPosition = mPositionCalculator + .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed); + + lp.gravity = mPosition.getGravity(); + ((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity = + mPosition.getGravity(); + + updateTranslation(mPosition, /* animate */ false); + + return lp; + } + private void updateTranslation(Position position, boolean animate) { final int translationX = position.getTranslationX(); final int translationY = position.getTranslationY(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java index c5f8fc15b3b7..a4b6451caaea 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -17,6 +17,8 @@ package com.android.systemui.shared.rotation; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -37,12 +39,15 @@ public class FloatingRotationButtonView extends ImageView { private KeyButtonRipple mRipple; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Configuration mLastConfiguration; + public FloatingRotationButtonView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mLastConfiguration = getResources().getConfiguration(); setClickable(true); @@ -63,6 +68,17 @@ public class FloatingRotationButtonView extends ImageView { } } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + final int changes = mLastConfiguration.updateFrom(newConfig); + if ((changes & ActivityInfo.CONFIG_SCREEN_SIZE) != 0 + || ((changes & ActivityInfo.CONFIG_DENSITY) != 0)) { + if (mRipple != null) { + mRipple.updateResources(); + } + } + } + public void setColors(int lightColor, int darkColor) { getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN)); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 2dbd5dee76aa..78867f7220af 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -481,7 +481,9 @@ public class RotationButtonController { * orientation overview. */ public void setSkipOverrideUserLockPrefsOnce() { - mSkipOverrideUserLockPrefsOnce = true; + // If live-tile is enabled (recents animation keeps running in overview), there is no + // activity switch so the display rotation is not changed, then it is no need to skip. + mSkipOverrideUserLockPrefsOnce = !mIsRecentsAnimationRunning; } private boolean shouldOverrideUserLockPrefs(final int rotation) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index 3ebd652b3467..986f296571e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -479,9 +479,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView> Resources resources = mView.getResources(); - if (resources.getBoolean(R.bool.can_use_one_handed_bouncer) - && resources.getBoolean( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) { + if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)) { gravity = resources.getInteger( R.integer.keyguard_host_view_one_handed_gravity); } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index abd89b97e3c7..172c7f62100f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -30,6 +30,7 @@ import android.content.Context; import android.graphics.Rect; import android.provider.Settings; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.Gravity; @@ -37,6 +38,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowManager; @@ -44,6 +46,8 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.dynamicanimation.animation.DynamicAnimation; @@ -58,6 +62,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.util.settings.GlobalSettings; import java.util.ArrayList; import java.util.List; @@ -67,6 +72,12 @@ public class KeyguardSecurityContainer extends FrameLayout { static final int USER_TYPE_WORK_PROFILE = 2; static final int USER_TYPE_SECONDARY_USER = 3; + @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER}) + public @interface Mode {} + static final int MODE_DEFAULT = 0; + static final int MODE_ONE_HANDED = 1; + static final int MODE_USER_SWITCHER = 2; + // Bouncer is dismissed due to no security. static final int BOUNCER_DISMISS_NONE_SECURITY = 0; // Bouncer is dismissed due to pin, password or pattern entered. @@ -78,6 +89,8 @@ public class KeyguardSecurityContainer extends FrameLayout { // Bouncer is dismissed due to sim card unlock code entered. static final int BOUNCER_DISMISS_SIM = 4; + private static final String TAG = "KeyguardSecurityView"; + // Make the view move slower than the finger, as if the spring were applying force. private static final float TOUCH_Y_MULTIPLIER = 0.25f; // How much you need to drag the bouncer to trigger an auth retry (in dps.) @@ -96,6 +109,7 @@ public class KeyguardSecurityContainer extends FrameLayout { @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; + private GlobalSettings mGlobalSettings; private AlertDialog mAlertDialog; private boolean mSwipeUpToRetry; @@ -110,10 +124,8 @@ public class KeyguardSecurityContainer extends FrameLayout { private float mStartTouchY = -1; private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; - - private boolean mIsSecurityViewLeftAligned = true; - private boolean mOneHandedMode = false; - @Nullable private ValueAnimator mRunningOneHandedAnimator; + private ModeLogic mModeLogic = new DefaultModeLogic(); + private @Mode int mCurrentMode = MODE_DEFAULT; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -260,172 +272,62 @@ public class KeyguardSecurityContainer extends FrameLayout { updateBiometricRetry(securityMode, faceAuthEnabled); } - /** - * Sets whether this security container is in one handed mode. If so, it will measure its - * child SecurityViewFlipper in one half of the screen, and move it when tapping on the opposite - * side of the screen. - */ - public void setOneHandedMode(boolean oneHandedMode) { - mOneHandedMode = oneHandedMode; - updateSecurityViewGravity(); - updateSecurityViewLocation(false); - } + void initMode(@Mode int mode, GlobalSettings globalSettings) { + if (mCurrentMode == mode) return; + Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " + + modeToString(mode)); + mCurrentMode = mode; - /** Returns whether this security container is in one-handed mode. */ - public boolean isOneHandedMode() { - return mOneHandedMode; + switch (mode) { + case MODE_ONE_HANDED: + mModeLogic = new OneHandedModeLogic(); + break; + case MODE_USER_SWITCHER: + mModeLogic = new UserSwitcherModeLogic(); + break; + default: + mModeLogic = new DefaultModeLogic(); + } + mGlobalSettings = globalSettings; + finishSetup(); + } + + private String modeToString(@Mode int mode) { + switch (mode) { + case MODE_DEFAULT: + return "Default"; + case MODE_ONE_HANDED: + return "OneHanded"; + case MODE_USER_SWITCHER: + return "UserSwitcher"; + default: + throw new IllegalArgumentException("mode: " + mode + " not supported"); + } } - /** - * When in one-handed mode, sets if the inner SecurityViewFlipper should be aligned to the - * left-hand side of the screen or not, and whether to animate when moving between the two. - */ - public void setOneHandedModeLeftAligned(boolean leftAligned, boolean animate) { - mIsSecurityViewLeftAligned = leftAligned; - updateSecurityViewLocation(animate); - } + private void finishSetup() { + if (mSecurityViewFlipper == null || mGlobalSettings == null) return; - /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */ - public boolean isOneHandedModeLeftAligned() { - return mIsSecurityViewLeftAligned; + mModeLogic.init(this, mGlobalSettings, mSecurityViewFlipper); } - private void updateSecurityViewGravity() { - if (mSecurityViewFlipper == null) { - return; - } - - FrameLayout.LayoutParams lp = - (FrameLayout.LayoutParams) mSecurityViewFlipper.getLayoutParams(); - - if (mOneHandedMode) { - lp.gravity = Gravity.LEFT | Gravity.BOTTOM; - } else { - lp.gravity = Gravity.CENTER_HORIZONTAL; - } - - mSecurityViewFlipper.setLayoutParams(lp); + @Mode int getMode() { + return mCurrentMode; } /** - * Moves the inner security view to the correct location (in one handed mode) with animation. - * This is triggered when the user taps on the side of the screen that is not currently occupied - * by the security view . + * The position of the container can be adjusted based upon a touch at location x. This has + * been used in one-handed mode to make sure the bouncer appears on the side of the display + * that the user last interacted with. */ - private void updateSecurityViewLocation(boolean animate) { - if (mSecurityViewFlipper == null) { - return; - } - - if (!mOneHandedMode) { - mSecurityViewFlipper.setTranslationX(0); - return; - } - - if (mRunningOneHandedAnimator != null) { - mRunningOneHandedAnimator.cancel(); - mRunningOneHandedAnimator = null; - } - - int targetTranslation = mIsSecurityViewLeftAligned - ? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth()); - - if (animate) { - // This animation is a bit fun to implement. The bouncer needs to move, and fade in/out - // at the same time. The issue is, the bouncer should only move a short amount (120dp or - // so), but obviously needs to go from one side of the screen to the other. This needs a - // pretty custom animation. - // - // This works as follows. It uses a ValueAnimation to simply drive the animation - // progress. This animator is responsible for both the translation of the bouncer, and - // the current fade. It will fade the bouncer out while also moving it along the 120dp - // path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer - // to its destination, then fade it back in again. The effect is that the bouncer will - // move from 0 -> X while fading out, then (destination - X) -> destination while fading - // back in again. - // TODO(b/195012405): Make this animation properly abortable. - Interpolator positionInterpolator = AnimationUtils.loadInterpolator( - mContext, android.R.interpolator.fast_out_extra_slow_in); - Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; - Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; - - mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS); - mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR); - - int initialTranslation = (int) mSecurityViewFlipper.getTranslationX(); - int totalTranslation = (int) getResources().getDimension( - R.dimen.one_handed_bouncer_move_animation_translation); - - final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering() - && mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; - if (shouldRestoreLayerType) { - mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); - } - - float initialAlpha = mSecurityViewFlipper.getAlpha(); - - mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRunningOneHandedAnimator = null; - } - }); - mRunningOneHandedAnimator.addUpdateListener(animation -> { - float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION; - boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; - - int currentTranslation = (int) (positionInterpolator.getInterpolation( - animation.getAnimatedFraction()) * totalTranslation); - int translationRemaining = totalTranslation - currentTranslation; - - // Flip the sign if we're going from right to left. - if (mIsSecurityViewLeftAligned) { - currentTranslation = -currentTranslation; - translationRemaining = -translationRemaining; - } - - if (isFadingOut) { - // The bouncer fades out over the first X%. - float fadeOutFraction = MathUtils.constrainedMap( - /* rangeMin= */1.0f, - /* rangeMax= */0.0f, - /* valueMin= */0.0f, - /* valueMax= */switchPoint, - animation.getAnimatedFraction()); - float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); - - // When fading out, the alpha needs to start from the initial opacity of the - // view flipper, otherwise we get a weird bit of jank as it ramps back to 100%. - mSecurityViewFlipper.setAlpha(opacity * initialAlpha); - - // Animate away from the source. - mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation); - } else { - // And in again over the remaining (100-X)%. - float fadeInFraction = MathUtils.constrainedMap( - /* rangeMin= */0.0f, - /* rangeMax= */1.0f, - /* valueMin= */switchPoint, - /* valueMax= */1.0f, - animation.getAnimatedFraction()); - - float opacity = fadeInInterpolator.getInterpolation(fadeInFraction); - mSecurityViewFlipper.setAlpha(opacity); - - // Fading back in, animate towards the destination. - mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining); - } - - if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { - mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); - } - }); + void updatePositionByTouchX(float x) { + mModeLogic.updatePositionByTouchX(x); + } - mRunningOneHandedAnimator.start(); - } else { - mSecurityViewFlipper.setTranslationX(targetTranslation); - } + /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */ + public boolean isOneHandedModeLeftAligned() { + return mCurrentMode == MODE_ONE_HANDED + && ((OneHandedModeLogic) mModeLogic).isLeftAligned(); } public void onPause() { @@ -526,7 +428,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } } else { if (!mIsDragging) { - handleTap(event); + mModeLogic.handleTap(event); } } } @@ -541,36 +443,6 @@ public class KeyguardSecurityContainer extends FrameLayout { mMotionEventListeners.remove(listener); } - private void handleTap(MotionEvent event) { - // If we're using a fullscreen security mode, skip - if (!mOneHandedMode) { - return; - } - - moveBouncerForXCoordinate(event.getX(), /* animate= */true); - } - - private void moveBouncerForXCoordinate(float x, boolean animate) { - // Did the tap hit the "other" side of the bouncer? - if ((mIsSecurityViewLeftAligned && (x > getWidth() / 2f)) - || (!mIsSecurityViewLeftAligned && (x < getWidth() / 2f))) { - mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; - - Settings.Global.putInt( - mContext.getContentResolver(), - Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT - : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); - - int keyguardState = mIsSecurityViewLeftAligned - ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT - : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); - - updateSecurityViewLocation(animate); - } - } - void setSwipeListener(SwipeListener swipeListener) { mSwipeListener = swipeListener; } @@ -618,6 +490,8 @@ public class KeyguardSecurityContainer extends FrameLayout { public void onFinishInflate() { super.onFinishInflate(); mSecurityViewFlipper = findViewById(R.id.view_flipper); + + finishSetup(); } @Override @@ -685,20 +559,15 @@ public class KeyguardSecurityContainer extends FrameLayout { int maxWidth = 0; int childState = 0; - int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(widthMeasureSpec) / 2, - MeasureSpec.getMode(widthMeasureSpec)); - for (int i = 0; i < getChildCount(); i++) { final View view = getChildAt(i); if (view.getVisibility() != GONE) { - if (mOneHandedMode && view == mSecurityViewFlipper) { - measureChildWithMargins(view, halfWidthMeasureSpec, 0, - heightMeasureSpec, 0); - } else { - measureChildWithMargins(view, widthMeasureSpec, 0, - heightMeasureSpec, 0); + int updatedWidthMeasureSpec = widthMeasureSpec; + if (view == mSecurityViewFlipper) { + updatedWidthMeasureSpec = mModeLogic.getChildWidthMeasureSpec(widthMeasureSpec); } + measureChildWithMargins(view, updatedWidthMeasureSpec, 0, heightMeasureSpec, 0); + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); maxWidth = Math.max(maxWidth, view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); @@ -726,7 +595,7 @@ public class KeyguardSecurityContainer extends FrameLayout { // After a layout pass, we need to re-place the inner bouncer, as our bounds may have // changed. - updateSecurityViewLocation(/* animate= */false); + mModeLogic.updateSecurityViewLocation(); } void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { @@ -770,4 +639,264 @@ public class KeyguardSecurityContainer extends FrameLayout { public void reset() { mDisappearAnimRunning = false; } + + /** + * Enscapsulates the differences between bouncer modes for the container. + */ + private interface ModeLogic { + + default void init(ViewGroup v, GlobalSettings globalSettings, + KeyguardSecurityViewFlipper viewFlipper) {}; + + /** Reinitialize the location */ + default void updateSecurityViewLocation() {}; + + /** Alter the ViewFlipper position, based upon a touch outside of it */ + default void updatePositionByTouchX(float x) {}; + + /** A tap on the container, outside of the ViewFlipper */ + default void handleTap(MotionEvent event) {}; + + /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */ + default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { + return parentWidthMeasureSpec; + } + } + + private static class DefaultModeLogic implements ModeLogic { + private ViewGroup mView; + private KeyguardSecurityViewFlipper mViewFlipper; + + @Override + public void init(ViewGroup v, GlobalSettings globalSettings, + KeyguardSecurityViewFlipper viewFlipper) { + mView = v; + mViewFlipper = viewFlipper; + + // Reset ViewGroup to default positions + updateSecurityViewGroup(); + } + + private void updateSecurityViewGroup() { + FrameLayout.LayoutParams lp = + (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams(); + lp.gravity = Gravity.CENTER_HORIZONTAL; + mViewFlipper.setLayoutParams(lp); + + mViewFlipper.setTranslationX(0); + } + } + + /** + * User switcher mode will display both the current user icon as well as + * a user switcher, in both portrait and landscape modes. + */ + private static class UserSwitcherModeLogic implements ModeLogic { + private ViewGroup mView; + + @Override + public void init(ViewGroup v, GlobalSettings globalSettings, + KeyguardSecurityViewFlipper viewFlipper) { + mView = v; + } + } + + /** + * Logic to enabled one-handed bouncer mode. Supports animating the bouncer + * between alternate sides of the display. + */ + private static class OneHandedModeLogic implements ModeLogic { + @Nullable private ValueAnimator mRunningOneHandedAnimator; + private ViewGroup mView; + private KeyguardSecurityViewFlipper mViewFlipper; + private GlobalSettings mGlobalSettings; + + @Override + public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + @NonNull KeyguardSecurityViewFlipper viewFlipper) { + mView = v; + mViewFlipper = viewFlipper; + mGlobalSettings = globalSettings; + + updateSecurityViewGravity(); + updateSecurityViewLocation(isLeftAligned(), /* animate= */false); + } + + /** + * One-handed mode contains the child to half of the available space. + */ + @Override + public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { + return MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) / 2, + MeasureSpec.getMode(parentWidthMeasureSpec)); + } + + private void updateSecurityViewGravity() { + FrameLayout.LayoutParams lp = + (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams(); + lp.gravity = Gravity.LEFT | Gravity.BOTTOM; + mViewFlipper.setLayoutParams(lp); + } + + /** + * Moves the bouncer to align with a tap (most likely in the shade), so the bouncer + * appears on the same side as a touch. Will not update the user-preference. + */ + @Override + public void updatePositionByTouchX(float x) { + updateSecurityViewLocation(x <= mView.getWidth() / 2f, /* animate= */false); + } + + boolean isLeftAligned() { + return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT) + == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; + } + + /** + * Determine if a tap on this view is on the other side. If so, will animate positions + * and record the preference to always show on this side. + */ + @Override + public void handleTap(MotionEvent event) { + float x = event.getX(); + boolean currentlyLeftAligned = isLeftAligned(); + // Did the tap hit the "other" side of the bouncer? + if ((currentlyLeftAligned && (x > mView.getWidth() / 2f)) + || (!currentlyLeftAligned && (x < mView.getWidth() / 2f))) { + + boolean willBeLeftAligned = !currentlyLeftAligned; + mGlobalSettings.putInt( + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + willBeLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + + int keyguardState = willBeLeftAligned + ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT + : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); + + updateSecurityViewLocation(willBeLeftAligned, true /* animate */); + } + } + + @Override + public void updateSecurityViewLocation() { + updateSecurityViewLocation(isLeftAligned(), /* animate= */false); + } + + /** + * Moves the inner security view to the correct location (in one handed mode) with + * animation. This is triggered when the user taps on the side of the screen that is not + * currently occupied by the security view. + */ + private void updateSecurityViewLocation(boolean leftAlign, boolean animate) { + if (mRunningOneHandedAnimator != null) { + mRunningOneHandedAnimator.cancel(); + mRunningOneHandedAnimator = null; + } + + int targetTranslation = leftAlign + ? 0 : (int) (mView.getMeasuredWidth() - mViewFlipper.getWidth()); + + if (animate) { + // This animation is a bit fun to implement. The bouncer needs to move, and fade + // in/out at the same time. The issue is, the bouncer should only move a short + // amount (120dp or so), but obviously needs to go from one side of the screen to + // the other. This needs a pretty custom animation. + // + // This works as follows. It uses a ValueAnimation to simply drive the animation + // progress. This animator is responsible for both the translation of the bouncer, + // and the current fade. It will fade the bouncer out while also moving it along the + // 120dp path. Once the bouncer is fully faded out though, it will "snap" the + // bouncer closer to its destination, then fade it back in again. The effect is that + // the bouncer will move from 0 -> X while fading out, then + // (destination - X) -> destination while fading back in again. + // TODO(b/208250221): Make this animation properly abortable. + Interpolator positionInterpolator = AnimationUtils.loadInterpolator( + mView.getContext(), android.R.interpolator.fast_out_extra_slow_in); + Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; + Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; + + mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS); + mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR); + + int initialTranslation = (int) mViewFlipper.getTranslationX(); + int totalTranslation = (int) mView.getResources().getDimension( + R.dimen.one_handed_bouncer_move_animation_translation); + + final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering() + && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; + if (shouldRestoreLayerType) { + mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); + } + + float initialAlpha = mViewFlipper.getAlpha(); + + mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningOneHandedAnimator = null; + } + }); + mRunningOneHandedAnimator.addUpdateListener(animation -> { + float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION; + boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; + + int currentTranslation = (int) (positionInterpolator.getInterpolation( + animation.getAnimatedFraction()) * totalTranslation); + int translationRemaining = totalTranslation - currentTranslation; + + // Flip the sign if we're going from right to left. + if (leftAlign) { + currentTranslation = -currentTranslation; + translationRemaining = -translationRemaining; + } + + if (isFadingOut) { + // The bouncer fades out over the first X%. + float fadeOutFraction = MathUtils.constrainedMap( + /* rangeMin= */1.0f, + /* rangeMax= */0.0f, + /* valueMin= */0.0f, + /* valueMax= */switchPoint, + animation.getAnimatedFraction()); + float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); + + // When fading out, the alpha needs to start from the initial opacity of the + // view flipper, otherwise we get a weird bit of jank as it ramps back to + // 100%. + mViewFlipper.setAlpha(opacity * initialAlpha); + + // Animate away from the source. + mViewFlipper.setTranslationX(initialTranslation + currentTranslation); + } else { + // And in again over the remaining (100-X)%. + float fadeInFraction = MathUtils.constrainedMap( + /* rangeMin= */0.0f, + /* rangeMax= */1.0f, + /* valueMin= */switchPoint, + /* valueMax= */1.0f, + animation.getAnimatedFraction()); + + float opacity = fadeInInterpolator.getInterpolation(fadeInFraction); + mViewFlipper.setAlpha(opacity); + + // Fading back in, animate towards the destination. + mViewFlipper.setTranslationX(targetTranslation - translationRemaining); + } + + if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { + mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); + } + }); + + mRunningOneHandedAnimator.start(); + } else { + mViewFlipper.setTranslationX(targetTranslation); + } + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index d4d3d5b3ea2d..40352294ad88 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -32,7 +32,6 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.metrics.LogMaker; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.util.Slog; import android.view.MotionEvent; @@ -56,6 +55,7 @@ import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; +import com.android.systemui.util.settings.GlobalSettings; import javax.inject.Inject; @@ -78,6 +78,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final SecurityCallback mSecurityCallback; private final ConfigurationController mConfigurationController; private final FalsingCollector mFalsingCollector; + private final GlobalSettings mGlobalSettings; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -99,10 +100,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // If we're in one handed mode, the user can tap on the opposite side of the screen // to move the bouncer across. In that case, inhibit the falsing (otherwise the taps // to move the bouncer to each screen side can end up closing it instead). - if (mView.isOneHandedMode()) { - if ((mView.isOneHandedModeLeftAligned() && ev.getX() > mView.getWidth() / 2f) - || (!mView.isOneHandedModeLeftAligned() - && ev.getX() <= mView.getWidth() / 2f)) { + if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { + boolean isLeftAligned = mView.isOneHandedModeLeftAligned(); + if ((isLeftAligned && ev.getX() > mView.getWidth() / 2f) + || (!isLeftAligned && ev.getX() <= mView.getWidth() / 2f)) { mFalsingCollector.avoidGesture(); } } @@ -152,8 +153,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT; - if (canUseOneHandedBouncer()) { - bouncerSide = isOneHandedKeyguardLeftAligned() + if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { + bouncerSide = mView.isOneHandedModeLeftAligned() ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT; } @@ -230,7 +231,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SecurityCallback securityCallback, KeyguardSecurityViewFlipperController securityViewFlipperController, ConfigurationController configurationController, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, + GlobalSettings globalSettings) { super(view); mLockPatternUtils = lockPatternUtils; mUpdateMonitor = keyguardUpdateMonitor; @@ -245,6 +247,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mConfigurationController = configurationController; mLastOrientation = getResources().getConfiguration().orientation; mFalsingCollector = falsingCollector; + mGlobalSettings = globalSettings; } @Override @@ -324,7 +327,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onResume(int reason) { if (mCurrentSecurityMode != SecurityMode.None) { int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN; - if (canUseOneHandedBouncer()) { + if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { state = mView.isOneHandedModeLeftAligned() ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT; @@ -477,47 +480,41 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (newView != null) { newView.onResume(KeyguardSecurityView.VIEW_REVEALED); mSecurityViewFlipperController.show(newView); - configureOneHandedMode(); + configureMode(); } mSecurityCallback.onSecurityModeChanged( securityMode, newView != null && newView.needsInput()); } - /** Read whether the one-handed keyguard should be on the left/right from settings. */ - private boolean isOneHandedKeyguardLeftAligned() { - try { - return Settings.Global.getInt(mView.getContext().getContentResolver(), - Settings.Global.ONE_HANDED_KEYGUARD_SIDE) - == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; - } catch (Settings.SettingNotFoundException ex) { - return true; - } - } - + /** + * Returns whether the given security view should be used in a "one handed" way. This can be + * used to change how the security view is drawn (e.g. take up less of the screen, and align to + * one side). + */ private boolean canUseOneHandedBouncer() { - // Is it enabled? - if (!getResources().getBoolean( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) { - return false; - } - - if (!KeyguardSecurityModel.isSecurityViewOneHanded(mCurrentSecurityMode)) { + if (!(mCurrentSecurityMode == SecurityMode.Pattern + || mCurrentSecurityMode == SecurityMode.PIN)) { return false; } return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); } - private void configureOneHandedMode() { - boolean oneHandedMode = canUseOneHandedBouncer(); - - mView.setOneHandedMode(oneHandedMode); + private boolean canDisplayUserSwitcher() { + return getResources().getBoolean(R.bool.bouncer_display_user_switcher); + } - if (oneHandedMode) { - mView.setOneHandedModeLeftAligned( - isOneHandedKeyguardLeftAligned(), /* animate= */false); + private void configureMode() { + // One-handed mode and user-switcher are currently mutually exclusive, and enforced here + int mode = KeyguardSecurityContainer.MODE_DEFAULT; + if (canDisplayUserSwitcher()) { + mode = KeyguardSecurityContainer.MODE_USER_SWITCHER; + } else if (canUseOneHandedBouncer()) { + mode = KeyguardSecurityContainer.MODE_ONE_HANDED; } + + mView.initMode(mode, mGlobalSettings); } public void reportFailedUnlockAttempt(int userId, int timeoutMs) { @@ -584,15 +581,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard int newOrientation = getResources().getConfiguration().orientation; if (newOrientation != mLastOrientation) { mLastOrientation = newOrientation; - configureOneHandedMode(); + configureMode(); } } /** Update keyguard position based on a tapped X coordinate. */ public void updateKeyguardPosition(float x) { - if (mView.isOneHandedMode()) { - mView.setOneHandedModeLeftAligned(x <= mView.getWidth() / 2f, false); - } + mView.updatePositionByTouchX(x); } static class Factory { @@ -609,6 +604,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController; private final ConfigurationController mConfigurationController; private final FalsingCollector mFalsingCollector; + private final GlobalSettings mGlobalSettings; @Inject Factory(KeyguardSecurityContainer view, @@ -622,7 +618,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard KeyguardStateController keyguardStateController, KeyguardSecurityViewFlipperController securityViewFlipperController, ConfigurationController configurationController, - FalsingCollector falsingCollector) { + FalsingCollector falsingCollector, + GlobalSettings globalSettings) { mView = view; mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; mLockPatternUtils = lockPatternUtils; @@ -634,6 +631,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mSecurityViewFlipperController = securityViewFlipperController; mConfigurationController = configurationController; mFalsingCollector = falsingCollector; + mGlobalSettings = globalSettings; } public KeyguardSecurityContainerController create( @@ -642,7 +640,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, securityCallback, mSecurityViewFlipperController, - mConfigurationController, mFalsingCollector); + mConfigurationController, mFalsingCollector, mGlobalSettings); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java index 69328cd5d344..bacd29f661ae 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java @@ -94,13 +94,4 @@ public class KeyguardSecurityModel { throw new IllegalStateException("Unknown security quality:" + security); } } - - /** - * Returns whether the given security view should be used in a "one handed" way. This can be - * used to change how the security view is drawn (e.g. take up less of the screen, and align to - * one side). - */ - public static boolean isSecurityViewOneHanded(SecurityMode securityMode) { - return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN; - } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e24f07c21076..ba6771644db1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -144,7 +144,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final boolean DEBUG_FACE = Build.IS_DEBUGGABLE; private static final boolean DEBUG_FINGERPRINT = Build.IS_DEBUGGABLE; private static final boolean DEBUG_SPEW = false; - private static final int FINGERPRINT_LOCKOUT_RESET_DELAY_MS = 600; + private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600; private static final String ACTION_FACE_UNLOCK_STARTED = "com.android.facelock.FACE_UNLOCK_STARTED"; @@ -201,6 +201,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int BIOMETRIC_STATE_CANCELLING = 2; /** + * Action indicating keyguard *can* start biometric authentiation. + */ + private static final int BIOMETRIC_ACTION_START = 0; + /** + * Action indicating keyguard *can* stop biometric authentiation. + */ + private static final int BIOMETRIC_ACTION_STOP = 1; + /** + * Action indicating keyguard *can* start or stop biometric authentiation. + */ + private static final int BIOMETRIC_ACTION_UPDATE = 2; + + /** * Biometric state: During cancelling we got another request to start listening, so when we * receive the cancellation done signal, we should start listening again. */ @@ -339,13 +352,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Runnable mFpCancelNotReceived = () -> { Log.e(TAG, "Fp cancellation not received, transitioning to STOPPED"); mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); }; private final Runnable mFaceCancelNotReceived = () -> { Log.e(TAG, "Face cancellation not received, transitioning to STOPPED"); mFaceRunningState = BIOMETRIC_STATE_STOPPED; - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_STOP); }; private final Handler mHandler; @@ -365,7 +378,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onChanged(boolean enabled, int userId) throws RemoteException { mHandler.post(() -> { mBiometricEnabledForUser.put(userId, enabled); - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); }); } }; @@ -415,7 +428,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final KeyguardListenQueue mListenModels = new KeyguardListenQueue(); private static int sCurrentUser; - private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState; public synchronized static void setCurrentUser(int currentUser) { sCurrentUser = currentUser; @@ -428,8 +440,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onTrustChanged(boolean enabled, int userId, int flags) { Assert.isMainThread(); + boolean wasTrusted = mUserHasTrust.get(userId, false); mUserHasTrust.put(userId, enabled); - updateBiometricListeningState(); + // If there was no change in trusted state, make sure we are not authenticating. + // TrustManager sends an onTrustChanged whenever a user unlocks keyguard, for + // this reason we need to make sure to not authenticate. + if (wasTrusted == enabled) { + updateBiometricListeningState(BIOMETRIC_ACTION_STOP); + } else if (!enabled) { + updateBiometricListeningState(BIOMETRIC_ACTION_START); + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -594,7 +615,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setCredentialAttempted() { mCredentialAttempted = true; - updateBiometricListeningState(); + // Do not update face listening state in case of false authentication attempts. + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -602,7 +624,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -610,7 +632,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void setKeyguardOccluded(boolean occluded) { mKeyguardOccluded = occluded; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } @@ -622,7 +644,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void requestFaceAuthOnOccludingApp(boolean request) { mOccludingAppRequestingFace = request; - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -633,7 +655,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void requestFingerprintAuthOnOccludingApp(boolean request) { mOccludingAppRequestingFp = request; - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -641,7 +663,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ public void onCameraLaunched() { mSecureCameraLaunched = true; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -676,7 +698,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } // Don't send cancel if authentication succeeds mFingerprintCancelSignal = null; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -772,7 +794,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " + mHardwareFingerprintUnavailableRetryCount); if (mFpm.isHardwareDetected()) { - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { mHardwareFingerprintUnavailableRetryCount++; mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT); @@ -792,7 +814,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED && mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } else { setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); } @@ -801,16 +823,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT); } + boolean lockedOutStateChanged = false; if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOutPermanent; mFingerprintLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged |= !mFingerprintLockedOut; mFingerprintLockedOut = true; if (isUdfpsEnrolled()) { - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } } @@ -820,9 +845,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + } } private void handleFingerprintLockoutReset() { + boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent; mFingerprintLockedOut = false; mFingerprintLockedOutPermanent = false; @@ -832,10 +862,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // that the events will arrive in a particular order. Add a delay here in case // an unlock is in progress. In this is a normal unlock the extra delay won't // be noticeable. - mHandler.postDelayed(this::updateFingerprintListeningState, - FINGERPRINT_LOCKOUT_RESET_DELAY_MS); + mHandler.postDelayed(() -> { + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS); } else { - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + } + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); } } @@ -875,7 +910,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } // Don't send cancel if authentication succeeds mFaceCancelSignal = null; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -968,7 +1003,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void run() { Log.w(TAG, "Retrying face after HW unavailable, attempt " + mHardwareFaceUnavailableRetryCount); - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); } }; @@ -985,7 +1020,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FaceManager.FACE_ERROR_CANCELED && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { setFaceRunningState(BIOMETRIC_STATE_STOPPED); - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); } else { setFaceRunningState(BIOMETRIC_STATE_STOPPED); } @@ -999,7 +1034,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + boolean lockedOutStateChanged = false; if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { + lockedOutStateChanged = !mFaceLockedOutPermanent; mFaceLockedOutPermanent = true; requireStrongAuthIfAllLockedOut(); } @@ -1011,11 +1048,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab BiometricSourceType.FACE); } } + + if (lockedOutStateChanged) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void handleFaceLockoutReset() { + boolean changed = mFaceLockedOutPermanent; mFaceLockedOutPermanent = false; - updateFaceListeningState(); + + mHandler.postDelayed(() -> { + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); + }, BIOMETRIC_LOCKOUT_RESET_DELAY_MS); + + if (changed) { + notifyLockedOutStateChanged(BiometricSourceType.FACE); + } } private void setFaceRunningState(int faceRunningState) { @@ -1237,6 +1286,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void notifyLockedOutStateChanged(BiometricSourceType type) { + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onLockedOutStateChanged(type); + } + } + } + public boolean isScreenOn() { return mScreenOn; } @@ -1254,7 +1313,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @VisibleForTesting void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } static class DisplayClientState { @@ -1593,7 +1652,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected void handleStartedWakingUp() { Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp"); Assert.isMainThread(); - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1614,7 +1673,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } mGoingToSleep = true; - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } protected void handleFinishedGoingToSleep(int arg1) { @@ -1626,7 +1685,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onFinishedGoingToSleep(arg1); } } - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } private void handleScreenTurnedOn() { @@ -1663,7 +1722,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onDreamingStateChanged(mIsDreaming); } } - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } private void handleUserInfoChanged(int userId) { @@ -1854,7 +1913,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setAssistantVisible((boolean) msg.obj); break; case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE: - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); break; case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED: updateLogoutEnabled(); @@ -1972,10 +2031,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onEnrollmentsChanged() { - mainExecutor.execute(() -> updateBiometricListeningState()); + mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE)); } }); - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); if (mFpm != null) { mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback); } @@ -2089,12 +2148,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED); } - private void updateBiometricListeningState() { - updateFingerprintListeningState(); - updateFaceListeningState(); + private void updateBiometricListeningState(int action) { + updateFingerprintListeningState(action); + updateFaceListeningState(action); } - private void updateFingerprintListeningState() { + private void updateFingerprintListeningState(int action) { // If this message exists, we should not authenticate again until this message is // consumed by the handler if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { @@ -2106,8 +2165,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; if (runningOrRestarting && !shouldListenForFingerprint) { + if (action == BIOMETRIC_ACTION_START) { + Log.v(TAG, "Ignoring stopListeningForFingerprint()"); + return; + } stopListeningForFingerprint(); } else if (!runningOrRestarting && shouldListenForFingerprint) { + if (action == BIOMETRIC_ACTION_STOP) { + Log.v(TAG, "Ignoring startListeningForFingerprint()"); + return; + } startListeningForFingerprint(); } } @@ -2136,7 +2203,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } mAuthInterruptActive = active; - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -2147,7 +2214,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void requestFaceAuth(boolean userInitiatedRequest) { if (DEBUG) Log.d(TAG, "requestFaceAuth() userInitiated=" + userInitiatedRequest); mIsFaceAuthUserRequested |= userInitiatedRequest; - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); } public boolean isFaceAuthUserRequested() { @@ -2161,7 +2228,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab stopListeningForFace(); } - private void updateFaceListeningState() { + private void updateFaceListeningState(int action) { // If this message exists, we should not authenticate again until this message is // consumed by the handler if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { @@ -2170,9 +2237,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mHandler.removeCallbacks(mRetryFaceAuthentication); boolean shouldListenForFace = shouldListenForFace(); if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) { + if (action == BIOMETRIC_ACTION_START) { + Log.v(TAG, "Ignoring stopListeningForFace()"); + return; + } mIsFaceAuthUserRequested = false; stopListeningForFace(); } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING && shouldListenForFace) { + if (action == BIOMETRIC_ACTION_STOP) { + Log.v(TAG, "Ignoring startListeningForFace()"); + return; + } startListeningForFace(); } } @@ -2380,7 +2455,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockIconPressed = true; final int userId = getCurrentUser(); mUserFaceAuthenticated.put(userId, null); - updateFaceListeningState(); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE); mStrongAuthTracker.onStrongAuthRequiredChanged(userId); } @@ -2454,6 +2529,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + public boolean isFingerprintLockedOut() { + return mFingerprintLockedOut || mFingerprintLockedOutPermanent; + } + /** * If biometrics hardware is available, not disabled, and user has enrolled templates. * This does NOT check if the device is encrypted or in lockdown. @@ -2552,7 +2631,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private void handleDevicePolicyManagerStateChanged(int userId) { Assert.isMainThread(); - updateFingerprintListeningState(); + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); updateSecondaryLockscreenRequirement(userId); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -2842,7 +2921,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onKeyguardVisibilityChangedRaw(showing); } } - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } /** Notifies that the occluded state changed. */ @@ -2864,7 +2943,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private void handleKeyguardReset() { if (DEBUG) Log.d(TAG, "handleKeyguardReset"); - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition(); } @@ -2910,7 +2989,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onKeyguardBouncerChanged(mBouncer); } } - updateBiometricListeningState(); + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); } /** @@ -3030,7 +3109,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void setSwitchingUser(boolean switching) { mSwitchingUser = switching; // Since this comes in on a binder thread, we need to post if first - mHandler.post(mUpdateBiometricListeningState); + mHandler.post(() -> { + updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE); + }); } private void sendUpdates(KeyguardUpdateMonitorCallback callback) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 12431984c9b9..8170a81a09e6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -292,6 +292,11 @@ public class KeyguardUpdateMonitorCallback { public void onStrongAuthStateChanged(int userId) { } /** + * When the current user's locked out state changed. + */ + public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { } + + /** * Called when the dream's window state is changed. * @param dreaming true if the dream's window has been created and is visible */ diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 68132f4c598b..b2ecc6140460 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.drawable.Drawable; @@ -30,6 +31,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -82,14 +84,18 @@ public class LockIconView extends FrameLayout implements Dumpable { void updateColorAndBackgroundVisibility() { if (mUseBackground && mLockIcon.getDrawable() != null) { - mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), - android.R.attr.textColorPrimary); + mLockIconColor = ColorUtils.blendARGB( + Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary), + Color.WHITE, + mDozeAmount); mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); mBgView.setAlpha(1f - mDozeAmount); mBgView.setVisibility(View.VISIBLE); } else { - mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), - R.attr.wallpaperTextColorAccent); + mLockIconColor = ColorUtils.blendARGB( + Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent), + Color.WHITE, + mDozeAmount); mBgView.setVisibility(View.GONE); } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 8a997284b26f..251c1e632f95 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -121,7 +121,8 @@ public class SystemUIFactory { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())); + .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())) + .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -140,7 +141,8 @@ public class SystemUIFactory { .setStartingSurface(Optional.ofNullable(null)) .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) - .setSizeCompatUI(Optional.ofNullable(null)); + .setSizeCompatUI(Optional.ofNullable(null)) + .setDragAndDrop(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java index c941d662dfd9..e4e0da6a2310 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SecureSettingsContentObserver.java @@ -71,7 +71,9 @@ public abstract class SecureSettingsContentObserver<T> { public void addListener(@NonNull T listener) { Objects.requireNonNull(listener, "listener must be non-null"); - mListeners.add(listener); + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } if (mListeners.size() == 1) { mContentResolver.registerContentObserver( diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 794b9dd5b68b..a10efa982701 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -158,12 +158,13 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie @MainThread void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable IRemoteMagnificationAnimationCallback callback) { final WindowMagnificationController windowMagnificationController = mMagnificationControllerSupplier.get(displayId); if (windowMagnificationController != null) { - windowMagnificationController.enableWindowMagnification(scale, centerX, - centerY, callback); + windowMagnificationController.enableWindowMagnification(scale, centerX, centerY, + magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback); } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java index 1bfa9c1a2a51..dc1e0054ff24 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java @@ -62,6 +62,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp private final ValueAnimator mValueAnimator; private final AnimationSpec mStartSpec = new AnimationSpec(); private final AnimationSpec mEndSpec = new AnimationSpec(); + private float mMagnificationFrameOffsetRatioX = 0f; + private float mMagnificationFrameOffsetRatioY = 0f; private final Context mContext; // Called when the animation is ended successfully without cancelling or mStartSpec and // mEndSpec are equal. @@ -88,7 +90,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp } /** - * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)} + * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float, + * float, float, IRemoteMagnificationAnimationCallback)} * with transition animation. If the window magnification is not enabled, the scale will start * from 1.0 and the center won't be changed during the animation. If {@link #mState} is * {@code STATE_DISABLING}, the animation runs in reverse. @@ -106,16 +109,48 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp */ void enableWindowMagnification(float scale, float centerX, float centerY, @Nullable IRemoteMagnificationAnimationCallback animationCallback) { + enableWindowMagnification(scale, centerX, centerY, 0f, 0f, animationCallback); + } + + /** + * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float, + * float, float, IRemoteMagnificationAnimationCallback)} + * with transition animation. If the window magnification is not enabled, the scale will start + * from 1.0 and the center won't be changed during the animation. If {@link #mState} is + * {@code STATE_DISABLING}, the animation runs in reverse. + * + * @param scale The target scale, or {@link Float#NaN} to leave unchanged. + * @param centerX The screen-relative X coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param centerY The screen-relative Y coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset between + * frame position X and centerX + * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset between + * frame position Y and centerY + * @param animationCallback Called when the transition is complete, the given arguments + * are as same as current values, or the transition is interrupted + * due to the new transition request. + * + * @see #onAnimationUpdate(ValueAnimator) + */ + void enableWindowMagnification(float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, + @Nullable IRemoteMagnificationAnimationCallback animationCallback) { if (mController == null) { return; } sendAnimationCallback(false); + mMagnificationFrameOffsetRatioX = magnificationFrameOffsetRatioX; + mMagnificationFrameOffsetRatioY = magnificationFrameOffsetRatioY; + // Enable window magnification without animation immediately. if (animationCallback == null) { if (mState == STATE_ENABLING || mState == STATE_DISABLING) { mValueAnimator.cancel(); } - mController.enableWindowMagnification(scale, centerX, centerY); + mController.enableWindowMagnificationInternal(scale, centerX, centerY, + mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY); setState(STATE_ENABLED); return; } @@ -123,7 +158,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp setupEnableAnimationSpecs(scale, centerX, centerY); if (mEndSpec.equals(mStartSpec)) { if (mState == STATE_DISABLED) { - mController.enableWindowMagnification(scale, centerX, centerY); + mController.enableWindowMagnificationInternal(scale, centerX, centerY, + mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY); } else if (mState == STATE_ENABLING || mState == STATE_DISABLING) { mValueAnimator.cancel(); } @@ -273,7 +309,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract; final float centerY = mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract; - mController.enableWindowMagnification(sentScale, centerX, centerY); + mController.enableWindowMagnificationInternal(sentScale, centerX, centerY, + mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY); } private static ValueAnimator newValueAnimator(Resources resources) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index 92cd8b183b62..2133da202ce9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -49,11 +49,13 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } @Override - public void enableWindowMagnification(int displayId, float scale, float centerX, - float centerY, IRemoteMagnificationAnimationCallback callback) { + public void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, + IRemoteMagnificationAnimationCallback callback) { mHandler.post( () -> mWindowMagnification.enableWindowMagnification(displayId, scale, centerX, - centerY, callback)); + centerY, magnificationFrameOffsetRatioX, + magnificationFrameOffsetRatioY, callback)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index 250700487b7f..b064ba904120 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -95,17 +95,46 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold @Surface.Rotation @VisibleForTesting int mRotation; - private final Rect mMagnificationFrame = new Rect(); private final SurfaceControl.Transaction mTransaction; private final WindowManager mWm; private float mScale; + /** + * MagnificationFrame represents the bound of {@link #mMirrorSurface} and is constrained + * by the {@link #mMagnificationFrameBoundary}. + * We use MagnificationFrame to calculate the position of {@link #mMirrorView}. + * We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and + * {@link #mMagnificationFrameOffsetY} to calculate the position of {@link #mSourceBounds}. + */ + private final Rect mMagnificationFrame = new Rect(); private final Rect mTmpRect = new Rect(); + + /** + * MirrorViewBounds is the bound of the {@link #mMirrorView} which displays the magnified + * content. + * {@link #mMirrorView}'s center is equal to {@link #mMagnificationFrame}'s center. + */ private final Rect mMirrorViewBounds = new Rect(); + + /** + * SourceBound is the bound of the magnified region which projects the magnified content. + * SourceBound's center is equal to the parameters centerX and centerY in + * {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float, float)}} + * but it is calculated from {@link #mMagnificationFrame}'s center in the runtime. + */ private final Rect mSourceBounds = new Rect(); + /** + * The relation of centers between {@link #mSourceBounds} and {@link #mMagnificationFrame} is + * calculated in {@link #calculateSourceBounds(Rect, float)} and the equations are as following: + * MagnificationFrame = SourceBound (e.g., centerX & centerY) + MagnificationFrameOffset + * SourceBound = MagnificationFrame - MagnificationFrameOffset + */ + private int mMagnificationFrameOffsetX = 0; + private int mMagnificationFrameOffsetY = 0; + // The root of the mirrored content private SurfaceControl mMirrorSurface; @@ -123,6 +152,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final Runnable mMirrorViewRunnable; private final Runnable mUpdateStateDescriptionRunnable; private final Runnable mWindowInsetChangeRunnable; + // MirrorView is the mirror window which displays the magnified content. private View mMirrorView; private SurfaceView mMirrorSurfaceView; private int mMirrorSurfaceMargin; @@ -339,7 +369,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold // window size changed not caused by rotation. if (isWindowVisible() && reCreateWindow) { deleteWindowMagnification(); - enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN); + enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); } } @@ -633,6 +663,26 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale)); int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale)); mSourceBounds.set(left, top, right, bottom); + + // SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's + // center. The relation between SourceBound and MagnificationFrame is as following: + // MagnificationFrame = SourceBound (center[X,Y]) + MagnificationFrameOffset + // SourceBound = MagnificationFrame - MagnificationFrameOffset + mSourceBounds.offset(-mMagnificationFrameOffsetX, -mMagnificationFrameOffsetY); + + if (mSourceBounds.left < 0) { + mSourceBounds.offsetTo(0, mSourceBounds.top); + } else if (mSourceBounds.right > mWindowBounds.width()) { + mSourceBounds.offsetTo(mWindowBounds.width() - mSourceBounds.width(), + mSourceBounds.top); + } + + if (mSourceBounds.top < 0) { + mSourceBounds.offsetTo(mSourceBounds.left, 0); + } else if (mSourceBounds.bottom > mWindowBounds.height()) { + mSourceBounds.offsetTo(mSourceBounds.left, + mWindowBounds.height() - mSourceBounds.height()); + } } private void calculateMagnificationFrameBoundary() { @@ -646,11 +696,31 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold final int scaledWidth = (int) (halfWidth / mScale); // The scaled half height of magnified region. final int scaledHeight = (int) (halfHeight / mScale); - final int exceededWidth = halfWidth - scaledWidth; - final int exceededHeight = halfHeight - scaledHeight; - mMagnificationFrameBoundary.set(-exceededWidth, -exceededHeight, - mWindowBounds.width() + exceededWidth, mWindowBounds.height() + exceededHeight); + // MagnificationFrameBoundary constrain the space of MagnificationFrame, and it also has + // to leave enough space for SourceBound to magnify the whole screen space. + // However, there is an offset between SourceBound and MagnificationFrame. + // The relation between SourceBound and MagnificationFrame is as following: + // SourceBound = MagnificationFrame - MagnificationFrameOffset + // Therefore, we have to adjust the exceededBoundary based on the offset. + // + // We have to increase the offset space for the SourceBound edges which are located in + // the MagnificationFrame. For example, if the offsetX and offsetY are negative, which + // means SourceBound is at right-bottom size of MagnificationFrame, the left and top + // edges of SourceBound are located in MagnificationFrame. So, we have to leave extra + // offset space at left and top sides and don't have to leave extra space at right and + // bottom sides. + final int exceededLeft = Math.max(halfWidth - scaledWidth - mMagnificationFrameOffsetX, 0); + final int exceededRight = Math.max(halfWidth - scaledWidth + mMagnificationFrameOffsetX, 0); + final int exceededTop = Math.max(halfHeight - scaledHeight - mMagnificationFrameOffsetY, 0); + final int exceededBottom = Math.max(halfHeight - scaledHeight + mMagnificationFrameOffsetY, + 0); + + mMagnificationFrameBoundary.set( + -exceededLeft, + -exceededTop, + mWindowBounds.width() + exceededRight, + mWindowBounds.height() + exceededBottom); } /** @@ -711,24 +781,30 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } /** - * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)} + * Wraps {@link WindowMagnificationController#enableWindowMagnificationInternal(float, float, + * float, float, float)} * with transition animation. If the window magnification is not enabled, the scale will start * from 1.0 and the center won't be changed during the animation. If animator is * {@code STATE_DISABLING}, the animation runs in reverse. * * @param scale The target scale, or {@link Float#NaN} to leave unchanged. - * @param centerX The screen-relative X coordinate around which to center, + * @param centerX The screen-relative X coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. - * @param centerY The screen-relative Y coordinate around which to center, + * @param centerY The screen-relative Y coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. + * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset + * between frame position X and centerX + * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset + * between frame position Y and centerY * @param animationCallback Called when the transition is complete, the given arguments * are as same as current values, or the transition is interrupted * due to the new transition request. */ void enableWindowMagnification(float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable IRemoteMagnificationAnimationCallback animationCallback) { - mAnimationController.enableWindowMagnification(scale, centerX, - centerY, animationCallback); + mAnimationController.enableWindowMagnification(scale, centerX, centerY, + magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, animationCallback); } /** @@ -738,21 +814,56 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold * be consistent with the behavior of display magnification. * * @param scale the target scale, or {@link Float#NaN} to leave unchanged - * @param centerX the screen-relative X coordinate around which to center, + * @param centerX the screen-relative X coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. - * @param centerY the screen-relative Y coordinate around which to center, + * @param centerY the screen-relative Y coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. */ - void enableWindowMagnification(float scale, float centerX, float centerY) { + void enableWindowMagnificationInternal(float scale, float centerX, float centerY) { + enableWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN); + } + + /** + * Enables window magnification with specified parameters. If the given scale is <strong>less + * than or equal to 1.0f<strong>, then + * {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to + * be consistent with the behavior of display magnification. + * + * @param scale the target scale, or {@link Float#NaN} to leave unchanged + * @param centerX the screen-relative X coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param centerY the screen-relative Y coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset + * between frame position X and centerX, + * or {@link Float#NaN} to leave unchanged. + * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset + * between frame position Y and centerY, + * or {@link Float#NaN} to leave unchanged. + */ + void enableWindowMagnificationInternal(float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) { if (Float.compare(scale, 1.0f) <= 0) { deleteWindowMagnification(); return; } + mMagnificationFrameOffsetX = Float.isNaN(magnificationFrameOffsetRatioX) + ? mMagnificationFrameOffsetX + : (int) (mMagnificationFrame.width() / 2 * magnificationFrameOffsetRatioX); + mMagnificationFrameOffsetY = Float.isNaN(magnificationFrameOffsetRatioY) + ? mMagnificationFrameOffsetY + : (int) (mMagnificationFrame.height() / 2 * magnificationFrameOffsetRatioY); + + // The relation of centers between SourceBound and MagnificationFrame is as following: + // MagnificationFrame = SourceBound (e.g., centerX & centerY) + MagnificationFrameOffset + final float newMagnificationFrameCenterX = centerX + mMagnificationFrameOffsetX; + final float newMagnificationFrameCenterY = centerY + mMagnificationFrameOffsetY; + final float offsetX = Float.isNaN(centerX) ? 0 - : centerX - mMagnificationFrame.exactCenterX(); + : newMagnificationFrameCenterX - mMagnificationFrame.exactCenterX(); final float offsetY = Float.isNaN(centerY) ? 0 - : centerY - mMagnificationFrame.exactCenterY(); + : newMagnificationFrameCenterY - mMagnificationFrame.exactCenterY(); mScale = Float.isNaN(scale) ? mScale : scale; calculateMagnificationFrameBoundary(); @@ -774,7 +885,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (mAnimationController.isAnimating() || !isWindowVisible() || mScale == scale) { return; } - enableWindowMagnification(scale, Float.NaN, Float.NaN); + enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN); mHandler.removeCallbacks(mUpdateStateDescriptionRunnable); mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java index cff6cf1f53f1..cc5a792e89a1 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java @@ -98,7 +98,8 @@ public class AccessibilityFloatingMenuController implements mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - init(); + mIsKeyguardVisible = false; + mIsAccessibilityManagerServiceReady = false; } /** @@ -124,9 +125,8 @@ public class AccessibilityFloatingMenuController implements handleFloatingMenuVisibility(mIsKeyguardVisible, mBtnMode, mBtnTargets); } - private void init() { - mIsKeyguardVisible = false; - mIsAccessibilityManagerServiceReady = false; + /** Initializes the AccessibilityFloatingMenuController configurations. */ + public void init() { mBtnMode = mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode(); mBtnTargets = mAccessibilityButtonTargetsObserver.getCurrentAccessibilityButtonTargets(); registerContentObservers(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 0ff3dbc302a0..3f077f570876 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -163,7 +163,10 @@ public class UdfpsController implements DozeReceiver { private boolean mOnFingerDown; private boolean mAttemptedToDismissKeyguard; private Set<Callback> mCallbacks = new HashSet<>(); - private final VibrationEffect mLowTick; + + // by default, use low tick + private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK; + private final VibrationEffect mTick; @VisibleForTesting public static final VibrationAttributes VIBRATION_ATTRIBUTES = @@ -571,7 +574,7 @@ public class UdfpsController implements DozeReceiver { mConfigurationController = configurationController; mSystemClock = systemClock; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mLowTick = lowTick(); + mTick = lowTick(); mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists @@ -607,22 +610,31 @@ public class UdfpsController implements DozeReceiver { } private VibrationEffect lowTick() { + boolean useLowTickDefault = mContext.getResources() + .getBoolean(R.bool.config_udfpsUseLowTick); + if (Settings.Global.getFloat( + mContext.getContentResolver(), + "tick-low", useLowTickDefault ? 1 : 0) == 0) { + mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK; + } float tickIntensity = Settings.Global.getFloat( - mContext.getContentResolver(), "low-tick-intensity", .5f); - VibrationEffect.Composition composition = VibrationEffect.startComposition(); - composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - tickIntensity, 0); + mContext.getContentResolver(), + "tick-intensity", + mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity)); int tickDelay = Settings.Global.getInt( - mContext.getContentResolver(), "low-tick-delay", 25); + mContext.getContentResolver(), + "tick-delay", + mContext.getResources().getInteger(R.integer.config_udfpsTickDelay)); + + VibrationEffect.Composition composition = VibrationEffect.startComposition(); + composition.addPrimitive(mPrimitiveTick, tickIntensity, 0); int primitives = 1000 / tickDelay; float[] rampUp = new float[]{.48f, .58f, .69f, .83f}; for (int i = 0; i < rampUp.length; i++) { - composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - tickIntensity * rampUp[i], tickDelay); + composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay); } for (int i = rampUp.length; i < primitives; i++) { - composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, - tickIntensity, tickDelay); + composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay); } return composition.compose(); } @@ -636,7 +648,7 @@ public class UdfpsController implements DozeReceiver { mVibrator.vibrate( Process.myUid(), mContext.getOpPackageName(), - mLowTick, + mTick, "udfps-onStart-tick", VIBRATION_ATTRIBUTES); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 223eb78044c4..8f4d6f6aa973 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -255,7 +255,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private void maybeShowInputBouncer() { if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { mKeyguardViewManager.showBouncer(true); - mKeyguardViewManager.resetAlternateAuth(false); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt new file mode 100644 index 000000000000..b5d81f253916 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.dagger + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.util.concurrency.ThreadFactory +import dagger.Module +import dagger.Provides +import java.util.concurrent.Executor +import javax.inject.Qualifier + +/** + * Dagger module for all things biometric. + */ +@Module +object BiometricsModule { + + /** Background [Executor] for HAL related operations. */ + @Provides + @SysUISingleton + @JvmStatic + @BiometricsBackground + fun providesPluginExecutor(threadFactory: ThreadFactory): Executor = + threadFactory.buildExecutorOnNewThread("biometrics") +} + +/** + * Background executor for HAL operations that are latency sensitive but too + * slow to run on the main thread. Prefer the shared executors, such as + * [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved. + */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class BiometricsBackground diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 5fdf026b86f3..38e4d78920bc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -23,6 +23,7 @@ import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; +import com.android.systemui.media.taptotransfer.MediaTttChipController; import com.android.systemui.people.PeopleProvider; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.SysUIUnfoldComponent; @@ -32,6 +33,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -111,6 +113,9 @@ public interface SysUIComponent { @BindsInstance Builder setSizeCompatUI(Optional<SizeCompatUI> s); + @BindsInstance + Builder setDragAndDrop(Optional<DragAndDrop> d); + SysUIComponent build(); } @@ -126,6 +131,8 @@ public interface SysUIComponent { c.getUnfoldTransitionWallpaperController().init(); }); getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init()); + // No init method needed, just needs to be gotten so that it's created. + getMediaTttChipController(); } /** @@ -172,6 +179,9 @@ public interface SysUIComponent { */ Optional<NaturalRotationUnfoldProgressProvider> getNaturalRotationUnfoldProgressProvider(); + /** */ + Optional<MediaTttChipController> getMediaTttChipController(); + /** * Member injection into the supplied argument. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index a64351fd8940..1d17fd89bb45 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -30,6 +30,7 @@ import com.android.systemui.SystemUIFactory; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.UdfpsHbmProvider; +import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.controls.dagger.ControlsModule; @@ -66,7 +67,6 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; @@ -101,6 +101,7 @@ import dagger.Provides; @Module(includes = { AppOpsModule.class, AssistModule.class, + BiometricsModule.class, ClockModule.class, CommunalModule.class, DreamModule.class, @@ -127,7 +128,6 @@ import dagger.Provides; }, subcomponents = { StatusBarComponent.class, - StatusBarFragmentComponent.class, NotificationRowComponent.class, DozeComponent.class, ExpandableNotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 543ba8f9b854..90a3ad225f51 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -29,6 +29,7 @@ import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; +import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -119,4 +120,7 @@ public interface WMComponent { @WMSingleton SizeCompatUI getSizeCompatUI(); + + @WMSingleton + DragAndDrop getDragAndDrop(); } diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt new file mode 100644 index 000000000000..42f3512129a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialog.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.fgsmanager + +import android.content.Context +import android.os.Bundle +import android.text.format.DateUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.GuardedBy +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.fgsmanager.FgsManagerDialogController.RunningApp +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.time.SystemClock +import java.util.concurrent.Executor + +/** + * Dialog which shows a list of running foreground services and offers controls to them + */ +class FgsManagerDialog( + context: Context, + private val executor: Executor, + @Background private val backgroundExecutor: Executor, + private val systemClock: SystemClock, + private val fgsManagerDialogController: FgsManagerDialogController +) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog) { + + private val appListRecyclerView: RecyclerView = RecyclerView(this.context) + private val adapter: AppListAdapter = AppListAdapter() + + init { + setTitle(R.string.fgs_manager_dialog_title) + setView(appListRecyclerView) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + appListRecyclerView.layoutManager = LinearLayoutManager(context) + fgsManagerDialogController.registerDialogForChanges( + object : FgsManagerDialogController.FgsManagerDialogCallback { + override fun onRunningAppsChanged(apps: List<RunningApp>) { + executor.execute { + adapter.setData(apps) + } + } + } + ) + appListRecyclerView.adapter = adapter + backgroundExecutor.execute { adapter.setData(fgsManagerDialogController.runningAppList) } + } + + private inner class AppListAdapter : RecyclerView.Adapter<AppItemViewHolder>() { + private val lock = Any() + + @GuardedBy("lock") + private val data: MutableList<RunningApp> = ArrayList() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder { + return AppItemViewHolder(LayoutInflater.from(context) + .inflate(R.layout.fgs_manager_app_item, parent, false)) + } + + override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) { + var runningApp: RunningApp + synchronized(lock) { + runningApp = data[position] + } + with(holder) { + iconView.setImageDrawable(runningApp.mIcon) + appLabelView.text = runningApp.mAppLabel + durationView.text = DateUtils.formatDuration( + Math.max(systemClock.elapsedRealtime() - runningApp.mTimeStarted, 60000), + DateUtils.LENGTH_MEDIUM) + stopButton.setOnClickListener { + fgsManagerDialogController + .stopAllFgs(runningApp.mUserId, runningApp.mPackageName) + } + } + } + + override fun getItemCount(): Int { + synchronized(lock) { return data.size } + } + + fun setData(newData: List<RunningApp>) { + var oldData: List<RunningApp> + synchronized(lock) { + oldData = ArrayList(data) + data.clear() + data.addAll(newData) + } + + DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return oldData.size + } + + override fun getNewListSize(): Int { + return newData.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): + Boolean { + return oldData[oldItemPosition] == newData[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): + Boolean { + return true // TODO, look into updating the time subtext + } + }).dispatchUpdatesTo(this) + } + } + + private class AppItemViewHolder(parent: View) : RecyclerView.ViewHolder(parent) { + val appLabelView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_label) + val durationView: TextView = parent.requireViewById(R.id.fgs_manager_app_item_duration) + val iconView: ImageView = parent.requireViewById(R.id.fgs_manager_app_item_icon) + val stopButton: Button = parent.requireViewById(R.id.fgs_manager_app_item_stop_button) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt new file mode 100644 index 000000000000..159ed39025a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogController.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.fgsmanager + +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.graphics.drawable.Drawable +import android.os.Handler +import android.os.UserHandle +import android.util.ArrayMap +import android.util.Log +import androidx.annotation.GuardedBy +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.RunningFgsController +import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime +import javax.inject.Inject + +/** + * Controls events relevant to FgsManagerDialog + */ +class FgsManagerDialogController @Inject constructor( + private val packageManager: PackageManager, + @Background private val backgroundHandler: Handler, + private val runningFgsController: RunningFgsController +) : RunningFgsController.Callback { + private val lock = Any() + private val clearCacheToken = Any() + + @GuardedBy("lock") + private var runningApps: Map<UserPackageTime, RunningApp>? = null + @GuardedBy("lock") + private var listener: FgsManagerDialogCallback? = null + + interface FgsManagerDialogCallback { + fun onRunningAppsChanged(apps: List<RunningApp>) + } + + data class RunningApp( + val mUserId: Int, + val mPackageName: String, + val mAppLabel: CharSequence, + val mIcon: Drawable, + val mTimeStarted: Long + ) + + val runningAppList: List<RunningApp> + get() { + synchronized(lock) { + if (runningApps == null) { + onFgsPackagesChangedLocked(runningFgsController.getPackagesWithFgs()) + } + return convertToRunningAppList(runningApps!!) + } + } + + fun registerDialogForChanges(callback: FgsManagerDialogCallback) { + synchronized(lock) { + runningFgsController.addCallback(this) + listener = callback + backgroundHandler.removeCallbacksAndMessages(clearCacheToken) + } + } + + fun onFinishDialog() { + synchronized(lock) { + listener = null + // Keep data such as icons cached for some time since loading can be slow + backgroundHandler.postDelayed( + { + synchronized(lock) { + runningFgsController.removeCallback(this) + runningApps = null + } + }, clearCacheToken, RUNNING_APP_CACHE_TIMEOUT_MILLIS) + } + } + + private fun onRunningAppsChanged(apps: ArrayMap<UserPackageTime, RunningApp>) { + listener?.let { + backgroundHandler.post { it.onRunningAppsChanged(convertToRunningAppList(apps)) } + } + } + + override fun onFgsPackagesChanged(packages: List<UserPackageTime>) { + backgroundHandler.post { + synchronized(lock) { onFgsPackagesChangedLocked(packages) } + } + } + + /** + * Run on background thread + */ + private fun onFgsPackagesChangedLocked(packages: List<UserPackageTime>) { + val newRunningApps = ArrayMap<UserPackageTime, RunningApp>() + for (packageWithFgs in packages) { + val ra = runningApps?.get(packageWithFgs) + if (ra == null) { + val userId = packageWithFgs.userId + val packageName = packageWithFgs.packageName + try { + val ai = packageManager.getApplicationInfo(packageName, 0) + var icon = packageManager.getApplicationIcon(ai) + icon = packageManager.getUserBadgedIcon(icon, + UserHandle.of(userId)) + val label = packageManager.getApplicationLabel(ai) + newRunningApps[packageWithFgs] = RunningApp(userId, packageName, + label, icon, packageWithFgs.startTimeMillis) + } catch (e: NameNotFoundException) { + Log.e(LOG_TAG, + "Application info not found: $packageName", e) + } + } else { + newRunningApps[packageWithFgs] = ra + } + } + runningApps = newRunningApps + onRunningAppsChanged(newRunningApps) + } + + fun stopAllFgs(userId: Int, packageName: String) { + runningFgsController.stopFgs(userId, packageName) + } + + companion object { + private val LOG_TAG = FgsManagerDialogController::class.java.simpleName + private const val RUNNING_APP_CACHE_TIMEOUT_MILLIS: Long = 20_000 + + private fun convertToRunningAppList(apps: Map<UserPackageTime, RunningApp>): + List<RunningApp> { + val result = mutableListOf<RunningApp>() + result.addAll(apps.values) + result.sortWith { a: RunningApp, b: RunningApp -> + b.mTimeStarted.compareTo(a.mTimeStarted) + } + return result + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt new file mode 100644 index 000000000000..28749296c4cf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fgsmanager/FgsManagerDialogFactory.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.fgsmanager + +import android.content.Context +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.animation.DialogLaunchAnimator +import android.content.DialogInterface +import android.view.View +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.util.time.SystemClock +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Factory to create [FgsManagerDialog] instances + */ +@SysUISingleton +class FgsManagerDialogFactory +@Inject constructor( + private val context: Context, + @Main private val executor: Executor, + @Background private val backgroundExecutor: Executor, + private val systemClock: SystemClock, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val fgsManagerDialogController: FgsManagerDialogController +) { + + val lock = Any() + + companion object { + private var fgsManagerDialog: FgsManagerDialog? = null + } + + /** + * Creates the dialog if it doesn't exist + */ + fun create(viewLaunchedFrom: View?) { + if (fgsManagerDialog == null) { + fgsManagerDialog = FgsManagerDialog(context, executor, backgroundExecutor, + systemClock, fgsManagerDialogController) + fgsManagerDialog!!.setOnDismissListener { i: DialogInterface? -> + fgsManagerDialogController.onFinishDialog() + fgsManagerDialog = null + } + dialogLaunchAnimator.showFromView(fgsManagerDialog!!, viewLaunchedFrom!!) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index a1413f9e1b74..458cdc1f7814 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -46,6 +46,10 @@ public class Flags { public static final BooleanFlag NOTIFICATION_UPDATES = new BooleanFlag(102, true); + public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = + new BooleanFlag(103, false); + + /***************************************/ // 200 - keyguard/lockscreen public static final BooleanFlag KEYGUARD_LAYOUT = @@ -114,6 +118,10 @@ public class Flags { public static final BooleanFlag MONET = new BooleanFlag(800, true, R.bool.flag_monet); + /***************************************/ + // 900 - media + public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 2f7c8bae384d..ff14064834d7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -31,8 +31,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_G import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Dialog; @@ -56,6 +54,7 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -80,8 +79,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -112,7 +109,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; -import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Background; @@ -123,6 +120,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -239,6 +237,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private int mSmallestScreenWidthDp; private final Optional<StatusBar> mStatusBarOptional; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final DialogLaunchAnimator mDialogLaunchAnimator; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -346,7 +345,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Main Handler handler, PackageManager packageManager, Optional<StatusBar> statusBarOptional, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + DialogLaunchAnimator dialogLaunchAnimator) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -377,6 +377,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mDialogLaunchAnimator = dialogLaunchAnimator; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -435,11 +436,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } /** - * Show the global actions dialog (creating if necessary) + * Show the global actions dialog (creating if necessary) or hide it if it's already showing. * - * @param keyguardShowing True if keyguard is showing + * @param keyguardShowing True if keyguard is showing + * @param isDeviceProvisioned True if device is provisioned + * @param view The view from which we should animate the dialog when showing it */ - public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, + @Nullable View view) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; if (mDialog != null && mDialog.isShowing()) { @@ -451,7 +455,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.dismiss(); mDialog = null; } else { - handleShow(); + handleShow(view); } } @@ -483,7 +487,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } - protected void handleShow() { + protected void handleShow(@Nullable View view) { awakenIfNecessary(); mDialog = createDialog(); prepareDialog(); @@ -493,8 +497,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mDialog.getWindow().setAttributes(attrs); // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports - mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM); - mDialog.show(); + mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM); + + if (view != null) { + mDialogLaunchAnimator.showFromView(mDialog, view); + } else { + mDialog.show(); + } mWindowManagerFuncs.onGlobalActionsShown(); } @@ -643,7 +652,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } - protected void onRotate() { + protected void onRefresh() { // re-allocate actions between main and overflow lists this.createActionItems(); } @@ -667,7 +676,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger, + mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils); dialog.setOnDismissListener(this); @@ -702,14 +711,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - public void onUiModeChanged() { - mContext.getTheme().applyStyle(mContext.getThemeResId(), true); - if (mDialog != null && mDialog.isShowing()) { - mDialog.refreshDialog(); - } - } - - @Override public void onConfigChanged(Configuration newConfig) { if (mDialog != null && mDialog.isShowing() && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) { @@ -717,6 +718,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.refreshDialog(); } } + /** * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is * called when the quick access wallet requests dismissal. @@ -1363,6 +1365,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = mAdapter.getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1379,6 +1385,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene if (mDialog != null) { // don't dismiss the dialog if we're opening the power options menu if (!(item instanceof PowerOptionsAction)) { + // Usually clicking an item shuts down the phone, locks, or starts an + // activity. We don't want to animate back into the power button when that + // happens, so we disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } } else { @@ -1446,6 +1456,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1459,6 +1473,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Action item = getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action clicked while mDialog is null."); @@ -1510,6 +1528,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1523,6 +1545,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Action item = getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action clicked while mDialog is null."); @@ -2066,7 +2092,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene case MESSAGE_DISMISS: if (mDialog != null) { if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - mDialog.completeDismiss(); + // Hide instantly. + mDialog.hide(); + mDialog.dismiss(); } else { mDialog.dismiss(); } @@ -2113,7 +2141,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @VisibleForTesting - static class ActionsDialogLite extends Dialog implements DialogInterface, + static class ActionsDialogLite extends SystemUIDialog implements DialogInterface, ColorExtractor.OnColorsChangedListener { protected final Context mContext; @@ -2126,13 +2154,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Drawable mBackgroundDrawable; protected final SysuiColorExtractor mColorExtractor; private boolean mKeyguardShowing; - protected boolean mShowing; protected float mScrimAlpha; protected final NotificationShadeWindowController mNotificationShadeWindowController; protected final SysUiState mSysUiState; private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; - protected final Runnable mOnRotateCallback; + protected final Runnable mOnRefreshCallback; private UiEventLogger mUiEventLogger; private GestureDetector mGestureDetector; private Optional<StatusBar> mStatusBarOptional; @@ -2151,7 +2178,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - public boolean onSingleTapConfirmed(MotionEvent e) { + public boolean onSingleTapUp(MotionEvent e) { // Close without opening shade mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); cancel(); @@ -2189,11 +2216,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, + SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) { - super(context, themeRes); + // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to + // dismiss this dialog when the device is locked. + super(context, themeRes, false /* dismissOnDeviceLock */); mContext = context; mAdapter = adapter; mOverflowAdapter = overflowAdapter; @@ -2202,36 +2231,32 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; mSysUiState = sysuiState; - mOnRotateCallback = onRotateCallback; + mOnRefreshCallback = onRefreshCallback; mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; - mGestureDetector = new GestureDetector(mContext, mGestureListener); + } - // Window initialization - Window window = getWindow(); - window.requestFeature(Window.FEATURE_NO_TITLE); - // Inflate the decor view, so the attributes below are not overwritten by the theme. - window.getDecorView(); - window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.setLayout(MATCH_PARENT, MATCH_PARENT); - window.addFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.getAttributes().setFitInsetsTypes(0 /* types */); - setTitle(R.string.global_actions); - + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); initializeLayout(); } @Override + protected int getWidth() { + return MATCH_PARENT; + } + + @Override + protected int getHeight() { + return MATCH_PARENT; + } + + @Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); } @@ -2371,7 +2396,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene message.animate() .alpha(0f) .setDuration(TOAST_FADE_TIME) - .setStartDelay(visibleTime); + .setStartDelay(visibleTime) + .setListener(null); } }); } @@ -2423,122 +2449,32 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void show() { super.show(); - // split this up so we can override but still call Dialog.show - showDialog(); - } - - protected void showDialog() { - mShowing = true; mNotificationShadeWindowController.setRequestTopUi(true, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) .commitUpdate(mContext.getDisplayId()); - - ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); - root.setOnApplyWindowInsetsListener((v, windowInsets) -> { - root.setPadding(windowInsets.getStableInsetLeft(), - windowInsets.getStableInsetTop(), - windowInsets.getStableInsetRight(), - windowInsets.getStableInsetBottom()); - return WindowInsets.CONSUMED; - }); - - mBackgroundDrawable.setAlpha(0); - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f); - alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - alphaAnimator.setDuration(183); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - }); - - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f); - xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.start(); } @Override public void dismiss() { - dismissWithAnimation(() -> { - dismissInternal(); - }); - } - - protected void dismissInternal() { - mContainer.setTranslationX(0); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f); - alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - alphaAnimator.setDuration(233); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = 1f - animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - }); - - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset); - xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - completeDismiss(); - } - }); - - animatorSet.start(); - - // close first, as popup windows will not fade during the animation - dismissOverflow(false); - dismissPowerOptions(false); - } + dismissOverflow(); + dismissPowerOptions(); - void dismissWithAnimation(Runnable animation) { - if (!mShowing) { - return; - } - mShowing = false; - animation.run(); - } - - protected void completeDismiss() { - mShowing = false; - dismissOverflow(true); - dismissPowerOptions(true); mNotificationShadeWindowController.setRequestTopUi(false, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) .commitUpdate(mContext.getDisplayId()); + super.dismiss(); } - protected final void dismissOverflow(boolean immediate) { + protected final void dismissOverflow() { if (mOverflowPopup != null) { - if (immediate) { - mOverflowPopup.dismissImmediate(); - } else { - mOverflowPopup.dismiss(); - } + mOverflowPopup.dismiss(); } } - protected final void dismissPowerOptions(boolean immediate) { + protected final void dismissPowerOptions() { if (mPowerOptionsDialog != null) { - if (immediate) { - mPowerOptionsDialog.dismiss(); - } else { - mPowerOptionsDialog.dismiss(); - } + mPowerOptionsDialog.dismiss(); } } @@ -2574,20 +2510,18 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } public void refreshDialog() { - // ensure dropdown menus are dismissed before re-initializing the dialog - dismissOverflow(true); - dismissPowerOptions(true); + mOnRefreshCallback.run(); - // re-create dialog - initializeLayout(); + // Dismiss the dropdown menus. + dismissOverflow(); + dismissPowerOptions(); + + // Update the list as the max number of items per row has probably changed. mGlobalActionsLayout.updateList(); } public void onRotate(int from, int to) { - if (mShowing) { - mOnRotateCallback.run(); - refreshDialog(); - } + refreshDialog(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c4508e043c7d..96ae646ac7f9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -82,7 +82,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks if (mDisabled) return; mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), - mDeviceProvisionedController.isDeviceProvisioned()); + mDeviceProvisionedController.isDeviceProvisioned(), null /* view */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index e97e7622c272..fbc9ba605000 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -793,7 +793,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; } else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; - } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) { + } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0 + || mUpdateMonitor.isFingerprintLockedOut())) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; @@ -820,6 +821,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; + private final InteractionJankMonitor mInteractionJankMonitor; private boolean mWallpaperSupportsAmbientMode; /** @@ -845,7 +847,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + InteractionJankMonitor interactionJankMonitor) { super(context); mFalsingCollector = falsingCollector; mLockPatternUtils = lockPatternUtils; @@ -882,6 +885,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mKeyguardStateController = keyguardStateController; mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; + mInteractionJankMonitor = interactionJankMonitor; } public void userActivity() { @@ -2245,8 +2249,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, onKeyguardExitFinished(); mKeyguardViewControllerLazy.get().hide(0 /* startTime */, 0 /* fadeoutDuration */); - InteractionJankMonitor.getInstance() - .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } @Override @@ -2255,7 +2258,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } }; try { - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RunRemoteAnimation")); runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps, wallpapers, nonApps, callback); @@ -2271,14 +2274,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback; mSurfaceBehindRemoteAnimationRunning = true; - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation( apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); } else { - InteractionJankMonitor.getInstance().begin( + mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration); @@ -2288,7 +2291,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // supported, so it's always null. mContext.getMainExecutor().execute(() -> { if (finishedCallback == null) { - InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); return; } @@ -2316,8 +2319,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } catch (RemoteException e) { Slog.e(TAG, "RemoteException"); } finally { - InteractionJankMonitor.getInstance() - .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } } @@ -2328,8 +2330,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } catch (RemoteException e) { Slog.e(TAG, "RemoteException"); } finally { - InteractionJankMonitor.getInstance() - .cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index cae9feeb62eb..8d23e9f6a12b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.PowerManager; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardUpdateMonitor; @@ -97,7 +98,8 @@ public class KeyguardModule { KeyguardStateController keyguardStateController, Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - Lazy<NotificationShadeDepthController> notificationShadeDepthController) { + Lazy<NotificationShadeDepthController> notificationShadeDepthController, + InteractionJankMonitor interactionJankMonitor) { return new KeyguardViewMediator( context, falsingCollector, @@ -120,7 +122,8 @@ public class KeyguardModule { keyguardStateController, keyguardUnlockAnimationController, unlockedScreenOffAnimationController, - notificationShadeDepthController + notificationShadeDepthController, + interactionJankMonitor ); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 47ef5e4c62fd..d54b1514abf7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -400,7 +400,7 @@ class MediaCarouselController @Inject constructor( } updatePageIndicator() mediaCarouselScrollHandler.onPlayersChanged() - mediaCarousel.requiresRemeasuring = true + mediaFrame.requiresRemeasuring = true // Check postcondition: mediaContent should have the same number of children as there are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { @@ -439,7 +439,7 @@ class MediaCarouselController @Inject constructor( updatePlayerToState(newRecs, noAnimation = true) reorderAllPlayers(curVisibleMediaKey) updatePageIndicator() - mediaCarousel.requiresRemeasuring = true + mediaFrame.requiresRemeasuring = true // Check postcondition: mediaContent should have the same number of children as there are // elements in mediaPlayers. if (MediaPlayerData.players().size != mediaContent.childCount) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 57ac9dfb52cd..237d0771bec2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -16,11 +16,19 @@ package com.android.systemui.media.dagger; +import android.content.Context; +import android.view.WindowManager; + import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHostStatesManager; +import com.android.systemui.media.taptotransfer.MediaTttChipController; +import com.android.systemui.media.taptotransfer.MediaTttFlags; +import com.android.systemui.statusbar.commandline.CommandRegistry; + +import java.util.Optional; import javax.inject.Named; @@ -63,4 +71,18 @@ public interface MediaModule { MediaHostStatesManager statesManager) { return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); } + + /** */ + @Provides + @SysUISingleton + static Optional<MediaTttChipController> providesMediaTttChipController( + MediaTttFlags mediaTttFlags, + Context context, + CommandRegistry commandRegistry, + WindowManager windowManager) { + if (!mediaTttFlags.isMediaTttEnabled()) { + return Optional.empty(); + } + return Optional.of(new MediaTttChipController(context, commandRegistry, windowManager)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 113ba59cd514..5fa66cd3a507 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -40,6 +40,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { private static final String TAG = "MediaOutputAdapter"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f; + private static final float DEVICE_CONNECTED_ALPHA = 1f; private final MediaOutputDialog mMediaOutputDialog; private ViewGroup mConnectedItem; @@ -109,8 +111,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (currentlyConnected) { mConnectedItem = mContainerLayout; } - mBottomDivider.setVisibility(View.GONE); mCheckBox.setVisibility(View.GONE); + mStatusIcon.setVisibility(View.GONE); if (currentlyConnected && mController.isActiveRemoteDevice(device) && mController.getSelectableMediaDevice().size() > 0) { // Init active device layout @@ -124,35 +126,42 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (mCurrentActivePosition == position) { mCurrentActivePosition = -1; } + if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE + && !device.isConnected()) { + mTitleText.setAlpha(DEVICE_DISCONNECTED_ALPHA); + mTitleIcon.setAlpha(DEVICE_DISCONNECTED_ALPHA); + } else { + mTitleText.setAlpha(DEVICE_CONNECTED_ALPHA); + mTitleIcon.setAlpha(DEVICE_CONNECTED_ALPHA); + } + if (mController.isTransferring()) { if (device.getState() == MediaDeviceState.STATE_CONNECTING && !mController.hasAdjustVolumeUserRestriction()) { - setTwoLineLayout(device, true /* bFocused */, false /* showSeekBar*/, - true /* showProgressBar */, false /* showSubtitle */); + setSingleLineLayout(getItemTitle(device), true /* bFocused */, + false /* showSeekBar*/, + true /* showProgressBar */, false /* showStatus */); } else { setSingleLineLayout(getItemTitle(device), false /* bFocused */); } } else { // Set different layout for each device if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { + mStatusIcon.setImageDrawable( + mContext.getDrawable(R.drawable.media_output_status_failed)); setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, false /* showProgressBar */, - true /* showSubtitle */); + true /* showSubtitle */, true /* showStatus */); mSubTitleText.setText(R.string.media_output_dialog_connect_failed); mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) { - setTwoLineLayout(device, true /* bFocused */, true /* showSeekBar */, - false /* showProgressBar */, false /* showSubtitle */); + mStatusIcon.setImageDrawable( + mContext.getDrawable(R.drawable.media_output_status_check)); + setSingleLineLayout(getItemTitle(device), true /* bFocused */, + true /* showSeekBar */, + false /* showProgressBar */, true /* showStatus */); initSeekbar(device); mCurrentActivePosition = position; - } else if ( - device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE - && !device.isConnected()) { - setTwoLineLayout(device, false /* bFocused */, - false /* showSeekBar */, false /* showProgressBar */, - true /* showSubtitle */); - mSubTitleText.setText(R.string.media_output_dialog_disconnected); - mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); } else { setSingleLineLayout(getItemTitle(device), false /* bFocused */); mContainerLayout.setOnClickListener(v -> onItemClick(v, device)); @@ -165,7 +174,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { mCheckBox.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); - mBottomDivider.setVisibility(View.GONE); setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new), false /* bFocused */); final Drawable d = mContext.getDrawable(R.drawable.ic_add); @@ -175,7 +183,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW)); } else if (customizedItem == CUSTOMIZED_ITEM_DYNAMIC_GROUP) { mConnectedItem = mContainerLayout; - mBottomDivider.setVisibility(View.GONE); mCheckBox.setVisibility(View.GONE); if (mController.getSelectableMediaDevice().size() > 0) { mAddIcon.setVisibility(View.VISIBLE); @@ -200,7 +207,6 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } mCurrentActivePosition = -1; - playSwitchingAnim(mConnectedItem, view); mController.connectDevice(device); device.setState(MediaDeviceState.STATE_CONNECTING); if (!isAnimating()) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 868193b44704..dc4aaa92f45a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -30,6 +30,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; @@ -40,7 +41,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.R; @@ -113,6 +113,7 @@ public abstract class MediaOutputBaseAdapter extends private static final int ANIM_DURATION = 200; final LinearLayout mContainerLayout; + final FrameLayout mItemLayout; final TextView mTitleText; final TextView mTwoLineTitleText; final TextView mSubTitleText; @@ -121,13 +122,14 @@ public abstract class MediaOutputBaseAdapter extends final ProgressBar mProgressBar; final SeekBar mSeekBar; final RelativeLayout mTwoLineLayout; - final View mBottomDivider; + final ImageView mStatusIcon; final CheckBox mCheckBox; private String mDeviceId; MediaDeviceBaseViewHolder(View view) { super(view); mContainerLayout = view.requireViewById(R.id.device_container); + mItemLayout = view.requireViewById(R.id.item_layout); mTitleText = view.requireViewById(R.id.title); mSubTitleText = view.requireViewById(R.id.subtitle); mTwoLineLayout = view.requireViewById(R.id.two_line_layout); @@ -135,8 +137,8 @@ public abstract class MediaOutputBaseAdapter extends mTitleIcon = view.requireViewById(R.id.title_icon); mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress); mSeekBar = view.requireViewById(R.id.volume_seekbar); - mBottomDivider = view.requireViewById(R.id.bottom_divider); mAddIcon = view.requireViewById(R.id.add_icon); + mStatusIcon = view.requireViewById(R.id.media_output_item_status); mCheckBox = view.requireViewById(R.id.check_box); } @@ -156,11 +158,26 @@ public abstract class MediaOutputBaseAdapter extends abstract void onBind(int customizedItem, boolean topMargin, boolean bottomMargin); void setSingleLineLayout(CharSequence title, boolean bFocused) { + setSingleLineLayout(title, bFocused, false, false, false); + } + + void setSingleLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar, + boolean showProgressBar, boolean showStatus) { mTwoLineLayout.setVisibility(View.GONE); - mProgressBar.setVisibility(View.GONE); + final Drawable backgroundDrawable = + showSeekBar + ? mContext.getDrawable(R.drawable.media_output_item_background_active) + .mutate() : mContext.getDrawable( + R.drawable.media_output_item_background) + .mutate(); + mItemLayout.setBackground(backgroundDrawable); + mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); + mSeekBar.setAlpha(1); + mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); + mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); + mTitleText.setText(title); mTitleText.setVisibility(View.VISIBLE); mTitleText.setTranslationY(0); - mTitleText.setText(title); if (bFocused) { mTitleText.setTypeface(Typeface.create(mContext.getString( com.android.internal.R.string.config_headlineFontFamilyMedium), @@ -173,20 +190,32 @@ public abstract class MediaOutputBaseAdapter extends void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) { - setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle); + setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle, + false); + } + + void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar, + boolean showProgressBar, boolean showSubtitle, boolean showStatus) { + setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle, + showStatus); } void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) { - setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle); + setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle, + false); } private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused, - boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) { + boolean showSeekBar, boolean showProgressBar, boolean showSubtitle, + boolean showStatus) { mTitleText.setVisibility(View.GONE); mTwoLineLayout.setVisibility(View.VISIBLE); + mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE); mSeekBar.setAlpha(1); mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE); + mItemLayout.setBackground(mContext.getDrawable(R.drawable.media_output_item_background) + .mutate()); mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); mTwoLineTitleText.setTranslationY(0); @@ -289,6 +318,9 @@ public abstract class MediaOutputBaseAdapter extends public void onAnimationEnd(Animator animation) { to.requireViewById(R.id.volume_indeterminate_progress).setVisibility( View.VISIBLE); + // Unset the listener, otherwise this may persist for another view + // property animation + toTitleText.animate().setListener(null); } }); // Animation for seek bar @@ -312,8 +344,14 @@ public abstract class MediaOutputBaseAdapter extends public void onAnimationEnd(Animator animation) { mIsAnimating = false; notifyDataSetChanged(); + // Unset the listener, otherwise this may persist for + // another view property animation + fromTitleText.animate().setListener(null); } }); + // Unset the listener, otherwise this may persist for another view + // property animation + fromSeekBar.animate().setListener(null); } }); } @@ -325,7 +363,7 @@ public abstract class MediaOutputBaseAdapter extends R.color.advanced_icon_color, mContext.getTheme()); drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(), PorterDuff.Mode.SRC_IN)); - return BluetoothUtils.buildAdvancedDrawable(mContext, drawable); + return drawable; } private void disableSeekBar() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 26ce645eefc5..91d0b49b856d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -20,6 +20,7 @@ import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -64,6 +65,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements private TextView mHeaderTitle; private TextView mHeaderSubtitle; private ImageView mHeaderIcon; + private ImageView mAppResourceIcon; private RecyclerView mDevicesRecyclerView; private LinearLayout mDeviceListLayout; private Button mDoneButton; @@ -112,6 +114,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mDeviceListLayout = mDialogView.requireViewById(R.id.device_list); mDoneButton = mDialogView.requireViewById(R.id.done); mStopButton = mDialogView.requireViewById(R.id.stop); + mAppResourceIcon = mDialogView.requireViewById(R.id.app_source_icon); mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener( mDeviceListLayoutListener); @@ -145,6 +148,12 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements // Update header icon final int iconRes = getHeaderIconRes(); final IconCompat iconCompat = getHeaderIcon(); + final Drawable appSourceDrawable = getAppSourceIcon(); + if (appSourceDrawable != null) { + mAppResourceIcon.setImageDrawable(appSourceDrawable); + } else { + mAppResourceIcon.setVisibility(View.GONE); + } if (iconRes != 0) { mHeaderIcon.setVisibility(View.VISIBLE); mHeaderIcon.setImageResource(iconRes); @@ -183,6 +192,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements mStopButton.setVisibility(getStopButtonVisibility()); } + abstract Drawable getAppSourceIcon(); + abstract int getHeaderIconRes(); abstract IconCompat getHeaderIcon(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 6da4d4811ac9..eef5fb0a34a8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog; import android.app.Notification; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -181,6 +182,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mMetricLogger.logOutputFailure(mMediaDevices, reason); } + Drawable getAppSourceIcon() { + if (mPackageName.isEmpty()) { + return null; + } + try { + Log.d(TAG, "try to get app icon"); + return mContext.getPackageManager() + .getApplicationIcon(mPackageName); + } catch (PackageManager.NameNotFoundException e) { + Log.d(TAG, "icon not found"); + return null; + } + } + CharSequence getHeaderTitle() { if (mMediaController != null) { final MediaMetadata metadata = mMediaController.getMetadata(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index eca8ac90427b..7696a1f63c01 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -17,6 +17,7 @@ package com.android.systemui.media.dialog; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.View; import android.view.WindowManager; @@ -79,6 +80,11 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { } @Override + Drawable getAppSourceIcon() { + return mMediaOutputController.getAppSourceIcon(); + } + + @Override int getStopButtonVisibility() { return mMediaOutputController.isActiveRemoteDevice( mMediaOutputController.getCurrentConnectedMediaDevice()) ? View.VISIBLE : View.GONE; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java index a201c071bbbe..104ddf907dc7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java @@ -97,7 +97,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter { void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) { super.onBind(device, topMargin, bottomMargin, position); mAddIcon.setVisibility(View.GONE); - mBottomDivider.setVisibility(View.GONE); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { onCheckBoxClicked(isChecked, device); @@ -131,7 +130,6 @@ public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter { true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, false /* showSubtitle*/); mTitleIcon.setImageDrawable(getSpeakerDrawable()); - mBottomDivider.setVisibility(View.VISIBLE); mCheckBox.setVisibility(View.GONE); mAddIcon.setVisibility(View.GONE); initSessionSeekbar(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java index 1300400f3b66..f1c66016a49a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java @@ -17,6 +17,7 @@ package com.android.systemui.media.dialog; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.View; import android.view.WindowManager; @@ -28,6 +29,7 @@ import com.android.systemui.R; /** * Dialog for media output group. */ +// TODO(b/203073091): Remove this class once group logic been implemented. public class MediaOutputGroupDialog extends MediaOutputBaseDialog { MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController @@ -76,6 +78,11 @@ public class MediaOutputGroupDialog extends MediaOutputBaseDialog { } @Override + Drawable getAppSourceIcon() { + return null; + } + + @Override int getStopButtonVisibility() { return View.VISIBLE; } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt new file mode 100644 index 000000000000..85e5b336575b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer + +import android.content.Context +import android.graphics.PixelFormat +import android.view.Gravity +import android.view.WindowManager +import android.widget.TextView +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +/** + * A controller to display and hide the Media Tap-To-Transfer chip. This chip is shown when a user + * is currently playing media on a local "media cast sender" device (e.g. a phone) and gets close + * enough to a "media cast receiver" device (e.g. a tablet). This chip encourages the user to + * transfer the media from the sender device to the receiver device. + */ +@SysUISingleton +class MediaTttChipController @Inject constructor( + context: Context, + commandRegistry: CommandRegistry, + private val windowManager: WindowManager, +) { + init { + commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() } + commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() } + } + + private val windowLayoutParams = WindowManager.LayoutParams().apply { + width = WindowManager.LayoutParams.MATCH_PARENT + height = WindowManager.LayoutParams.MATCH_PARENT + gravity = Gravity.CENTER_HORIZONTAL + type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY + title = "Media Tap-To-Transfer Chip View" + flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + format = PixelFormat.TRANSLUCENT + setTrustedOverlay() + } + + // TODO(b/203800327): Create a layout that matches UX. + private val chipView: TextView = TextView(context).apply { + text = "Media Tap-To-Transfer Chip" + } + + private var chipDisplaying: Boolean = false + + private fun addChip() { + if (chipDisplaying) { return } + windowManager.addView(chipView, windowLayoutParams) + chipDisplaying = true + } + + private fun removeChip() { + if (!chipDisplaying) { return } + windowManager.removeView(chipView) + chipDisplaying = false + } + + inner class AddChipCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) = addChip() + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG") + } + } + + inner class RemoveChipCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) = removeChip() + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG") + } + } + + companion object { + @VisibleForTesting + const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add" + @VisibleForTesting + const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt new file mode 100644 index 000000000000..03bc9350674b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject + +/** Flags related to media tap-to-transfer. */ +@SysUISingleton +class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) { + /** */ + fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER) +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 52103d3bd739..25337b6d52a1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -24,12 +24,15 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; +import android.view.View; +import android.view.WindowInsets; import android.view.accessibility.AccessibilityManager; import androidx.annotation.NonNull; @@ -42,12 +45,14 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import javax.inject.Inject; @@ -69,10 +74,11 @@ public final class NavBarHelper implements Dumpable { private final AccessibilityManager mAccessibilityManager; private final Lazy<AssistManager> mAssistManagerLazy; + private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; private final UserTracker mUserTracker; private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>(); - private Context mContext; + private final Context mContext; private ContentResolver mContentResolver; private boolean mAssistantAvailable; private boolean mLongPressHomeEnabled; @@ -87,17 +93,25 @@ public final class NavBarHelper implements } }; + /** + * @param context This is not display specific, then again neither is any of the code in + * this class. Once there's display specific code, we may want to create an + * instance of this class per navbar vs having it be a singleton. + */ @Inject - public NavBarHelper(AccessibilityManager accessibilityManager, + public NavBarHelper(Context context, AccessibilityManager accessibilityManager, AccessibilityManagerWrapper accessibilityManagerWrapper, AccessibilityButtonModeObserver accessibilityButtonModeObserver, OverviewProxyService overviewProxyService, Lazy<AssistManager> assistManagerLazy, + Lazy<Optional<StatusBar>> statusBarOptionalLazy, NavigationModeController navigationModeController, UserTracker userTracker, DumpManager dumpManager) { + mContext = context; mAccessibilityManager = accessibilityManager; mAssistManagerLazy = assistManagerLazy; + mStatusBarOptionalLazy = statusBarOptionalLazy; mUserTracker = userTracker; accessibilityManagerWrapper.addCallback( accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate()); @@ -109,8 +123,7 @@ public final class NavBarHelper implements dumpManager.registerDumpable(this); } - public void init(Context context) { - mContext = context; + public void init() { mContentResolver = mContext.getContentResolver(); mContentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), @@ -227,6 +240,19 @@ public final class NavBarHelper implements } /** + * @return Whether the IME is shown on top of the screen given the {@code vis} flag of + * {@link InputMethodService} and the keyguard states. + */ + public boolean isImeShown(int vis) { + View shadeWindowView = mStatusBarOptionalLazy.get().get().getNotificationShadeWindowView(); + boolean isKeyguardShowing = mStatusBarOptionalLazy.get().get().isKeyguardShowing(); + boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() + && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime()); + return imeVisibleOnShade + || (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0); + } + + /** * Callbacks will get fired once immediately after registering via * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)} */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index e0da9a0dca2c..8026df747ff6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -71,7 +71,6 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; -import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -93,7 +92,6 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; -import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; @@ -563,7 +561,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mCommandQueue.addCallback(this); mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); mContentResolver = mContext.getContentResolver(); - mNavBarHelper.init(mContext); + mNavBarHelper.init(); mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean( R.bool.allow_force_nav_bar_handle_opaque); mForceNavBarHandleOpaque = DeviceConfig.getBoolean( @@ -884,17 +882,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, if (displayId != mDisplayId) { return; } - boolean imeVisibleOnShade = mStatusBarOptionalLazy.get().map(statusBar -> { - View shadeWindowView = statusBar.getNotificationShadeWindowView(); - return shadeWindowView.isAttachedToWindow() - && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime()); - }).orElse(false); - boolean isKeyguardShowing = mStatusBarOptionalLazy.get().map( - StatusBar::isKeyguardShowing).orElse(false); - boolean imeShown = imeVisibleOnShade - || (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0); + boolean imeShown = mNavBarHelper.isImeShown(vis); showImeSwitcher = imeShown && showImeSwitcher; - int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); if (hints == mNavigationIconHints) return; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 339877824b16..0429c022234d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -104,7 +105,8 @@ public class NavigationBarController implements TaskbarDelegate taskbarDelegate, NavigationBar.Factory navigationBarFactory, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { mContext = context; mHandler = mainHandler; mNavigationBarFactory = navigationBarFactory; @@ -116,7 +118,7 @@ public class NavigationBarController implements mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, navBarHelper, navigationModeController, sysUiFlagsContainer, - dumpManager, autoHideController); + dumpManager, autoHideController, lightBarController); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 680cc5cb5cba..7c8c3e0dce44 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -110,6 +110,7 @@ public class NavigationBarView extends FrameLayout implements private final int mNavColorSampleMargin; private final SysUiState mSysUiFlagContainer; + // The current view is one of mHorizontal or mVertical depending on the current configuration View mCurrentView = null; private View mVertical; private View mHorizontal; @@ -397,12 +398,6 @@ public class NavigationBarView extends FrameLayout implements } } - @Override - protected boolean onSetAlpha(int alpha) { - Log.e(TAG, "onSetAlpha", new Throwable()); - return super.onSetAlpha(alpha); - } - public void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; } @@ -505,6 +500,18 @@ public class NavigationBarView extends FrameLayout implements return mCurrentView; } + /** + * Applies {@param consumer} to each of the nav bar views. + */ + public void forEachView(Consumer<View> consumer) { + if (mVertical != null) { + consumer.accept(mVertical); + } + if (mHorizontal != null) { + consumer.accept(mHorizontal); + } + } + public RotationButtonController getRotationButtonController() { return mRotationButtonController; } @@ -1205,7 +1212,9 @@ public class NavigationBarView extends FrameLayout implements protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mTmpLastConfiguration.updateFrom(mConfiguration); - mConfiguration.updateFrom(newConfig); + final int changes = mConfiguration.updateFrom(newConfig); + mFloatingRotationButton.onConfigurationChanged(changes); + boolean uiCarModeChanged = updateCarMode(); updateIcons(mTmpLastConfiguration); updateRecentsIcon(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 428d9d6993b7..8fb394c06ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -41,7 +41,6 @@ import android.content.ComponentCallbacks; import android.content.Context; import android.content.res.Configuration; import android.hardware.display.DisplayManager; -import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -64,6 +63,9 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.LightBarTransitionsController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -85,6 +87,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; private AutoHideController mAutoHideController; + private LightBarController mLightBarController; + private LightBarTransitionsController mLightBarTransitionsController; private int mDisplayId; private int mNavigationIconHints; private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = @@ -141,7 +145,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, - AutoHideController autoHideController) { + AutoHideController autoHideController, + LightBarController lightBarController) { // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; @@ -150,6 +155,30 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mSysUiState = sysUiState; dumpManager.registerDumpable(this); mAutoHideController = autoHideController; + mLightBarController = lightBarController; + mLightBarTransitionsController = createLightBarTransitionsController(); + } + + // Separated into a method to keep setDependencies() clean/readable. + private LightBarTransitionsController createLightBarTransitionsController() { + return new LightBarTransitionsController(mContext, + new LightBarTransitionsController.DarkIntensityApplier() { + @Override + public void applyDarkIntensity(float darkIntensity) { + mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity); + } + + @Override + public int getTintAnimationDuration() { + return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION; + } + }, mCommandQueue) { + @Override + public boolean supportsIconTintForNavMode(int navigationMode) { + // Always tint taskbar nav buttons (region sampling handles gesture bar separately). + return true; + } + }; } public void init(int displayId) { @@ -162,7 +191,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mEdgeBackGestureHandler.onNavigationModeChanged( mNavigationModeController.addListener(this)); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); - mNavBarHelper.init(mContext); + mNavBarHelper.init(); mEdgeBackGestureHandler.onNavBarAttached(); // Initialize component callback Display display = mDisplayManager.getDisplay(displayId); @@ -171,6 +200,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, // Set initial state for any listeners updateSysuiFlags(); mAutoHideController.setNavigationBar(mAutoHideUiElement); + mLightBarController.setNavigationBar(mLightBarTransitionsController); mInitialized = true; } @@ -189,6 +219,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, mWindowContext = null; } mAutoHideController.setNavigationBar(null); + mLightBarTransitionsController.destroy(mContext); + mLightBarController.setNavigationBar(null); mInitialized = false; } @@ -232,7 +264,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { - boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; + boolean imeShown = mNavBarHelper.isImeShown(vis); + showImeSwitcher = imeShown && showImeSwitcher; int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); if (hints != mNavigationIconHints) { @@ -268,6 +301,10 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities, String packageName) { mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior); + if (mLightBarController != null && displayId == mDisplayId) { + mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/, + BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme); + } if (mBehavior != behavior) { mBehavior = behavior; updateSysuiFlags(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 98b914672112..e10e4d8a825c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -17,8 +17,10 @@ package com.android.systemui.qs import android.content.Intent +import android.os.Handler import android.os.UserManager import android.provider.Settings +import android.provider.Settings.Global.USER_SWITCHER_ENABLED import android.view.View import android.widget.Toast import androidx.annotation.VisibleForTesting @@ -35,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.phone.SettingsButton import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -42,6 +45,7 @@ import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener import com.android.systemui.tuner.TunerService import com.android.systemui.util.ViewController +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import javax.inject.Named @@ -55,6 +59,7 @@ class FooterActionsController @Inject constructor( private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val userTracker: UserTracker, private val userInfoController: UserInfoController, private val multiUserSwitchController: MultiUserSwitchController, private val deviceProvisionedController: DeviceProvisionedController, @@ -64,7 +69,9 @@ class FooterActionsController @Inject constructor( private val globalActionsDialog: GlobalActionsDialogLite, private val uiEventLogger: UiEventLogger, @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean, - private val buttonsVisibleState: ExpansionState + private val buttonsVisibleState: ExpansionState, + private val globalSetting: GlobalSettings, + private val handler: Handler ) : ViewController<FooterActionsView>(view) { enum class ExpansionState { COLLAPSED, EXPANDED } @@ -83,6 +90,16 @@ class FooterActionsController @Inject constructor( mView.onUserInfoChanged(picture, isGuestUser) } + private val multiUserSetting = + object : SettingObserver( + globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) { + override fun handleValueChanged(value: Int, observedChange: Boolean) { + if (observedChange) { + updateView() + } + } + } + private val onClickListener = View.OnClickListener { v -> // Don't do anything until views are unhidden. Don't do anything if the tap looks // suspicious. @@ -116,7 +133,7 @@ class FooterActionsController @Inject constructor( } } else if (v === powerMenuLite) { uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) - globalActionsDialog.showOrHideDialog(false, true) + globalActionsDialog.showOrHideDialog(false, true, v) } } @@ -182,6 +199,7 @@ class FooterActionsController @Inject constructor( return } this.listening = listening + multiUserSetting.isListening = listening if (this.listening) { userInfoController.addCallback(onUserInfoChangedListener) updateView() @@ -215,4 +233,4 @@ class FooterActionsController @Inject constructor( } private fun isTunerEnabled() = tunerService.isTunerEnabled -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt index f6c89a9c66a2..dd4dc87d8a9f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt @@ -16,18 +16,22 @@ package com.android.systemui.qs +import android.os.Handler import android.os.UserManager import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.FooterActionsController.ExpansionState import com.android.systemui.qs.dagger.QSFlagsModule +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.tuner.TunerService +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import javax.inject.Named @@ -35,6 +39,7 @@ class FooterActionsControllerBuilder @Inject constructor( private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, + private val userTracker: UserTracker, private val userInfoController: UserInfoController, private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, private val deviceProvisionedController: DeviceProvisionedController, @@ -43,7 +48,9 @@ class FooterActionsControllerBuilder @Inject constructor( private val tunerService: TunerService, private val globalActionsDialog: GlobalActionsDialogLite, private val uiEventLogger: UiEventLogger, - @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean + @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean, + private val globalSettings: GlobalSettings, + @Main private val handler: Handler ) { private lateinit var view: FooterActionsView private lateinit var buttonsVisibleState: ExpansionState @@ -60,8 +67,9 @@ class FooterActionsControllerBuilder @Inject constructor( fun build(): FooterActionsController { return FooterActionsController(view, qsPanelController, activityStarter, userManager, - userInfoController, multiUserSwitchControllerFactory.create(view), + userTracker, userInfoController, multiUserSwitchControllerFactory.create(view), deviceProvisionedController, falsingManager, metricsLogger, tunerService, - globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState) + globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState, + globalSettings, handler) } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java index 2f189beb7780..768598af6885 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java @@ -133,10 +133,7 @@ public class PseudoGridView extends ViewGroup { x += width + mHorizontalSpacing; } } - y += maxHeight; - if (row > 0) { - y += mVerticalSpacing; - } + y += maxHeight + mVerticalSpacing; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e42c47b1d3be..ee59ae6ab6d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -770,6 +770,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void onAnimationEnd(Animator animation) { mHeaderAnimating = false; updateQsState(); + // Unset the listener, otherwise this may persist for another view property animation + getView().animate().setListener(null); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 6d1bbeed5372..48255b5360fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.HotspotController; +import com.android.systemui.statusbar.policy.RunningFgsController; +import com.android.systemui.statusbar.policy.RunningFgsControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; @@ -89,4 +91,9 @@ public interface QSModule { /** */ @Binds QSHost provideQsHost(QSTileHost controllerImpl); + + /** */ + @Binds + RunningFgsController provideRunningFgsController( + RunningFgsControllerImpl runningFgsController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt index baf30186ea1e..11c49496fc1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt @@ -18,11 +18,8 @@ package com.android.systemui.qs.external import android.content.Context import android.graphics.drawable.Icon -import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.view.WindowInsets import android.widget.TextView import com.android.systemui.R import com.android.systemui.plugins.qs.QSTile @@ -38,25 +35,12 @@ import com.android.systemui.statusbar.phone.SystemUIDialog */ class TileRequestDialog( context: Context -) : SystemUIDialog(context, R.style.TileRequestDialog) { +) : SystemUIDialog(context) { companion object { internal val CONTENT_ID = R.id.content } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - window?.apply { - attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() - attributes.receiveInsetsIgnoringZOrder = true - setLayout( - context.resources - .getDimensionPixelSize(R.dimen.qs_tile_service_request_dialog_width), - WRAP_CONTENT - ) - } - } - /** * Set the data of the tile to add, to show the user. */ @@ -76,9 +60,7 @@ class TileRequestDialog( context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) ) } - val spacing = context.resources.getDimensionPixelSize( - R.dimen.qs_tile_service_request_content_space - ) + val spacing = 0 setView(ll, spacing, spacing, spacing, spacing / 2) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt new file mode 100644 index 000000000000..e0f9cc28ca62 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.StatusBarManager +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEventLoggerImpl + +class TileRequestDialogEventLogger @VisibleForTesting constructor( + private val uiEventLogger: UiEventLogger, + private val instanceIdSequence: InstanceIdSequence +) { + companion object { + const val MAX_INSTANCE_ID = 1 shl 20 + } + + constructor() : this(UiEventLoggerImpl(), InstanceIdSequence(MAX_INSTANCE_ID)) + + /** + * Obtain a new [InstanceId] to log a session for a dialog request. + */ + fun newInstanceId(): InstanceId = instanceIdSequence.newInstanceId() + + /** + * Log that the dialog has been shown to the user for a tile in the given [packageName]. This + * call should use a new [instanceId]. + */ + fun logDialogShown(packageName: String, instanceId: InstanceId) { + uiEventLogger.logWithInstanceId( + TileRequestDialogEvent.TILE_REQUEST_DIALOG_SHOWN, + /* uid */ 0, + packageName, + instanceId + ) + } + + /** + * Log the user response to the dialog being shown. Must follow a call to [logDialogShown] that + * used the same [packageName] and [instanceId]. Only the following responses are valid: + * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED] + * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED] + * * [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED] + */ + fun logUserResponse( + @StatusBarManager.RequestResult response: Int, + packageName: String, + instanceId: InstanceId + ) { + val event = when (response) { + StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED -> { + TileRequestDialogEvent.TILE_REQUEST_DIALOG_DISMISSED + } + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED -> { + TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_NOT_ADDED + } + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED -> { + TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ADDED + } + else -> { + throw IllegalArgumentException("User response not valid: $response") + } + } + uiEventLogger.logWithInstanceId(event, /* uid */ 0, packageName, instanceId) + } + + /** + * Log that the dialog will not be shown because the tile was already part of the active set. + * Corresponds to a response of [StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED]. + */ + fun logTileAlreadyAdded(packageName: String, instanceId: InstanceId) { + uiEventLogger.logWithInstanceId( + TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED, + /* uid */ 0, + packageName, + instanceId + ) + } +} + +enum class TileRequestDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Tile request dialog not shown because tile is already added.") + TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED(917), + + @UiEvent(doc = "Tile request dialog shown to user.") + TILE_REQUEST_DIALOG_SHOWN(918), + + @UiEvent(doc = "User dismisses dialog without choosing an option.") + TILE_REQUEST_DIALOG_DISMISSED(919), + + @UiEvent(doc = "User accepts adding tile from dialog.") + TILE_REQUEST_DIALOG_TILE_ADDED(920), + + @UiEvent(doc = "User denies adding tile from dialog.") + TILE_REQUEST_DIALOG_TILE_NOT_ADDED(921); + + override fun getId() = _id +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt index 210ee93bb7ef..73d6b971f785 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt @@ -44,6 +44,7 @@ class TileServiceRequestController constructor( private val qsTileHost: QSTileHost, private val commandQueue: CommandQueue, private val commandRegistry: CommandRegistry, + private val eventLogger: TileRequestDialogEventLogger, private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsTileHost.context) } ) { @@ -97,25 +98,31 @@ class TileServiceRequestController constructor( icon: Icon?, callback: Consumer<Int> ) { + val instanceId = eventLogger.newInstanceId() + val packageName = componentName.packageName if (isTileAlreadyAdded(componentName)) { callback.accept(TILE_ALREADY_ADDED) + eventLogger.logTileAlreadyAdded(packageName, instanceId) return } val dialogResponse = Consumer<Int> { response -> if (response == ADD_TILE) { addTile(componentName) } + dialogCanceller = null + eventLogger.logUserResponse(response, packageName, instanceId) callback.accept(response) } val tileData = TileRequestDialog.TileData(appName, label, icon) createDialog(tileData, dialogResponse).also { dialog -> dialogCanceller = { - if (componentName.packageName == it) { + if (packageName == it) { dialog.cancel() } dialogCanceller = null } }.show() + eventLogger.logDialogShown(packageName, instanceId) } private fun createDialog( @@ -168,7 +175,12 @@ class TileServiceRequestController constructor( private val commandRegistry: CommandRegistry ) { fun create(qsTileHost: QSTileHost): TileServiceRequestController { - return TileServiceRequestController(qsTileHost, commandQueue, commandRegistry) + return TileServiceRequestController( + qsTileHost, + commandQueue, + commandRegistry, + TileRequestDialogEventLogger() + ) } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 16be99827afb..ac95bf50bb98 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -36,6 +36,7 @@ import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DeviceControlsTile; import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.qs.tiles.FgsManagerTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; import com.android.systemui.qs.tiles.InternetTile; @@ -94,6 +95,7 @@ public class QSFactoryImpl implements QSFactory { private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider; private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider; private final Provider<OneHandedModeTile> mOneHandedModeTileProvider; + private final Provider<FgsManagerTile> mFgsManagerTileProvider; private final Lazy<QSHost> mQsHostLazy; private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; @@ -130,7 +132,8 @@ public class QSFactoryImpl implements QSFactory { Provider<AlarmTile> alarmTileProvider, Provider<QuickAccessWalletTile> quickAccessWalletTileProvider, Provider<QRCodeScannerTile> qrCodeScannerTileProvider, - Provider<OneHandedModeTile> oneHandedModeTileProvider) { + Provider<OneHandedModeTile> oneHandedModeTileProvider, + Provider<FgsManagerTile> fgsManagerTileProvider) { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; @@ -163,6 +166,7 @@ public class QSFactoryImpl implements QSFactory { mQuickAccessWalletTileProvider = quickAccessWalletTileProvider; mQRCodeScannerTileProvider = qrCodeScannerTileProvider; mOneHandedModeTileProvider = oneHandedModeTileProvider; + mFgsManagerTileProvider = fgsManagerTileProvider; } public QSTile createTile(String tileSpec) { @@ -233,6 +237,8 @@ public class QSFactoryImpl implements QSFactory { return mQRCodeScannerTileProvider.get(); case "onehanded": return mOneHandedModeTileProvider.get(); + case "fgsmanager": + return mFgsManagerTileProvider.get(); } // Custom tiles diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index b1cd03c4a2f2..106a1b60a7a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -253,7 +253,7 @@ public class QSIconViewImpl extends QSIconView { return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); case Tile.STATE_ACTIVE: return Utils.getColorAttrDefaultColor(context, - android.R.attr.textColorPrimaryInverse); + com.android.internal.R.attr.textColorOnAccent); default: Log.e("QSIconView", "Invalid state " + state); return 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 09fad30f02ff..6bb7986724b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -88,7 +88,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive) private val colorLabelActive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse) + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) private val colorLabelUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorLabelInactive) @@ -652,7 +652,8 @@ internal object SubtitleArrayMapping { "wallet" to R.array.tile_states_wallet, "qr_code_scanner" to R.array.tile_states_qr_code_scanner, "alarm" to R.array.tile_states_alarm, - "onehanded" to R.array.tile_states_onehanded + "onehanded" to R.array.tile_states_onehanded, + "fgsmanager" to R.array.tile_states_fgsmanager ) fun getSubtitleId(spec: String?): Int { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 99cb700a324d..18b401f043d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_OFF; +import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.Intent; @@ -41,7 +42,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; -import android.view.WindowManager; import android.widget.Switch; import android.widget.Toast; @@ -53,6 +53,7 @@ import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.SysUIToast; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -84,6 +85,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final DndDetailAdapter mDetailAdapter; private final SharedPreferences mSharedPreferences; private final SettingObserver mSettingZenDuration; + private final DialogLaunchAnimator mDialogLaunchAnimator; private boolean mListening; private boolean mShowingDetail; @@ -100,7 +102,8 @@ public class DndTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, ZenModeController zenModeController, @Main SharedPreferences sharedPreferences, - SecureSettings secureSettings + SecureSettings secureSettings, + DialogLaunchAnimator dialogLaunchAnimator ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -108,6 +111,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSharedPreferences = sharedPreferences; mDetailAdapter = new DndDetailAdapter(); mController.observe(getLifecycle(), mZenCallback); + mDialogLaunchAnimator = dialogLaunchAnimator; mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler, Settings.Secure.ZEN_DURATION, getHost().getUserId()) { @Override @@ -117,8 +121,6 @@ public class DndTile extends QSTileImpl<BooleanState> { }; } - - public static void setVisible(Context context, boolean visible) { Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible); } @@ -187,14 +189,17 @@ public class DndTile extends QSTileImpl<BooleanState> { switch (zenDuration) { case Settings.Secure.ZEN_DURATION_PROMPT: mUiHandler.post(() -> { - Dialog mDialog = new EnableZenModeDialog(mContext).createDialog(); - mDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - SystemUIDialog.setShowForAllUsers(mDialog, true); - SystemUIDialog.registerDismissListener(mDialog); - SystemUIDialog.setWindowOnTop(mDialog); - mUiHandler.post(() -> mDialog.show()); - mHost.collapsePanels(); + Dialog dialog = makeZenModeDialog(); + if (view != null) { + final Dialog hostDialog = + mDialogLaunchAnimator.showFromView(dialog, view, false); + setDialogListeners(dialog, hostDialog); + } else { + // If we are not launching with animator, register default + // dismiss listener + SystemUIDialog.registerDismissListener(dialog); + dialog.show(); + } }); break; case Settings.Secure.ZEN_DURATION_FOREVER: @@ -209,6 +214,20 @@ public class DndTile extends QSTileImpl<BooleanState> { } } + private Dialog makeZenModeDialog() { + AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog) + .createDialog(); + SystemUIDialog.applyFlags(dialog); + SystemUIDialog.setShowForAllUsers(dialog, true); + return dialog; + } + + private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) { + // Zen mode dialog is never hidden. + SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss); + zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel()); + } + @Override protected void handleSecondaryClick(@Nullable View view) { if (mController.isVolumeRestricted()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt new file mode 100644 index 000000000000..75cf4d1ace8e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.qs.tiles + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.provider.DeviceConfig +import android.view.View +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.logging.MetricsLogger +import com.android.systemui.DejankUtils +import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.fgsmanager.FgsManagerDialogFactory +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.statusbar.policy.RunningFgsController +import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Quicksettings tile for the foreground services manager (task manager) + */ +class FgsManagerTile @Inject constructor( + host: QSHost?, + @Background backgroundLooper: Looper?, + @Background private val backgroundExecutor: Executor?, + @Main mainHandler: Handler?, + falsingManager: FalsingManager?, + metricsLogger: MetricsLogger?, + statusBarStateController: StatusBarStateController?, + activityStarter: ActivityStarter?, + qsLogger: QSLogger?, + private val fgsManagerDialogFactory: FgsManagerDialogFactory, + private val runningFgsController: RunningFgsController +) : QSTileImpl<QSTile.State?>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback { + + override fun handleInitialize() { + super.handleInitialize() + mUiHandler.post { runningFgsController.observe(lifecycle, this) } + } + + override fun isAvailable(): Boolean { + return DejankUtils.whitelistIpcs<Boolean> { + DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED, false) + } + } + + override fun newTileState(): QSTile.State { + return QSTile.State() + } + + override fun handleClick(view: View?) { + mUiHandler.post { fgsManagerDialogFactory.create(view) } + } + + override fun handleUpdateState(state: QSTile.State?, arg: Any?) { + state?.label = tileLabel + state?.secondaryLabel = runningFgsController.getPackagesWithFgs().size.toString() + state?.handlesLongClick = false + state?.icon = ResourceIcon.get(R.drawable.ic_list) + } + + override fun getMetricsCategory(): Int = 0 + + override fun getLongClickIntent(): Intent? = null + + // Inline the string so we don't waste translator time since this isn't used in the mocks. + // TODO If mocks change need to remember to move this to strings.xml + override fun getTileLabel(): CharSequence = "Active apps" + + override fun onFgsPackagesChanged(packages: List<UserPackageTime>) = refreshState() +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index 521dbe714906..7e1712455e72 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -105,8 +105,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { @Override public Intent getLongClickIntent() { - // TODO(b/201743873) define new intent action ACTION_ONE_HANDED_SETTINGS in Settings. - return null; + return new Intent(Settings.ACTION_ONE_HANDED_SETTINGS); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 77b9cc14fa6d..033fe1e0e6a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -56,6 +56,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -120,6 +121,7 @@ public class InternetDialog extends SystemUIDialog implements private TextView mMobileTitleText; private TextView mMobileSummaryText; private Switch mMobileDataToggle; + private View mMobileToggleDivider; private Switch mWiFiToggle; private FrameLayout mDoneLayout; private Drawable mBackgroundOn; @@ -128,6 +130,7 @@ public class InternetDialog extends SystemUIDialog implements private boolean mCanConfigMobileData; // Wi-Fi entries + private int mWifiNetworkHeight; @VisibleForTesting protected WifiEntry mConnectedWifiEntry; @VisibleForTesting @@ -185,6 +188,9 @@ public class InternetDialog extends SystemUIDialog implements window.setWindowAnimations(R.style.Animation_InternetDialog); + mWifiNetworkHeight = mContext.getResources() + .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height); + mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); @@ -207,6 +213,7 @@ public class InternetDialog extends SystemUIDialog implements mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider); mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); @@ -332,9 +339,6 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { - if (isChecked) { - mWifiScanNotifyLayout.setVisibility(View.GONE); - } buttonView.setChecked(isChecked); mWifiManager.setWifiEnabled(isChecked); }); @@ -378,7 +382,17 @@ public class InternetDialog extends SystemUIDialog implements mMobileNetworkLayout.setBackground( isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff); + TypedArray array = mContext.obtainStyledAttributes( + R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background}); + int dividerColor = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondary); + mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected + ? array.getColor(0, dividerColor) : dividerColor); + array.recycle(); + mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); + mMobileToggleDivider.setVisibility( + mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); } } @@ -416,9 +430,26 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setVisibility(View.GONE); return; } - mWifiRecyclerView.setVisibility(mWifiEntriesCount > 0 ? View.VISIBLE : View.GONE); - mSeeAllLayout.setVisibility( - (mConnectedWifiEntry != null || mWifiEntriesCount > 0) ? View.VISIBLE : View.GONE); + mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount()); + mWifiRecyclerView.setVisibility(View.VISIBLE); + final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0; + mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE); + } + + @VisibleForTesting + @MainThread + int getWifiListMaxCount() { + int count = InternetDialogController.MAX_WIFI_ENTRY_COUNT; + if (mEthernetLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + if (mMobileNetworkLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + if (mConnectedWifListLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + return count; } @MainThread diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt deleted file mode 100644 index 26d1bbde2a54..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.user - -import android.content.Context -import android.os.Bundle -import android.view.Gravity -import android.view.View -import android.view.ViewGroup -import android.view.WindowInsets -import android.view.WindowManager -import com.android.systemui.qs.PseudoGridView -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.R - -/** - * Dialog for switching users or creating new ones. - */ -class UserDialog( - context: Context -) : SystemUIDialog(context) { - - // create() is no-op after creation - private lateinit var _doneButton: View - /** - * Button with text "Done" in dialog. - */ - val doneButton: View - get() { - create() - return _doneButton - } - - private lateinit var _settingsButton: View - /** - * Button with text "User Settings" in dialog. - */ - val settingsButton: View - get() { - create() - return _settingsButton - } - - private lateinit var _grid: PseudoGridView - /** - * Grid to populate with user avatar from adapter - */ - val grid: ViewGroup - get() { - create() - return _grid - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - window?.apply { - setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars() - attributes.receiveInsetsIgnoringZOrder = true - setGravity(Gravity.CENTER) - } - setContentView(R.layout.qs_user_dialog_content) - - _doneButton = requireViewById(R.id.done) - _settingsButton = requireViewById(R.id.settings) - _grid = requireViewById(R.id.grid) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt index d74a50e24ed3..00e04540fd94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt @@ -21,13 +21,16 @@ import android.content.Context import android.content.DialogInterface import android.content.Intent import android.provider.Settings +import android.view.LayoutInflater import android.view.View import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject import javax.inject.Provider @@ -40,7 +43,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, private val dialogLaunchAnimator: DialogLaunchAnimator, - private val dialogFactory: (Context) -> UserDialog + private val dialogFactory: (Context) -> SystemUIDialog ) { @Inject @@ -54,7 +57,7 @@ class UserSwitchDialogController @VisibleForTesting constructor( activityStarter, falsingManager, dialogLaunchAnimator, - { UserDialog(it) } + { SystemUIDialog(it) } ) companion object { @@ -71,9 +74,10 @@ class UserSwitchDialogController @VisibleForTesting constructor( with(dialogFactory(view.context)) { setShowForAllUsers(true) setCanceledOnTouchOutside(true) - create() // Needs to be called before we can retrieve views - settingsButton.setOnClickListener { + setTitle(R.string.qs_user_switch_dialog_title) + setPositiveButton(R.string.quick_settings_done, null) + setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ -> if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations() activityStarter.postStartActivityDismissingKeyguard( @@ -81,12 +85,14 @@ class UserSwitchDialogController @VisibleForTesting constructor( 0 ) } - dismiss() } - doneButton.setOnClickListener { dismiss() } + val gridFrame = LayoutInflater.from(this.context) + .inflate(R.layout.qs_user_dialog_content, null) + setView(gridFrame) val adapter = userDetailViewAdapterProvider.get() - adapter.linkToViewGroup(grid) + + adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid)) val hostDialog = dialogLaunchAnimator.showFromView(this, view) adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator)) diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index fa874b19c2cc..3ed7e84af020 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -986,6 +986,18 @@ public class OverviewProxyService extends CurrentUserTracker implements } } + public void onNavButtonsDarkIntensityChanged(float darkIntensity) { + try { + if (mOverviewProxy != null) { + mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity); + } else { + Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity"); + } + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e); + } + } + private void updateEnabledState() { final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId(); mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index a7f8bca7ada3..7bcaf5f0f6bb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -86,6 +86,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; +import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; @@ -162,6 +163,7 @@ public class ScreenshotView extends FrameLayout implements private GestureDetector mSwipeDetector; private SwipeDismissHandler mSwipeDismissHandler; private InputMonitorCompat mInputMonitor; + private InputChannelCompat.InputEventReceiver mInputEventReceiver; private boolean mShowScrollablePreview; private String mPackageName = ""; @@ -302,8 +304,8 @@ public class ScreenshotView extends FrameLayout implements private void startInputListening() { stopInputListening(); mInputMonitor = new InputMonitorCompat("Screenshot", Display.DEFAULT_DISPLAY); - mInputMonitor.getInputReceiver(Looper.getMainLooper(), Choreographer.getInstance(), - ev -> { + mInputEventReceiver = mInputMonitor.getInputReceiver( + Looper.getMainLooper(), Choreographer.getInstance(), ev -> { if (ev instanceof MotionEvent) { MotionEvent event = (MotionEvent) ev; if (event.getActionMasked() == MotionEvent.ACTION_DOWN @@ -320,6 +322,10 @@ public class ScreenshotView extends FrameLayout implements mInputMonitor.dispose(); mInputMonitor = null; } + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } } @Override // ViewGroup diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 75b3592d4ac6..3cecbb71407a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -533,9 +533,13 @@ public class CommandQueue extends IStatusBar.Stub implements * @param animate {@code true} to show animations. */ public void recomputeDisableFlags(int displayId, boolean animate) { - int disabled1 = getDisabled1(displayId); - int disabled2 = getDisabled2(displayId); - disable(displayId, disabled1, disabled2, animate); + // This must update holding the lock otherwise it can clobber the disabled flags set on the + // binder thread from the disable() call + synchronized (mLock) { + int disabled1 = getDisabled1(displayId); + int disabled2 = getDisabled2(displayId); + disable(displayId, disabled1, disabled2, animate); + } } private void setDisabled(int displayId, int disabled1, int disabled2) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 190c773ef422..f23a7cac7d2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -717,6 +717,9 @@ public class KeyguardIndicationController { textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); ViewClippingUtil.setClippingDeactivated(textView, false, mClippingParams); + // Unset the listener, otherwise this may persist for + // another view property animation + textView.animate().setListener(null); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index 5635f653aac5..2b5453a56434 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -33,6 +33,7 @@ import android.os.UserManager; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.ArraySet; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Pair; import android.view.MotionEvent; @@ -66,6 +67,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.RemoteInputView; +import com.android.systemui.util.DumpUtilsKt; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -653,9 +655,19 @@ public class NotificationRemoteInputManager implements Dumpable { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); + if (mRemoteInputController != null) { + pw.println("mRemoteInputController: " + mRemoteInputController); + pw.increaseIndent(); + mRemoteInputController.dump(pw); + pw.decreaseIndent(); + } if (mRemoteInputListener instanceof Dumpable) { + pw.println("mRemoteInputListener: " + mRemoteInputListener.getClass().getSimpleName()); + pw.increaseIndent(); ((Dumpable) mRemoteInputListener).dump(fd, pw, args); + pw.decreaseIndent(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index ecde001e6bf9..00e7a03457c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; + import android.content.Context; import android.content.res.Resources; import android.os.Handler; @@ -39,6 +41,8 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; +import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -97,6 +101,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle private final Context mContext; private NotificationPresenter mPresenter; + private NotifStackController mStackController; private NotificationListContainer mListContainer; // Used to help track down re-entrant calls to our update methods, which will cause bugs. @@ -147,8 +152,10 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } public void setUpWithPresenter(NotificationPresenter presenter, + NotifStackController stackController, NotificationListContainer listContainer) { mPresenter = presenter; + mStackController = stackController; mListContainer = listContainer; if (!mNotifPipelineFlags.isNewPipelineEnabled()) { mDynamicPrivacyController.addListener(this); @@ -328,6 +335,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle mTmpChildOrderMap.clear(); updateRowStatesInternal(); + updateNotifStats(); mListContainer.onNotificationViewUpdateFinished(); @@ -335,6 +343,56 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle } /** + * In the spirit of unidirectional data flow, calculate this information when the notification + * views are updated, and set it once, speeding up lookups later. + * This is analogous to logic in the + * {@link com.android.systemui.statusbar.notification.collection.coordinator.StackCoordinator} + */ + private void updateNotifStats() { + boolean hasNonClearableAlertingNotifs = false; + boolean hasClearableAlertingNotifs = false; + boolean hasNonClearableSilentNotifs = false; + boolean hasClearableSilentNotifs = false; + final int childCount = mListContainer.getContainerChildCount(); + int visibleTopLevelEntries = 0; + for (int i = 0; i < childCount; i++) { + View child = mListContainer.getContainerChildAt(i); + if (child == null || child.getVisibility() == View.GONE) { + continue; + } + if (!(child instanceof ExpandableNotificationRow)) { + continue; + } + final ExpandableNotificationRow row = (ExpandableNotificationRow) child; + boolean isSilent = row.getEntry().getBucket() == BUCKET_SILENT; + // NOTE: NotificationEntry.isClearable() will internally check group children to ensure + // the group itself definitively clearable. + boolean isClearable = row.getEntry().isClearable(); + visibleTopLevelEntries++; + if (isSilent) { + if (isClearable) { + hasClearableSilentNotifs = true; + } else { // !isClearable + hasNonClearableSilentNotifs = true; + } + } else { // !isSilent + if (isClearable) { + hasClearableAlertingNotifs = true; + } else { // !isClearable + hasNonClearableAlertingNotifs = true; + } + } + } + mStackController.setNotifStats(new NotifStats( + visibleTopLevelEntries /* numActiveNotifs */, + hasNonClearableAlertingNotifs /* hasNonClearableAlertingNotifs */, + hasClearableAlertingNotifs /* hasClearableAlertingNotifs */, + hasNonClearableSilentNotifs /* hasNonClearableSilentNotifs */, + hasClearableSilentNotifs /* hasClearableSilentNotifs */ + )); + } + + /** * Should a notification entry from the active list be suppressed and not show? */ private boolean shouldSuppressActiveNotification(NotificationEntry ent) { @@ -528,9 +586,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle @Override public void onDynamicPrivacyChanged() { - if (mNotifPipelineFlags.isNewPipelineEnabled()) { - throw new IllegalStateException("Old pipeline code running w/ new pipeline enabled"); - } + mNotifPipelineFlags.assertLegacyPipelineEnabled(); if (mPerformingUpdate) { Log.w(TAG, "onDynamicPrivacyChanged made a re-entrant call"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index cde3b0e2e76b..31ab6bdf0240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -23,11 +23,15 @@ import android.net.Uri; import android.os.SystemProperties; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import android.util.IndentingPrintWriter; import android.util.Pair; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.policy.RemoteInputView; +import com.android.systemui.util.DumpUtilsKt; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -293,6 +297,28 @@ public class RemoteInputController { mRemoteInputUriController.grantInlineReplyUriPermission(sbn, data); } + /** dump debug info; called by {@link NotificationRemoteInputManager} */ + public void dump(@NonNull IndentingPrintWriter pw) { + pw.print("isRemoteInputActive: "); + pw.println(isRemoteInputActive()); // Note that this prunes the mOpen list, printed later. + pw.println("mOpen: " + mOpen.size()); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + for (Pair<WeakReference<NotificationEntry>, Object> open : mOpen) { + NotificationEntry entry = open.first.get(); + pw.println(entry == null ? "???" : entry.getKey()); + } + }); + pw.println("mSpinning: " + mSpinning.size()); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + for (String key : mSpinning.keySet()) { + pw.println(key); + } + }); + pw.println(mSpinning); + pw.print("mDelegate: "); + pw.println(mDelegate); + } + public interface Callback { default void onRemoteInputActive(boolean active) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index da2b85ee0b61..2dbe59e27ea6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -96,6 +96,7 @@ public class StatusBarStateControllerImpl implements private final ArrayList<RankedListener> mListeners = new ArrayList<>(); private final UiEventLogger mUiEventLogger; + private final InteractionJankMonitor mInteractionJankMonitor; private int mState; private int mLastState; private int mUpcomingState; @@ -149,8 +150,10 @@ public class StatusBarStateControllerImpl implements private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN; @Inject - public StatusBarStateControllerImpl(UiEventLogger uiEventLogger, DumpManager dumpManager) { + public StatusBarStateControllerImpl(UiEventLogger uiEventLogger, DumpManager dumpManager, + InteractionJankMonitor interactionJankMonitor) { mUiEventLogger = uiEventLogger; + mInteractionJankMonitor = interactionJankMonitor; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } @@ -344,17 +347,23 @@ public class StatusBarStateControllerImpl implements } private void beginInteractionJankMonitor() { - if (mView != null && mView.isAttachedToWindow()) { - InteractionJankMonitor.getInstance().begin(mView, getCujType()); + if (mInteractionJankMonitor != null && mView != null && mView.isAttachedToWindow()) { + mInteractionJankMonitor.begin(mView, getCujType()); } } private void endInteractionJankMonitor() { - InteractionJankMonitor.getInstance().end(getCujType()); + if (mInteractionJankMonitor == null) { + return; + } + mInteractionJankMonitor.end(getCujType()); } private void cancelInteractionJankMonitor() { - InteractionJankMonitor.getInstance().cancel(getCujType()); + if (mInteractionJankMonitor == null) { + return; + } + mInteractionJankMonitor.cancel(getCujType()); } private int getCujType() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 0fb9fc80e510..1432f788a6fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -24,11 +24,11 @@ import com.android.systemui.flags.Flags import javax.inject.Inject class NotifPipelineFlags @Inject constructor( - val context: Context, - val featureFlags: FeatureFlags + val context: Context, + val featureFlags: FeatureFlags ) { fun checkLegacyPipelineEnabled(): Boolean { - if (!featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)) { + if (!isNewPipelineEnabled()) { return true } Log.d("NotifPipeline", "Old pipeline code running w/ new pipeline enabled", Exception()) @@ -36,10 +36,16 @@ class NotifPipelineFlags @Inject constructor( return false } - fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled( - Flags.NEW_NOTIFICATION_PIPELINE_RENDERING) + fun assertLegacyPipelineEnabled(): Unit = + check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" } + + fun isNewPipelineEnabled(): Boolean = + featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING) + + fun isDevLoggingEnabled(): Boolean = + featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING) fun isSmartspaceDedupingEnabled(): Boolean = - featureFlags.isEnabled(Flags.SMARTSPACE) - && featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING) + featureFlags.isEnabled(Flags.SMARTSPACE) && + featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING) }
\ No newline at end of file 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 e78b4f43f00a..0389a7b01c72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -791,6 +791,7 @@ public class NotificationEntryManager implements * these don't exist, although there are a couple exceptions. */ public Iterable<NotificationEntry> getPendingNotificationsIterator() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mPendingNotifications.values(); } @@ -803,6 +804,7 @@ public class NotificationEntryManager implements * @return a {@link NotificationEntry} if it has been prepared, else null */ public NotificationEntry getActiveNotificationUnfiltered(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mActiveNotifications.get(key); } @@ -811,6 +813,7 @@ public class NotificationEntryManager implements * notification doesn't exist. */ public NotificationEntry getPendingOrActiveNotif(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); NotificationEntry entry = mPendingNotifications.get(key); if (entry != null) { return entry; @@ -945,6 +948,7 @@ public class NotificationEntryManager implements * @return A read-only list of the currently active notifications */ public List<NotificationEntry> getVisibleNotifications() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications; } @@ -954,17 +958,20 @@ public class NotificationEntryManager implements */ @Override public Collection<NotificationEntry> getAllNotifs() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyAllNotifications; } @Nullable @Override public NotificationEntry getEntry(String key) { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return getPendingOrActiveNotif(key); } /** @return A count of the active notifications */ public int getActiveNotificationsCount() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications.size(); } @@ -972,6 +979,7 @@ public class NotificationEntryManager implements * @return {@code true} if there is at least one notification that should be visible right now */ public boolean hasActiveNotifications() { + mNotifPipelineFlags.checkLegacyPipelineEnabled(); return mReadOnlyNotifications.size() != 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java new file mode 100644 index 000000000000..8d2e3c92c92a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification; + +import android.view.View; + +import androidx.annotation.Nullable; + +/** + * Used to let views that have an alpha not apply the HARDWARE layer type directly, and instead + * delegate that to specific children. This is useful if we want to fake not having overlapping + * rendering to avoid layer trashing, when fading out a view that is also changing. + */ +public interface NotificationFadeAware { + /** + * Calls {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} if faded and + * {@link View#LAYER_TYPE_NONE} otherwise. + */ + static void setLayerTypeForFaded(@Nullable View view, boolean faded) { + if (view != null) { + int newLayerType = faded ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; + view.setLayerType(newLayerType, null); + } + } + + /** + * Used like {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} or + * {@link View#LAYER_TYPE_NONE} except that instead of necessarily affecting this view + * specifically, this may delegate the call to child views. + * + * When set to <code>true</code>, the view has two possible paths: + * 1. If a hardware layer is required to ensure correct appearance of this view, then + * set that layer type. + * 2. Otherwise, delegate this call to children, who might make that call for themselves. + * + * When set to <code>false</code>, the view should undo the above, typically by calling + * {@link View#setLayerType} with {@link View#LAYER_TYPE_NONE} on itself and children, and + * delegating to this method on children where implemented. + * + * When this delegates to {@link View#setLayerType} on this view or a subview, `null` will be + * passed for the `paint` argument of that call. + */ + void setNotificationFaded(boolean faded); + + /** + * Interface for the top level notification view that fades and optimizes that through deep + * awareness of individual components. + */ + interface FadeOptimizedNotification extends NotificationFadeAware { + /** Top-level feature switch */ + boolean FADE_LAYER_OPTIMIZATION_ENABLED = true; + + /** Determine if the notification is currently faded. */ + boolean isNotificationFaded(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt new file mode 100644 index 000000000000..1f2d0fe6c46e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import javax.inject.Inject + +/** + * A class which is used to classify the sections. + * NOTE: This class exists to avoid putting metadata like "isMinimized" on the NotifSection + */ +@SysUISingleton +class SectionClassifier @Inject constructor() { + private lateinit var lowPrioritySections: Set<NotifSectioner> + + /** + * Feed the provider the information it needs about which sections should have minimized top + * level views, so that it can calculate the correct minimized state. + */ + fun setMinimizedSections(sections: Collection<NotifSectioner>) { + lowPrioritySections = sections.toSet() + } + + /** + * Determine if the given section is minimized + */ + fun isMinimizedSection(section: NotifSection): Boolean { + return lowPrioritySections.contains(section.sectioner) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index 0ea685793214..918843cedd2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator; - import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -61,24 +59,6 @@ public class GroupEntry extends ListEntry { mSummary = summary; } - /** - * @see #getUntruncatedChildCount() - */ - public void setUntruncatedChildCount(int childCount) { - mUntruncatedChildCount = childCount; - } - - /** - * Get the untruncated number of children from the data model, including those that will not - * have views bound. This includes children that {@link PreparationCoordinator} will filter out - * entirely when they are beyond the last visible child. - * - * TODO: This should move to some shared class between the model and view hierarchy - */ - public int getUntruncatedChildCount() { - return mUntruncatedChildCount; - } - void clearChildren() { mChildren.clear(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index 52c5c3e08118..6be8a491eead 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -55,15 +55,26 @@ public class ListDumper { interactionTracker.hasUserInteractedWith(entry.getKey())); if (entry instanceof GroupEntry) { GroupEntry ge = (GroupEntry) entry; + NotificationEntry summary = ge.getSummary(); + if (summary != null) { + dumpEntry(summary, + topEntryIndex + ":*", + childEntryIndent, + sb, + true, + includeRecordKeeping, + interactionTracker.hasUserInteractedWith(summary.getKey())); + } List<NotificationEntry> children = ge.getChildren(); for (int childIndex = 0; childIndex < children.size(); childIndex++) { - dumpEntry(children.get(childIndex), + NotificationEntry child = children.get(childIndex); + dumpEntry(child, topEntryIndex + "." + childIndex, childEntryIndent, sb, true, includeRecordKeeping, - interactionTracker.hasUserInteractedWith(entry.getKey())); + interactionTracker.hasUserInteractedWith(child.getKey())); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 2d6522e26686..f8f1279044d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -245,9 +245,15 @@ public class NotifCollection implements Dumpable { DismissedByUserStats stats = entriesToDismiss.get(i).second; requireNonNull(stats); - if (entry != mNotificationSet.get(entry.getKey())) { + NotificationEntry storedEntry = mNotificationSet.get(entry.getKey()); + if (storedEntry == null) { + mLogger.logNonExistentNotifDismissed(entry.getKey()); + continue; + } + if (entry != storedEntry) { throw mEulogizer.record( - new IllegalStateException("Invalid entry: " + entry.getKey())); + new IllegalStateException("Invalid entry: " + + "different stored and dismissed entries for " + entry.getKey())); } if (entry.getDismissState() == DISMISSED) { @@ -489,6 +495,37 @@ public class NotifCollection implements Dumpable { } } + /** + * Get the group summary entry + * @param group + * @return + */ + @Nullable + public NotificationEntry getGroupSummary(String group) { + return mNotificationSet + .values() + .stream() + .filter(it -> Objects.equals(it.getSbn().getGroup(), group)) + .filter(it -> it.getSbn().getNotification().isGroupSummary()) + .findFirst().orElse(null); + } + + /** + * Checks if the entry is the only child in the logical group + * @param entry + * @return + */ + public boolean isOnlyChildInGroup(NotificationEntry entry) { + String group = entry.getSbn().getGroup(); + return mNotificationSet.get(entry.getKey()) == entry + && mNotificationSet + .values() + .stream() + .filter(it -> Objects.equals(it.getSbn().getGroup(), group)) + .filter(it -> !it.getSbn().getNotification().isGroupSummary()) + .count() == 1; + } + private void applyRanking(@NonNull RankingMap rankingMap) { for (NotificationEntry entry : mNotificationSet.values()) { if (!isCanceled(entry)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 4f3c287d5f1e..4daed77c0954 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -93,7 +93,7 @@ public class NotifInflaterImpl implements NotifInflater { public void onAsyncInflationFinished(NotificationEntry entry) { mNotifErrorManager.clearInflationError(entry); if (callback != null) { - callback.onInflationFinished(entry); + callback.onInflationFinished(entry, entry.getRowController()); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt index 9ae9fe508944..6fbed9a8d3b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.collection import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener @@ -31,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.In import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.statusbar.notification.collection.render.RenderStageManager import javax.inject.Inject /** @@ -65,11 +69,15 @@ import javax.inject.Inject * 9. Finalize filters are fired on each notification ([.addFinalizeFilter]) * 10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener]) * 11. The list is handed off to the view layer to be rendered + * 12. OnAfterRenderListListeners are fired ([.addOnAfterRenderListListener]) + * 13. OnAfterRenderGroupListeners are fired ([.addOnAfterRenderGroupListener]) + * 13. OnAfterRenderEntryListeners are fired ([.addOnAfterRenderEntryListener]) */ @SysUISingleton class NotifPipeline @Inject constructor( private val mNotifCollection: NotifCollection, - private val mShadeListBuilder: ShadeListBuilder + private val mShadeListBuilder: ShadeListBuilder, + private val mRenderStageManager: RenderStageManager ) : CommonNotifCollection { /** * Returns the list of all known notifications, i.e. the notifications that are currently posted @@ -206,6 +214,28 @@ class NotifPipeline @Inject constructor( } /** + * Called at the end of the pipeline after the notif list has been handed off to the view layer. + */ + fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) { + mRenderStageManager.addOnAfterRenderListListener(listener) + } + + /** + * Called at the end of the pipeline after a group has been handed off to the view layer. + */ + fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) { + mRenderStageManager.addOnAfterRenderGroupListener(listener) + } + + /** + * Called at the end of the pipeline after an entry has been handed off to the view layer. + * This will be called for every top level entry, every group summary, and every group child. + */ + fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) { + mRenderStageManager.addOnAfterRenderEntryListener(listener) + } + + /** * Get an object which can be used to update a notification (internally to the pipeline) * in response to a user action. * @@ -218,8 +248,8 @@ class NotifPipeline @Inject constructor( /** * Returns a read-only view in to the current shade list, i.e. the list of notifications that - * are currently present in the shade. If this method is called during pipeline execution it - * will return the current state of the list, which will likely be only partially-generated. + * are currently present in the shade. + * @throws IllegalStateException if called during pipeline execution. */ val shadeList: List<ListEntry> get() = mShadeListBuilder.shadeList @@ -227,21 +257,20 @@ class NotifPipeline @Inject constructor( /** * Constructs a flattened representation of the notification tree, where each group will have * the summary (if present) followed by the children. + * @throws IllegalStateException if called during pipeline execution. */ fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry -> when (entry) { is NotificationEntry -> sequenceOf(entry) - is GroupEntry -> (entry.summary?.let { sequenceOf(it) }.orEmpty() + - entry.children) + is GroupEntry -> sequenceOf(entry.summary).filterNotNull() + entry.children else -> throw RuntimeException("Unexpected entry $entry") } } /** * Returns the number of notifications currently shown in the shade. This includes all - * children and summary notifications. If this method is called during pipeline execution it - * will return the number of notifications in its current state, which will likely be only - * partially-generated. + * children and summary notifications. + * @throws IllegalStateException if called during pipeline execution. */ fun getShadeListCount(): Int = shadeList.sumOf { entry -> // include the summary in the count diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 72cd95128779..748c69d24c36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -42,6 +42,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.NotificationInteractionTracker; +import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; @@ -87,6 +88,7 @@ public class ShadeListBuilder implements Dumpable { private final NotificationInteractionTracker mInteractionTracker; // used exclusivly by ShadeListBuilder#notifySectionEntriesUpdated private final ArrayList<ListEntry> mTempSectionMembers = new ArrayList<>(); + private final boolean mAlwaysLogList; private List<ListEntry> mNotifList = new ArrayList<>(); private List<ListEntry> mNewNotifList = new ArrayList<>(); @@ -119,6 +121,7 @@ public class ShadeListBuilder implements Dumpable { @Inject public ShadeListBuilder( SystemClock systemClock, + NotifPipelineFlags flags, ShadeListBuilderLogger logger, DumpManager dumpManager, NotificationInteractionTracker interactionTracker @@ -126,6 +129,7 @@ public class ShadeListBuilder implements Dumpable { Assert.isMainThread(); mSystemClock = systemClock; mLogger = logger; + mAlwaysLogList = flags.isDevLoggingEnabled(); mInteractionTracker = interactionTracker; dumpManager.registerDumpable(TAG, this); @@ -253,6 +257,9 @@ public class ShadeListBuilder implements Dumpable { List<ListEntry> getShadeList() { Assert.isMainThread(); + // NOTE: Accessing this method when the pipeline is running is generally going to provide + // incorrect results, and indicates a poorly behaved component of the pipeline. + mPipelineState.requireState(STATE_IDLE); return mReadOnlyNotifList; } @@ -404,7 +411,7 @@ public class ShadeListBuilder implements Dumpable { mIterationCount, mReadOnlyNotifList.size(), countChildren(mReadOnlyNotifList)); - if (mIterationCount % 10 == 0) { + if (mAlwaysLogList || mIterationCount % 10 == 0) { mLogger.logFinalList(mNotifList); } mPipelineState.setState(STATE_IDLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java index d013261a4193..e9b7caa565ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; +import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -57,6 +58,7 @@ import javax.inject.Inject; public class BubbleCoordinator implements Coordinator { private static final String TAG = "BubbleCoordinator"; + private final NotifPipelineFlags mNotifPipelineFlags; private final Optional<BubblesManager> mBubblesManagerOptional; private final Optional<Bubbles> mBubblesOptional; private final NotifCollection mNotifCollection; @@ -66,9 +68,11 @@ public class BubbleCoordinator implements Coordinator { @Inject public BubbleCoordinator( + NotifPipelineFlags notifPipelineFlags, Optional<BubblesManager> bubblesManagerOptional, Optional<Bubbles> bubblesOptional, NotifCollection notifCollection) { + mNotifPipelineFlags = notifPipelineFlags; mBubblesManagerOptional = bubblesManagerOptional; mBubblesOptional = bubblesOptional; mNotifCollection = notifCollection; @@ -130,6 +134,14 @@ public class BubbleCoordinator implements Coordinator { DismissedByUserStats dismissedByUserStats, int reason ) { + if (!mNotifPipelineFlags.isNewPipelineEnabled()) { + // The `entry` will be from whichever pipeline is active, so if the old pipeline is + // running, make sure that we use the new pipeline's entry (if it still exists). + NotificationEntry newPipelineEntry = mNotifPipeline.getEntry(entry.getKey()); + if (newPipelineEntry != null) { + entry = newPipelineEntry; + } + } if (isInterceptingDismissal(entry)) { mInterceptedDismissalEntries.remove(entry.getKey()); mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt new file mode 100644 index 000000000000..82b1268e4797 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt @@ -0,0 +1,35 @@ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.ArrayMap +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import javax.inject.Inject + +/** A small coordinator which calculates, stores, and applies the untruncated child count. */ +@CoordinatorScope +class GroupCountCoordinator @Inject constructor() : Coordinator { + private val untruncatedChildCounts = ArrayMap<GroupEntry, Int>() + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter) + pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroup) + } + + private fun onBeforeFinalizeFilter(entries: List<ListEntry>) { + // save untruncated child counts to our internal map + untruncatedChildCounts.clear() + entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> + untruncatedChildCounts[groupEntry] = groupEntry.children.size + } + } + + private fun onAfterRenderGroup(group: GroupEntry, controller: NotifGroupController) { + // find the untruncated child count for a group and apply it to the controller + val count = untruncatedChildCounts[group] + checkNotNull(count) { "No untruncated child count for group: ${group.key}" } + controller.setUntruncatedChildCount(count) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index dae76f810bad..a16b565d488c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -46,8 +46,11 @@ class NotifCoordinatorsImpl @Inject constructor( gutsCoordinator: GutsCoordinator, conversationCoordinator: ConversationCoordinator, preparationCoordinator: PreparationCoordinator, + groupCountCoordinator: GroupCountCoordinator, mediaCoordinator: MediaCoordinator, remoteInputCoordinator: RemoteInputCoordinator, + rowAppearanceCoordinator: RowAppearanceCoordinator, + stackCoordinator: StackCoordinator, shadeEventCoordinator: ShadeEventCoordinator, smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, viewConfigCoordinator: ViewConfigCoordinator, @@ -72,8 +75,11 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(deviceProvisionedCoordinator) mCoordinators.add(bubbleCoordinator) mCoordinators.add(conversationCoordinator) + mCoordinators.add(groupCountCoordinator) mCoordinators.add(mediaCoordinator) mCoordinators.add(remoteInputCoordinator) + mCoordinators.add(rowAppearanceCoordinator) + mCoordinators.add(stackCoordinator) mCoordinators.add(shadeEventCoordinator) mCoordinators.add(viewConfigCoordinator) mCoordinators.add(visualStabilityCoordinator) @@ -89,7 +95,7 @@ class NotifCoordinatorsImpl @Inject constructor( } // Manually add Ordered Sections - // HeadsUp > FGS > People > Alerting > Silent > Unknown/Default + // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default if (notifPipelineFlags.isNewPipelineEnabled()) { mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 644f248fca00..bbb97d145b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -31,19 +31,19 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; -import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn; +import com.android.systemui.statusbar.notification.collection.render.NotifViewController; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener; @@ -61,8 +61,7 @@ import javax.inject.Inject; * If a notification was uninflated, this coordinator will filter the notification out from the * {@link ShadeListBuilder} until it is inflated. */ -// TODO(b/204468557): Move to @CoordinatorScope -@SysUISingleton +@CoordinatorScope public class PreparationCoordinator implements Coordinator { private static final String TAG = "PreparationCoordinator"; @@ -145,7 +144,7 @@ public class PreparationCoordinator implements Coordinator { pipeline.addCollectionListener(mNotifCollectionListener); // Inflate after grouping/sorting since that affects what views to inflate. - pipeline.addOnBeforeFinalizeFilterListener(mOnBeforeFinalizeFilterListener); + pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllRequiredViews); pipeline.addFinalizeFilter(mNotifInflationErrorFilter); pipeline.addFinalizeFilter(mNotifInflatingFilter); } @@ -182,9 +181,6 @@ public class PreparationCoordinator implements Coordinator { } }; - private final OnBeforeFinalizeFilterListener mOnBeforeFinalizeFilterListener = - entries -> inflateAllRequiredViews(entries); - private final NotifFilter mNotifInflationErrorFilter = new NotifFilter( TAG + "InflationError") { /** @@ -256,7 +252,6 @@ public class PreparationCoordinator implements Coordinator { ListEntry entry = entries.get(i); if (entry instanceof GroupEntry) { GroupEntry groupEntry = (GroupEntry) entry; - groupEntry.setUntruncatedChildCount(groupEntry.getChildren().size()); inflateRequiredGroupViews(groupEntry); } else { NotificationEntry notifEntry = (NotificationEntry) entry; @@ -363,17 +358,17 @@ public class PreparationCoordinator implements Coordinator { mInflatingNotifs.remove(entry); } - private void onInflationFinished(NotificationEntry entry) { + private void onInflationFinished(NotificationEntry entry, NotifViewController controller) { mLogger.logNotifInflated(entry.getKey()); mInflatingNotifs.remove(entry); - mViewBarn.registerViewForEntry(entry, entry.getRowController()); + mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); mNotifInflatingFilter.invalidateList(); } private void freeNotifViews(NotificationEntry entry) { mViewBarn.removeViewForEntry(entry); - entry.setRow(null); + // TODO: clear the entry's row here, or even better, stop setting the row on the entry! mInflationStates.put(entry, STATE_UNINFLATED); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index c60ebcdc5fd1..57fd1975e13a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -20,11 +20,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; -import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; @@ -51,7 +51,7 @@ public class RankingCoordinator implements Coordinator { public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; - private final NotifUiAdjustmentProvider mAdjustmentProvider; + private final SectionClassifier mSectionClassifier; private final NodeController mSilentNodeController; private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @@ -62,13 +62,13 @@ public class RankingCoordinator implements Coordinator { public RankingCoordinator( StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, - NotifUiAdjustmentProvider adjustmentProvider, + SectionClassifier sectionClassifier, @AlertingHeader NodeController alertingHeaderController, @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; - mAdjustmentProvider = adjustmentProvider; + mSectionClassifier = sectionClassifier; mAlertingHeaderController = alertingHeaderController; mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; @@ -77,7 +77,7 @@ public class RankingCoordinator implements Coordinator { @Override public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - mAdjustmentProvider.setLowPrioritySections(Collections.singleton(mMinimizedNotifSectioner)); + mSectionClassifier.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner)); pipeline.addPreGroupFilter(mSuspendedFilter); pipeline.addPreGroupFilter(mDndVisualEffectsFilter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt index 3397815f008f..2608c30e33c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt @@ -22,7 +22,6 @@ import android.service.notification.NotificationListenerService.REASON_CLICK import android.util.Log import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.NotificationRemoteInputManager @@ -32,6 +31,7 @@ import com.android.systemui.statusbar.RemoteInputNotificationRebuilder import com.android.systemui.statusbar.SmartReplyController import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.notifcollection.SelfTrackingLifetimeExtender import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -61,7 +61,7 @@ private const val REMOTE_INPUT_EXTENDER_RELEASE_DELAY: Long = 200 /** Whether this class should print spammy debug logs */ private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) } -@SysUISingleton +@CoordinatorScope class RemoteInputCoordinator @Inject constructor( dumpManager: DumpManager, private val mRebuilder: RemoteInputNotificationRebuilder, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt new file mode 100644 index 000000000000..c8f736027e50 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.SectionClassifier +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.render.NotifRowController +import javax.inject.Inject + +/** + * A small coordinator which updates the notif rows with data related to the current shade after + * they are fully attached. + */ +@CoordinatorScope +class RowAppearanceCoordinator @Inject internal constructor( + context: Context, + private var mAssistantFeedbackController: AssistantFeedbackController, + private var mSectionClassifier: SectionClassifier +) : Coordinator { + + private var entryToExpand: NotificationEntry? = null + + /** + * `true` if notifications not part of a group should by default be rendered in their + * expanded state. If `false`, then only the first notification will be expanded if + * possible. + */ + private val mAlwaysExpandNonGroupedNotification = + context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications) + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnBeforeRenderListListener(::onBeforeRenderList) + pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry) + } + + private fun onBeforeRenderList(list: List<ListEntry>) { + entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry -> + !mSectionClassifier.isMinimizedSection(entry.section!!) + } + } + + private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) { + // If mAlwaysExpandNonGroupedNotification is false, then only expand the + // very first notification and if it's not a child of grouped notifications. + controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification || entry == entryToExpand) + // Show/hide the feedback icon + controller.showFeedbackIcon( + mAssistantFeedbackController.showFeedbackIndicator(entry), + mAssistantFeedbackController.getFeedbackResources(entry) + ) + // Show the "alerted" bell icon + controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt new file mode 100644 index 000000000000..38f11fc88b72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.collection.render.NotifStackController +import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT +import com.android.systemui.statusbar.phone.NotificationIconAreaController +import javax.inject.Inject + +/** + * A small coordinator which updates the notif stack (the view layer which holds notifications) + * with high-level data after the stack is populated with the final entries. + */ +@CoordinatorScope +class StackCoordinator @Inject internal constructor( + private val notificationIconAreaController: NotificationIconAreaController +) : Coordinator { + + override fun attach(pipeline: NotifPipeline) { + pipeline.addOnAfterRenderListListener(::onAfterRenderList) + } + + fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) { + controller.setNotifStats(calculateNotifStats(entries)) + notificationIconAreaController.updateNotificationIcons(entries) + } + + private fun calculateNotifStats(entries: List<ListEntry>): NotifStats { + var hasNonClearableAlertingNotifs = false + var hasClearableAlertingNotifs = false + var hasNonClearableSilentNotifs = false + var hasClearableSilentNotifs = false + entries.forEach { + val isSilent = it.section!!.bucket == BUCKET_SILENT + // NOTE: NotificationEntry.isClearable will internally check group children to ensure + // the group itself definitively clearable. + val isClearable = it.representativeEntry!!.isClearable + when { + isSilent && isClearable -> hasClearableSilentNotifs = true + isSilent && !isClearable -> hasNonClearableSilentNotifs = true + !isSilent && isClearable -> hasClearableAlertingNotifs = true + !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true + } + } + val stats = NotifStats( + numActiveNotifs = entries.size, + hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs, + hasClearableAlertingNotifs = hasClearableAlertingNotifs, + hasNonClearableSilentNotifs = hasNonClearableSilentNotifs, + hasClearableSilentNotifs = hasClearableSilentNotifs + ) + return stats + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt index c59f18436b74..d98e7f76a11b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.inflation import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.render.NotifViewController /** * Used by the [PreparationCoordinator]. When notifications are added or updated, the @@ -49,7 +50,7 @@ interface NotifInflater { * Callback once all the views are inflated and bound for a given NotificationEntry. */ interface InflationCallback { - fun onInflationFinished(entry: NotificationEntry) + fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index 3290cdffdceb..497691d18844 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.notification.collection.inflation import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.SectionClassifier import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import javax.inject.Inject /** @@ -27,25 +27,17 @@ import javax.inject.Inject * to ensure that notifications are reinflated when ranking-derived information changes. */ @SysUISingleton -open class NotifUiAdjustmentProvider @Inject constructor() { - - private lateinit var lowPrioritySections: Set<NotifSectioner> - - /** - * Feed the provider the information it needs about which sections should have minimized top - * level views, so that it can calculate the correct minimized value in the adjustment. - */ - fun setLowPrioritySections(sections: Collection<NotifSectioner>) { - lowPrioritySections = sections.toSet() - } +open class NotifUiAdjustmentProvider @Inject constructor( + private val sectionClassifier: SectionClassifier +) { private fun isEntryMinimized(entry: NotificationEntry): Boolean { val section = entry.section ?: error("Entry must have a section to determine if minimized") val parent = entry.parent ?: error("Entry must have a parent to determine if minimized") - val isLowPrioritySection = lowPrioritySections.contains(section.sectioner) + val isMinimizedSection = sectionClassifier.isMinimizedSection(section) val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY val isGroupSummary = parent.summary == entry - return isLowPrioritySection && (isTopLevelEntry || isGroupSummary) + return isMinimizedSection && (isTopLevelEntry || isGroupSummary) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java index 66ec30d6ee07..f50038c08bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java @@ -108,11 +108,10 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback @Override @Nullable public NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) { - if (entry.getParent() != null - && entry.getParent().getSummary() != null - && mGroupMembershipManager.isOnlyChildInGroup(entry)) { - NotificationEntry groupSummary = entry.getParent().getSummary(); - return groupSummary.isClearable() ? groupSummary : null; + String group = entry.getSbn().getGroup(); + if (mNotifCollection.isOnlyChildInGroup(entry)) { + NotificationEntry summary = mNotifCollection.getGroupSummary(group); + if (summary != null && summary.isClearable()) return summary; } return null; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java index a8f3730b1cdd..f9358eb2df33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java @@ -30,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; +import com.android.systemui.statusbar.notification.collection.render.RenderStageManager; import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -47,6 +49,7 @@ public class NotifPipelineInitializer implements Dumpable { private final GroupCoalescer mGroupCoalescer; private final NotifCollection mNotifCollection; private final ShadeListBuilder mListBuilder; + private final RenderStageManager mRenderStageManager; private final NotifCoordinators mNotifPluggableCoordinators; private final NotifInflaterImpl mNotifInflater; private final DumpManager mDumpManager; @@ -60,15 +63,18 @@ public class NotifPipelineInitializer implements Dumpable { GroupCoalescer groupCoalescer, NotifCollection notifCollection, ShadeListBuilder listBuilder, + RenderStageManager renderStageManager, NotifCoordinators notifCoordinators, NotifInflaterImpl notifInflater, DumpManager dumpManager, ShadeViewManagerFactory shadeViewManagerFactory, - NotifPipelineFlags notifPipelineFlags) { + NotifPipelineFlags notifPipelineFlags + ) { mPipelineWrapper = pipelineWrapper; mGroupCoalescer = groupCoalescer; mNotifCollection = notifCollection; mListBuilder = listBuilder; + mRenderStageManager = renderStageManager; mNotifPluggableCoordinators = notifCoordinators; mDumpManager = dumpManager; mNotifInflater = notifInflater; @@ -80,7 +86,8 @@ public class NotifPipelineInitializer implements Dumpable { public void initialize( NotificationListener notificationService, NotificationRowBinderImpl rowBinder, - NotificationListContainer listContainer) { + NotificationListContainer listContainer, + NotifStackController stackController) { mDumpManager.registerDumpable("NotifPipeline", this); @@ -94,8 +101,11 @@ public class NotifPipelineInitializer implements Dumpable { // Wire up pipeline if (mNotifPipelineFlags.isNewPipelineEnabled()) { - mShadeViewManagerFactory.create(listContainer).attach(mListBuilder); + mShadeViewManagerFactory + .create(listContainer, stackController) + .attach(mRenderStageManager); } + mRenderStageManager.attach(mListBuilder); mListBuilder.attach(mNotifCollection); mNotifCollection.attach(mGroupCoalescer); mGroupCoalescer.attach(notificationService); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java new file mode 100644 index 000000000000..20cd6dd3614c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderEntryListener.java @@ -0,0 +1,37 @@ +/* + * 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.listbuilder; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.render.NotifRowController; + +/** See {@link NotifPipeline#addOnAfterRenderEntryListener(OnAfterRenderEntryListener)} */ +public interface OnAfterRenderEntryListener { + /** + * Called at the end of the pipeline after an entry has been handed off to the view layer. + * This will be called for every top level entry, every group summary, and every group child. + * + * @param entry the entry to read from. + * @param controller the object to which data can be pushed. + */ + void onAfterRenderEntry( + @NonNull NotificationEntry entry, + @NonNull NotifRowController controller); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.java new file mode 100644 index 000000000000..b1a4b653c56c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderGroupListener.java @@ -0,0 +1,36 @@ +/* + * 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.listbuilder; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController; + +/** See {@link NotifPipeline#addOnAfterRenderGroupListener(OnAfterRenderGroupListener)} */ +public interface OnAfterRenderGroupListener { + /** + * Called at the end of the pipeline after a group has been handed off to the view layer. + * + * @param groupEntry the entry for the group itself. + * @param controller the object to which data can be pushed. + */ + void onAfterRenderGroup( + @NonNull GroupEntry groupEntry, + @NonNull NotifGroupController controller); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java new file mode 100644 index 000000000000..b5a0f7ae169d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java @@ -0,0 +1,39 @@ +/* + * 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.listbuilder; + +import androidx.annotation.NonNull; + +import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; + +import java.util.List; + +/** See {@link NotifPipeline#addOnAfterRenderListListener(OnAfterRenderListListener)} */ +public interface OnAfterRenderListListener { + /** + * Called at the end of the pipeline after the notif list has been handed off to the view layer. + * + * @param entries The current list of top-level entries. Note that this is a live view into the + * current list and will change whenever the pipeline is rerun. + * @param controller An object for setting state on the shade. + */ + void onAfterRenderList( + @NonNull List<ListEntry> entries, + @NonNull NotifStackController controller); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index b4389b95c9dc..7302de57e955 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -109,6 +109,14 @@ class NotifCollectionLogger @Inject constructor( }) } + fun logNonExistentNotifDismissed(key: String) { + buffer.log(TAG, INFO, { + str1 = key + }, { + "DISMISSED Non Existent $str1" + }) + } + fun logChildDismissed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { str1 = entry.key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index b9aa26f75c9b..86d263af88ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -91,7 +92,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Coordin @Override public void collapseGroups() { - for (NotificationEntry entry : mExpandedGroups) { + for (NotificationEntry entry : new ArrayList<>(mExpandedGroups)) { setGroupExpanded(entry, false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index 8182e73ab86b..f59e4ab2007b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -64,12 +64,13 @@ class NodeSpecBuilder( root.children.add(buildNotifNode(root, entry)) } - return root + return@traceSection root } private fun buildNotifNode(parent: NodeSpec, entry: ListEntry): NodeSpec = when (entry) { - is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireView(entry)) - is GroupEntry -> NodeSpecImpl(parent, viewBarn.requireView(checkNotNull(entry.summary))) + is NotificationEntry -> NodeSpecImpl(parent, viewBarn.requireNodeController(entry)) + is GroupEntry -> + NodeSpecImpl(parent, viewBarn.requireNodeController(checkNotNull(entry.summary))) .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } } else -> throw RuntimeException("Unexpected entry: $entry") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt new file mode 100644 index 000000000000..e2edc01f0d7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +/** A view controller for a notification group row */ +interface NotifGroupController { + /** Set the number of children that this group would have if not for the 8-child max */ + fun setUntruncatedChildCount(untruncatedChildCount: Int) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt new file mode 100644 index 000000000000..c10e4018b5db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import android.util.Pair + +/** A view controller for a notification row */ +interface NotifRowController { + /** + * This tells the row what the 'default expanded' state should be. Once a user expands or + * contracts a row, that will set the user expanded state, which takes precedence, but + * collapsing the shade and re-opening it will clear the user expanded state. This allows for + * nice auto expansion of the next notification as users dismiss the top notification. + */ + fun setSystemExpanded(systemExpanded: Boolean) + + /** + * Sets the timestamp that the notification was last audibly alerted, which the row uses to + * show a bell icon in the header which indicates to the user which notification made a noise. + */ + fun setLastAudiblyAlertedMs(lastAudiblyAlertedMs: Long) + + /** + * Sets both whether to show a feedback indicator and which resources to use for the drawable + * and content description. + */ + fun showFeedbackIcon(showFeedbackIndicator: Boolean, feedbackResources: Pair<Int, Int>?) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt new file mode 100644 index 000000000000..b6278d1d5f01 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +/** An interface by which the pipeline can make updates to the notification root view. */ +interface NotifStackController { + /** Provides stats about the list of notifications attached to the shade */ + fun setNotifStats(stats: NotifStats) +} + +/** Data provided to the NotificationRootController whenever the pipeline runs */ +data class NotifStats( + val numActiveNotifs: Int, + val hasNonClearableAlertingNotifs: Boolean, + val hasClearableAlertingNotifs: Boolean, + val hasNonClearableSilentNotifs: Boolean, + val hasClearableSilentNotifs: Boolean +) { + companion object { + @JvmStatic + val empty = NotifStats(0, false, false, false, false) + } +} + +/** + * An implementation of NotifStackController which provides default, no-op implementations of each + * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding + * methods, rather than forcing us to add no-op implementations in their implementation every time + * a method is added. + */ +open class DefaultNotifStackController : NotifStackController { + override fun setNotifStats(stats: NotifStats) {} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt index c79f59b5c625..fd91d5a2c082 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.render import android.view.textclassifier.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject /** @@ -26,30 +27,39 @@ import javax.inject.Inject */ @SysUISingleton class NotifViewBarn @Inject constructor() { - private val rowMap = mutableMapOf<String, NodeController>() + private val rowMap = mutableMapOf<String, NotifViewController>() - fun requireView(forEntry: ListEntry): NodeController { + fun requireNodeController(entry: ListEntry): NodeController { if (DEBUG) { - Log.d(TAG, "requireView: $forEntry.key") + Log.d(TAG, "requireNodeController: ${entry.key}") } - val li = rowMap[forEntry.key] - if (li == null) { - throw IllegalStateException("No view has been registered for entry: $forEntry") + return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}") + } + + fun requireGroupController(entry: NotificationEntry): NotifGroupController { + if (DEBUG) { + Log.d(TAG, "requireGroupController: ${entry.key}") } + return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}") + } - return li + fun requireRowController(entry: NotificationEntry): NotifRowController { + if (DEBUG) { + Log.d(TAG, "requireRowController: ${entry.key}") + } + return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}") } - fun registerViewForEntry(entry: ListEntry, controller: NodeController) { + fun registerViewForEntry(entry: ListEntry, controller: NotifViewController) { if (DEBUG) { - Log.d(TAG, "registerViewForEntry: $entry.key") + Log.d(TAG, "registerViewForEntry: ${entry.key}") } rowMap[entry.key] = controller } fun removeViewForEntry(entry: ListEntry) { if (DEBUG) { - Log.d(TAG, "removeViewForEntry: $entry.key") + Log.d(TAG, "removeViewForEntry: ${entry.key}") } rowMap.remove(entry.key) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt new file mode 100644 index 000000000000..11d4e83f1d38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +interface NotifViewController : NotifGroupController, NotifRowController, NodeController
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt new file mode 100644 index 000000000000..1ea574b2f386 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** + * This interface and the interfaces it returns define the main API surface that must be + * implemented by the view implementation. The term "render" is used to indicate a handoff + * to the view system, whether that be to attach views to the hierarchy or to update independent + * view models, data stores, or adapters. + */ +interface NotifViewRenderer { + + /** + * Hand off the list of notifications to the view implementation. This may attach views to the + * hierarchy or simply update an independent datastore, but once called, the implementer myst + * also ensure that future calls to [getStackController], [getGroupController], and + * [getRowController] will provide valid results. + */ + fun onRenderList(notifList: List<ListEntry>) + + /** + * Provides an interface for the pipeline to update the overall shade. + * This will be called at most once for each time [onRenderList] is called. + */ + fun getStackController(): NotifStackController + + /** + * Provides an interface for the pipeline to update individual groups. + * This will be called at most once for each group in the most recent call to [onRenderList]. + */ + fun getGroupController(group: GroupEntry): NotifGroupController + + /** + * Provides an interface for the pipeline to update individual entries. + * This will be called at most once for each entry in the most recent call to [onRenderList]. + * This includes top level entries, group summaries, and group children. + */ + fun getRowController(entry: NotificationEntry): NotifRowController + + /** + * Invoked after the render stage manager has finished dispatching to all of the listeners. + * + * This is an opportunity for the view system to do any cleanup or trigger any finalization + * logic now that all data from the pipeline is known to have been set for this execution. + * + * When this is called, the view system can expect that no more calls will be made to the + * getters on this interface until after the next call to [onRenderList]. Additionally, there + * should be no further calls made on the objects previously returned by those getters. + */ + fun onDispatchComplete() {} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt new file mode 100644 index 000000000000..6e7f41574743 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import com.android.systemui.statusbar.notification.collection.GroupEntry + +/** + * Extension used during the render stage which assumes the summary exists, and throws a more + * helpful error if not. + */ +inline val GroupEntry.requireSummary get() = checkNotNull(summary) { "No Summary: $this" }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt new file mode 100644 index 000000000000..a9c398726138 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.util.traceSection +import javax.inject.Inject + +/** + * The class which is part of the pipeline which guarantees a consistent that coordinators get a + * consistent interface to the view system regardless of the [NotifViewRenderer] implementation + * provided to [setViewRenderer]. + */ +@SysUISingleton +class RenderStageManager @Inject constructor() { + private val onAfterRenderListListeners = mutableListOf<OnAfterRenderListListener>() + private val onAfterRenderGroupListeners = mutableListOf<OnAfterRenderGroupListener>() + private val onAfterRenderEntryListeners = mutableListOf<OnAfterRenderEntryListener>() + private var viewRenderer: NotifViewRenderer? = null + + /** Attach this stage to the rest of the pipeline */ + fun attach(listBuilder: ShadeListBuilder) { + listBuilder.setOnRenderListListener(::onRenderList) + } + + private fun onRenderList(notifList: List<ListEntry>) { + traceSection("RenderStageManager.onRenderList") { + val viewRenderer = viewRenderer ?: return + viewRenderer.onRenderList(notifList) + dispatchOnAfterRenderList(viewRenderer, notifList) + dispatchOnAfterRenderGroups(viewRenderer, notifList) + dispatchOnAfterRenderEntries(viewRenderer, notifList) + viewRenderer.onDispatchComplete() + } + } + + /** Provides this class with the view rendering implementation. */ + fun setViewRenderer(renderer: NotifViewRenderer) { + viewRenderer = renderer + } + + /** Adds a listener that will get a single callback after rendering the list. */ + fun addOnAfterRenderListListener(listener: OnAfterRenderListListener) { + onAfterRenderListListeners.add(listener) + } + + /** Adds a listener that will get a callback for each group rendered. */ + fun addOnAfterRenderGroupListener(listener: OnAfterRenderGroupListener) { + onAfterRenderGroupListeners.add(listener) + } + + /** Adds a listener that will get a callback for each entry rendered. */ + fun addOnAfterRenderEntryListener(listener: OnAfterRenderEntryListener) { + onAfterRenderEntryListeners.add(listener) + } + + private fun dispatchOnAfterRenderList( + viewRenderer: NotifViewRenderer, + entries: List<ListEntry> + ) { + traceSection("RenderStageManager.dispatchOnAfterRenderList") { + val stackController = viewRenderer.getStackController() + onAfterRenderListListeners.forEach { listener -> + listener.onAfterRenderList(entries, stackController) + } + } + } + + private fun dispatchOnAfterRenderGroups( + viewRenderer: NotifViewRenderer, + entries: List<ListEntry> + ) { + traceSection("RenderStageManager.dispatchOnAfterRenderGroups") { + if (onAfterRenderGroupListeners.isEmpty()) { + return + } + entries.asSequence().filterIsInstance<GroupEntry>().forEach { group -> + val controller = viewRenderer.getGroupController(group) + onAfterRenderGroupListeners.forEach { listener -> + listener.onAfterRenderGroup(group, controller) + } + } + } + } + + private fun dispatchOnAfterRenderEntries( + viewRenderer: NotifViewRenderer, + entries: List<ListEntry> + ) { + traceSection("RenderStageManager.dispatchOnAfterRenderEntries") { + if (onAfterRenderEntryListeners.isEmpty()) { + return + } + entries.forEachNotificationEntry { entry -> + val controller = viewRenderer.getRowController(entry) + onAfterRenderEntryListeners.forEach { listener -> + listener.onAfterRenderEntry(entry, controller) + } + } + } + } + + /** + * Performs a forward, depth-first traversal of the list where the group's summary + * immediately precedes the group's children. + */ + private inline fun List<ListEntry>.forEachNotificationEntry( + action: (NotificationEntry) -> Unit + ) { + forEach { entry -> + when (entry) { + is NotificationEntry -> action(entry) + is GroupEntry -> { + action(entry.requireSummary) + entry.children.forEach(action) + } + else -> error("Unhandled entry: $entry") + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index b582a24f5a3e..1a8d720a12c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -20,10 +20,8 @@ import android.content.Context import android.view.View import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry -import com.android.systemui.statusbar.notification.collection.ShadeListBuilder -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.stack.NotificationListContainer -import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.util.traceSection import javax.inject.Inject @@ -34,9 +32,9 @@ import javax.inject.Inject class ShadeViewManager constructor( context: Context, listContainer: NotificationListContainer, + private val stackController: NotifStackController, logger: ShadeViewDifferLogger, - private val viewBarn: NotifViewBarn, - private val notificationIconAreaController: NotificationIconAreaController + private val viewBarn: NotifViewBarn ) { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. @@ -44,39 +42,39 @@ class ShadeViewManager constructor( private val specBuilder = NodeSpecBuilder(viewBarn) private val viewDiffer = ShadeViewDiffer(rootController, logger) - fun attach(listBuilder: ShadeListBuilder) = - listBuilder.setOnRenderListListener(::onNewNotifTree) - - private fun onNewNotifTree(notifList: List<ListEntry>) { - traceSection("ShadeViewManager.onNewNotifTree") { - viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) - updateGroupCounts(notifList) - notificationIconAreaController.updateNotificationIcons(notifList) - } + /** Method for attaching this manager to the pipeline. */ + fun attach(renderStageManager: RenderStageManager) { + renderStageManager.setViewRenderer(viewRenderer) } - private fun updateGroupCounts(notifList: List<ListEntry>) { - traceSection("ShadeViewManager.updateGroupCounts") { - notifList.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry -> - val controller = viewBarn.requireView(checkNotNull(groupEntry.summary)) - val row = controller.view as ExpandableNotificationRow - row.setUntruncatedChildCount(groupEntry.untruncatedChildCount) + private val viewRenderer = object : NotifViewRenderer { + + override fun onRenderList(notifList: List<ListEntry>) { + traceSection("ShadeViewManager.onRenderList") { + viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList)) } } + + override fun getStackController(): NotifStackController = stackController + + override fun getGroupController(group: GroupEntry): NotifGroupController = + viewBarn.requireGroupController(group.requireSummary) + + override fun getRowController(entry: NotificationEntry): NotifRowController = + viewBarn.requireRowController(entry) } } class ShadeViewManagerFactory @Inject constructor( private val context: Context, private val logger: ShadeViewDifferLogger, - private val viewBarn: NotifViewBarn, - private val notificationIconAreaController: NotificationIconAreaController + private val viewBarn: NotifViewBarn ) { - fun create(listContainer: NotificationListContainer) = - ShadeViewManager( - context, - listContainer, - logger, - viewBarn, - notificationIconAreaController) + fun create(listContainer: NotificationListContainer, stackController: NotifStackController) = + ShadeViewManager( + context, + listContainer, + stackController, + logger, + viewBarn) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt index 2f496dd55b2b..a59d4211e68a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt @@ -21,6 +21,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.StatusBar import com.android.wm.shell.bubbles.Bubbles @@ -40,6 +41,7 @@ interface NotificationsController { bubblesOptional: Optional<Bubbles>, presenter: NotificationPresenter, listContainer: NotificationListContainer, + stackController: NotifStackController, notificationActivityStarter: NotificationActivityStarter, bindRowCallback: NotificationRowBinderImpl.BindRowCallback ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 9411de75ba7f..212c342eb8f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.TargetSdkResolver import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy +import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.interruption.HeadsUpController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer @@ -47,7 +48,7 @@ import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy import java.io.FileDescriptor import java.io.PrintWriter -import java.util.* +import java.util.Optional import javax.inject.Inject /** @@ -85,6 +86,7 @@ class NotificationsControllerImpl @Inject constructor( bubblesOptional: Optional<Bubbles>, presenter: NotificationPresenter, listContainer: NotificationListContainer, + stackController: NotifStackController, notificationActivityStarter: NotificationActivityStarter, bindRowCallback: NotificationRowBinderImpl.BindRowCallback ) { @@ -112,7 +114,8 @@ class NotificationsControllerImpl @Inject constructor( newNotifPipeline.get().initialize( notificationListener, notificationRowBinder, - listContainer) + listContainer, + stackController) } if (notifPipelineFlags.isNewPipelineEnabled()) { @@ -152,8 +155,14 @@ class NotificationsControllerImpl @Inject constructor( } override fun resetUserExpandedStates() { - for (entry in entryManager.visibleNotifications) { - entry.resetUserExpansion() + if (notifPipelineFlags.isNewPipelineEnabled()) { + for (entry in notifPipeline.get().allNotifs) { + entry.resetUserExpansion() + } + } else { + for (entry in entryManager.visibleNotifications) { + entry.resetUserExpansion() + } } } @@ -167,9 +176,12 @@ class NotificationsControllerImpl @Inject constructor( } } - override fun getActiveNotificationsCount(): Int { - return entryManager.activeNotificationsCount - } + override fun getActiveNotificationsCount(): Int = + if (notifPipelineFlags.isNewPipelineEnabled()) { + notifPipeline.get().getShadeListCount() + } else { + entryManager.activeNotificationsCount + } companion object { // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt index eadf5ac735fc..1c9af11b7816 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt @@ -22,6 +22,7 @@ import com.android.systemui.statusbar.NotificationListener import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl +import com.android.systemui.statusbar.notification.collection.render.NotifStackController import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.statusbar.phone.StatusBar import com.android.wm.shell.bubbles.Bubbles @@ -42,6 +43,7 @@ class NotificationsControllerStub @Inject constructor( bubblesOptional: Optional<Bubbles>, presenter: NotificationPresenter, listContainer: NotificationListContainer, + stackController: NotifStackController, notificationActivityStarter: NotificationActivityStarter, bindRowCallback: NotificationRowBinderImpl.BindRowCallback ) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9f10322b5fa3..2fbd212cc3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -54,6 +54,7 @@ import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.util.Pair; @@ -92,6 +93,7 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ExpandAnimationParameters; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -131,7 +133,8 @@ import java.util.function.Consumer; * the group summary (which contains 1 or more child notifications). */ public class ExpandableNotificationRow extends ActivatableNotificationView - implements PluginListener<NotificationMenuRowPlugin>, SwipeableView { + implements PluginListener<NotificationMenuRowPlugin>, SwipeableView, + NotificationFadeAware.FadeOptimizedNotification { private static final boolean DEBUG = false; private static final int DEFAULT_DIVIDER_ALPHA = 0x29; @@ -144,6 +147,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mUpdateBackgroundOnUpdate; private boolean mNotificationTranslationFinished = false; private boolean mIsSnoozed; + private boolean mIsFaded; /** * Listener for when {@link ExpandableNotificationRow} is laid out. @@ -812,6 +816,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.setUntruncatedChildCount(childCount); } + /** Called after children have been attached to set the expansion states */ + public void resetChildSystemExpandedStates() { + if (isSummaryWithChildren()) { + mChildrenContainer.updateExpansionStates(); + } + } + /** * Add a child notification to this view. * @@ -2339,6 +2350,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView onExpansionChanged(false /* userAction */, wasExpanded); if (mIsSummaryWithChildren) { mChildrenContainer.updateGroupOverflow(); + resetChildSystemExpandedStates(); } } } @@ -2774,17 +2786,112 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // alphas are reset if (mChildrenContainer != null) { mChildrenContainer.setAlpha(1.0f); - mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); } for (NotificationContentView l : mLayouts) { l.setAlpha(1.0f); - l.setLayerType(LAYER_TYPE_NONE, null); + } + if (FADE_LAYER_OPTIMIZATION_ENABLED) { + setNotificationFaded(false); + } else { + setNotificationFadedOnChildren(false); } } else { setHeadsUpAnimatingAway(false); } } + /** Gets the last value set with {@link #setNotificationFaded(boolean)} */ + @Override + public boolean isNotificationFaded() { + return mIsFaded; + } + + /** + * This class needs to delegate the faded state set on it by + * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children. + * Having each notification use layerType of HARDWARE anytime it fades in/out can result in + * extremely large layers (in the case of groups, it can even exceed the device height). + * Because these large renders can cause serious jank when rendering, we instead have + * notifications return false from {@link #hasOverlappingRendering()} and delegate the + * layerType to child views which really need it in order to render correctly, such as icon + * views or the conversation face pile. + * + * Another compounding factor for notifications is that we change clipping on each frame of the + * animation, so the hardware layer isn't able to do any caching at the top level, but the + * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we + * never invalidate them. + */ + @Override + public void setNotificationFaded(boolean faded) { + mIsFaded = faded; + if (childrenRequireOverlappingRendering()) { + // == Simple Scenario == + // If a child (like remote input) needs this to have overlapping rendering, then set + // the layerType of this view and reset the children to render as if the notification is + // not fading. + NotificationFadeAware.setLayerTypeForFaded(this, faded); + setNotificationFadedOnChildren(false); + } else { + // == Delegating Scenario == + // This is the new normal for alpha: Explicitly reset this view's layer type to NONE, + // and require that all children use their own hardware layer if they have bad + // overlapping rendering. + NotificationFadeAware.setLayerTypeForFaded(this, false); + setNotificationFadedOnChildren(faded); + } + } + + /** Private helper for iterating over the layouts and children containers to set faded state */ + private void setNotificationFadedOnChildren(boolean faded) { + delegateNotificationFaded(mChildrenContainer, faded); + for (NotificationContentView layout : mLayouts) { + delegateNotificationFaded(layout, faded); + } + } + + private static void delegateNotificationFaded(@Nullable View view, boolean faded) { + if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) { + ((NotificationFadeAware) view).setNotificationFaded(faded); + } else { + NotificationFadeAware.setLayerTypeForFaded(view, faded); + } + } + + /** + * Only declare overlapping rendering if independent children of the view require it. + */ + @Override + public boolean hasOverlappingRendering() { + return super.hasOverlappingRendering() && childrenRequireOverlappingRendering(); + } + + /** + * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the + * row should require overlapping rendering to ensure that the overlapped view doesn't bleed + * through when alpha fading. + * + * Note that this currently works for top-level notifications which squish their height down + * while collapsing the shade, but does not work for children inside groups, because the + * accordion affect does not apply to those views, so super.hasOverlappingRendering() will + * always return false to avoid the clipping caused when the view's measured height is less than + * the 'actual height'. + */ + private boolean childrenRequireOverlappingRendering() { + if (!FADE_LAYER_OPTIMIZATION_ENABLED) { + return true; + } + // The colorized background is another layer with which all other elements overlap + if (getEntry().getSbn().getNotification().isColorized()) { + return true; + } + // Check if the showing layout has a need for overlapping rendering. + // NOTE: We could check both public and private layouts here, but becuause these states + // don't animate well, there are bigger visual artifacts if we start changing the shown + // layout during shade expansion. + NotificationContentView showingLayout = getShowingLayout(); + return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); + } + @Override public int getExtraBottomPadding() { if (mIsSummaryWithChildren && isGroupExpanded()) { @@ -3325,46 +3432,47 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); // Skip super call; dump viewState ourselves pw.println("Notification: " + mEntry.getKey()); - DumpUtilsKt.withIndenting(pw, ipw -> { - ipw.print("visibility: " + getVisibility()); - ipw.print(", alpha: " + getAlpha()); - ipw.print(", translation: " + getTranslation()); - ipw.print(", removed: " + isRemoved()); - ipw.print(", expandAnimationRunning: " + mExpandAnimationRunning); + DumpUtilsKt.withIncreasedIndent(pw, () -> { + pw.print("visibility: " + getVisibility()); + pw.print(", alpha: " + getAlpha()); + pw.print(", translation: " + getTranslation()); + pw.print(", removed: " + isRemoved()); + pw.print(", expandAnimationRunning: " + mExpandAnimationRunning); NotificationContentView showingLayout = getShowingLayout(); - ipw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); - ipw.println(); - showingLayout.dump(fd, ipw, args); + pw.print(", privateShowing: " + (showingLayout == mPrivateLayout)); + pw.println(); + showingLayout.dump(fd, pw, args); if (getViewState() != null) { - getViewState().dump(fd, ipw, args); - ipw.println(); + getViewState().dump(fd, pw, args); + pw.println(); } else { - ipw.println("no viewState!!!"); + pw.println("no viewState!!!"); } if (mIsSummaryWithChildren) { - ipw.println(); - ipw.print("ChildrenContainer"); - ipw.print(" visibility: " + mChildrenContainer.getVisibility()); - ipw.print(", alpha: " + mChildrenContainer.getAlpha()); - ipw.print(", translationY: " + mChildrenContainer.getTranslationY()); - ipw.println(); + pw.println(); + pw.print("ChildrenContainer"); + pw.print(" visibility: " + mChildrenContainer.getVisibility()); + pw.print(", alpha: " + mChildrenContainer.getAlpha()); + pw.print(", translationY: " + mChildrenContainer.getTranslationY()); + pw.println(); List<ExpandableNotificationRow> notificationChildren = getAttachedChildren(); - ipw.println("Children: " + notificationChildren.size()); - ipw.print("{"); - ipw.increaseIndent(); + pw.println("Children: " + notificationChildren.size()); + pw.print("{"); + pw.increaseIndent(); for (ExpandableNotificationRow child : notificationChildren) { - ipw.println(); - child.dump(fd, ipw, args); + pw.println(); + child.dump(fd, pw, args); } - ipw.decreaseIndent(); - ipw.println("}"); + pw.decreaseIndent(); + pw.println("}"); } else if (mPrivateLayout != null) { - mPrivateLayout.dumpSmartReplies(ipw); + mPrivateLayout.dumpSmartReplies(pw); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 0b29ae57510c..3d35d0e3a195 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -20,6 +20,8 @@ import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import android.util.Log; +import android.util.Pair; import android.view.View; import android.view.ViewGroup; @@ -36,6 +38,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.collection.render.NodeController; +import com.android.systemui.statusbar.notification.collection.render.NotifViewController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.dagger.AppName; @@ -58,7 +61,8 @@ import javax.inject.Named; * Controller for {@link ExpandableNotificationRow}. */ @NotificationRowScope -public class ExpandableNotificationRowController implements NodeController { +public class ExpandableNotificationRowController implements NotifViewController { + private static final String TAG = "NotifRowController"; private final ExpandableNotificationRow mView; private final NotificationListContainer mListContainer; private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory; @@ -241,7 +245,7 @@ public class ExpandableNotificationRowController implements NodeController { public void addChildAt(NodeController child, int index) { ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView(); - mView.addChildNotification((ExpandableNotificationRow) child.getView()); + mView.addChildNotification((ExpandableNotificationRow) child.getView(), index); mListContainer.notifyGroupChildAdded(childView); } @@ -267,4 +271,28 @@ public class ExpandableNotificationRowController implements NodeController { final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren(); return mChildren != null ? mChildren.size() : 0; } + + @Override + public void setUntruncatedChildCount(int childCount) { + if (mView.isSummaryWithChildren()) { + mView.setUntruncatedChildCount(childCount); + } else { + Log.w(TAG, "Called setUntruncatedChildCount(" + childCount + ") on a leaf row"); + } + } + + @Override + public void setSystemExpanded(boolean systemExpanded) { + mView.setSystemExpanded(systemExpanded); + } + + @Override + public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) { + mView.setLastAudiblyAlertedMs(lastAudiblyAlertedMs); + } + + @Override + public void showFeedbackIcon(boolean show, Pair<Integer, Integer> feedbackResources) { + mView.showFeedbackIcon(show, feedbackResources); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index fa2c1ee772cb..4b3d6f76ba9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -22,6 +22,7 @@ import android.content.res.Configuration; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; +import android.util.IndentingPrintWriter; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -743,15 +744,16 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); pw.println(getClass().getSimpleName()); - DumpUtilsKt.withIndenting(pw, ipw -> { + DumpUtilsKt.withIncreasedIndent(pw, () -> { ExpandableViewState viewState = getViewState(); if (viewState == null) { - ipw.println("no viewState!!!"); + pw.println("no viewState!!!"); } else { - viewState.dump(fd, ipw, args); - ipw.println(); + viewState.dump(fd, pw, args); + pw.println(); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index b27a40a828f3..e8e6e310322d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.util.AttributeSet; +import android.util.IndentingPrintWriter; import android.view.View; import com.android.systemui.R; @@ -49,14 +50,15 @@ public class FooterView extends StackScrollerDecorView { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); super.dump(fd, pw, args); - DumpUtilsKt.withIndenting(pw, ipw -> { - ipw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); - ipw.println("manageButton showHistory: " + mShowHistory); - ipw.println("manageButton visibility: " + DumpUtilsKt.withIncreasedIndent(pw, () -> { + pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); + pw.println("manageButton showHistory: " + mShowHistory); + pw.println("manageButton visibility: " + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); - ipw.println("dismissButton visibility: " + pw.println("dismissButton visibility: " + DumpUtilsKt.visibilityString(mDismissButton.getVisibility())); }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java index caba3ac7e17b..c66140822d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java @@ -28,6 +28,7 @@ import android.widget.TextView; import com.android.internal.widget.ConversationLayout; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationFadeAware; /** * A hybrid view which may contain information about one ore more conversations. @@ -138,4 +139,14 @@ public class HybridConversationNotificationView extends HybridNotificationView { lp.height = size; view.setLayoutParams(lp); } + + /** + * Apply the faded state as a layer type change to the face pile view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java index bc2adac31d07..c0d85a6a16ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java @@ -28,13 +28,14 @@ import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.ViewTransformationHelper; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.TransformState; /** * A hybrid view which may contain information about one ore more notifications. */ public class HybridNotificationView extends AlphaOptimizedLinearLayout - implements TransformableView { + implements TransformableView, NotificationFadeAware { protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper(); protected TextView mTitleView; @@ -148,4 +149,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout setVisibility(visible ? View.VISIBLE : View.INVISIBLE); mTransformationHelper.setVisible(visible); } + + @Override + public void setNotificationFaded(boolean faded) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 727f0e55f58f..438992e22577 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -46,6 +46,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -73,7 +74,7 @@ import java.util.List; * expanded and heads up layout. This class is responsible for clipping the content and and * switching between the expanded, contracted and the heads up view depending on its clipped size. */ -public class NotificationContentView extends FrameLayout { +public class NotificationContentView extends FrameLayout implements NotificationFadeAware { private static final String TAG = "NotificationContentView"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -2045,6 +2046,41 @@ public class NotificationContentView extends FrameLayout { return Notification.COLOR_INVALID; } + /** + * Delegate the faded state to the notification content views which actually + * need to have overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + if (mContractedWrapper != null) { + mContractedWrapper.setNotificationFaded(faded); + } + if (mHeadsUpWrapper != null) { + mHeadsUpWrapper.setNotificationFaded(faded); + } + if (mExpandedWrapper != null) { + mExpandedWrapper.setNotificationFaded(faded); + } + if (mSingleLineView != null) { + mSingleLineView.setNotificationFaded(faded); + } + } + + /** + * @return true if a visible view has a remote input active, as this requires that the entire + * row report that it has overlapping rendering. + */ + public boolean requireRowToHaveOverlappingRendering() { + // This inexpensive check is done on both states to avoid state invalidating the result. + if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) { + return true; + } + if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) { + return true; + } + return false; + } + public void setRemoteInputViewSubcomponentFactory(RemoteInputViewSubcomponent.Factory factory) { mRemoteInputSubcomponentFactory = factory; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt index 12e94cbc1ab9..bb43b95357db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt @@ -21,6 +21,7 @@ import android.view.View import com.android.internal.widget.CachingIconView import com.android.internal.widget.CallLayout import com.android.systemui.R +import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -37,6 +38,7 @@ class NotificationCallTemplateViewWrapper constructor( NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height) private val callLayout: CallLayout = view as CallLayout + private lateinit var conversationIconContainer: View private lateinit var conversationIconView: CachingIconView private lateinit var conversationBadgeBg: View private lateinit var expandBtn: View @@ -45,6 +47,8 @@ class NotificationCallTemplateViewWrapper constructor( private fun resolveViews() { with(callLayout) { + conversationIconContainer = + requireViewById(com.android.internal.R.id.conversation_icon_container) conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon) conversationBadgeBg = requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) @@ -82,4 +86,14 @@ class NotificationCallTemplateViewWrapper constructor( } override fun getMinLayoutHeight(): Int = minHeightWithActions + + /** + * Apply the faded state as a layer type change to the face pile view which needs to have + * overlapping contents render precisely. + */ + override fun setNotificationFaded(faded: Boolean) { + // Do not call super + NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded) + NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 3ef5b665d778..e136055b80b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -23,6 +23,7 @@ import com.android.internal.widget.CachingIconView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingLinearLayout import com.android.systemui.R +import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform @@ -42,6 +43,7 @@ class NotificationConversationTemplateViewWrapper constructor( ) private val conversationLayout: ConversationLayout = view as ConversationLayout + private lateinit var conversationIconContainer: View private lateinit var conversationIconView: CachingIconView private lateinit var conversationBadgeBg: View private lateinit var expandBtn: View @@ -59,6 +61,8 @@ class NotificationConversationTemplateViewWrapper constructor( messagingLinearLayout = conversationLayout.messagingLinearLayout imageMessageContainer = conversationLayout.imageMessageContainer with(conversationLayout) { + conversationIconContainer = + requireViewById(com.android.internal.R.id.conversation_icon_container) conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon) conversationBadgeBg = requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) @@ -136,4 +140,10 @@ class NotificationConversationTemplateViewWrapper constructor( minHeightWithActions else super.getMinLayoutHeight() + + override fun setNotificationFaded(faded: Boolean) { + // Do not call super + NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded) + NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index 4c9c2f95b35c..fdff12d22354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -22,6 +22,7 @@ import android.view.View; import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -86,4 +87,14 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { return true; } + + /** + * Apply the faded state as a layer type change to the custom view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mView, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java index 8c6fa023d92b..31595397b9b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -67,4 +68,14 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate } super.onContentUpdated(row); } + + /** + * Apply the faded state as a layer type change to the custom view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mWrappedView, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 76301917b458..6c3e0d2f798b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -42,6 +42,7 @@ import com.android.internal.widget.CachingIconView; import com.android.settingslib.Utils; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.TransformState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -395,4 +396,13 @@ public abstract class NotificationViewWrapper implements TransformableView { */ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { } + + /** + * Apply the faded state as a layer type change to the views which need to have overlapping + * contents render precisely. + */ + public void setNotificationFaded(boolean faded) { + NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded); + NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index a4727106c5fa..18751242ec6f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -38,6 +38,7 @@ import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -51,7 +52,8 @@ import java.util.List; /** * A container containing child notifications */ -public class NotificationChildrenContainer extends ViewGroup { +public class NotificationChildrenContainer extends ViewGroup + implements NotificationFadeAware { @VisibleForTesting static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; @@ -111,6 +113,7 @@ public class NotificationChildrenContainer extends ViewGroup { private int mCurrentHeaderTranslation = 0; private float mHeaderVisibleAmount = 1.0f; private int mUntruncatedChildCount; + private boolean mContainingNotificationIsFaded = false; public NotificationChildrenContainer(Context context) { this(context, null); @@ -277,6 +280,7 @@ public class NotificationChildrenContainer extends ViewGroup { mDividers.add(newIndex, divider); row.setContentTransformationAmount(0, false /* isLastChild */); + row.setNotificationFaded(mContainingNotificationIsFaded); // It doesn't make sense to keep old animations around, lets cancel them! ExpandableViewState viewState = row.getViewState(); if (viewState != null) { @@ -301,6 +305,7 @@ public class NotificationChildrenContainer extends ViewGroup { }); row.setSystemChildExpanded(false); + row.setNotificationFaded(false); row.setUserLocked(false); if (!row.isRemoved()) { mGroupingUtil.restoreChildNotification(row); @@ -473,7 +478,8 @@ public class NotificationChildrenContainer extends ViewGroup { return result; } - private void updateExpansionStates() { + /** To be called any time the rows have been updated */ + public void updateExpansionStates() { if (mChildrenExpanded || mUserLocked) { // we don't modify it the group is expanded or if we are expanding it return; @@ -1308,4 +1314,18 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } } + + @Override + public void setNotificationFaded(boolean faded) { + mContainingNotificationIsFaded = faded; + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.setNotificationFaded(faded); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.setNotificationFaded(faded); + } + for (ExpandableNotificationRow child : mAttachedChildren) { + child.setNotificationFaded(faded); + } + } } 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 a97a54a50da5..44e37187c350 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 @@ -47,6 +47,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.util.AttributeSet; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.util.Pair; @@ -678,7 +679,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // TODO: move this logic to controller, which will invoke updateFooterView directly boolean showDismissView = mClearAllEnabled && mController.hasActiveClearableNotifications(ROWS_ALL); - boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) + boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0) && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() @@ -1173,20 +1174,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - /** - * Returns best effort count of visible notifications. - */ - public int getVisibleNotificationCount() { - int count = 0; - for (int i = 0; i < getChildCount(); i++) { - final View child = getChildAt(i); - if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) { - count++; - } - } - return count; - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private boolean isCurrentlyAnimating() { return mStateAnimator.isRunning(); @@ -1458,7 +1445,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) private float getAppearEndPosition() { int appearPosition = 0; - int visibleNotifCount = getVisibleNotificationCount(); + int visibleNotifCount = mController.getVisibleNotificationCount(); if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { if (isHeadsUpTransition() || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { @@ -4642,7 +4629,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } private void ensureRemovedFromTransientContainer(View v) { - if (v.getParent() == this && v instanceof SectionHeaderView) { + if (v.getParent() == this && v instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) v; ViewGroup transientContainer = expandableView.getTransientContainer(); // If the child is animating away, it will still have a parent, so @@ -4915,7 +4902,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); StringBuilder sb = new StringBuilder("[") .append(this.getClass().getSimpleName()).append(":") .append(" pulsing=").append(mPulsing ? "T" : "f") @@ -4929,15 +4917,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable .append(" hideAmount=").append(mAmbientState.getHideAmount()) .append("]"); pw.println(sb.toString()); - DumpUtilsKt.withIndenting(pw, ipw -> { + DumpUtilsKt.withIncreasedIndent(pw, () -> { int childCount = getChildCount(); - ipw.println("Number of children: " + childCount); - ipw.println(); + pw.println("Number of children: " + childCount); + pw.println(); for (int i = 0; i < childCount; i++) { ExpandableView child = (ExpandableView) getChildAt(i); - child.dump(fd, ipw, args); - ipw.println(); + child.dump(fd, pw, args); + pw.println(); } int transientViewCount = getTransientViewCount(); pw.println("Transient Views: " + transientViewCount); @@ -6108,6 +6096,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mExpandHelperCallback; } + float getAppearFraction() { + return mLastSentAppear; + } + + float getExpandedHeight() { + return mLastSentExpandedHeight; + } + /** Enum for selecting some or all notification rows (does not included non-notif views). */ @Retention(SOURCE) @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE}) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index f7a3e3c1f3ba..41a80c7aceeb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -48,6 +48,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -98,6 +99,8 @@ import com.android.systemui.statusbar.notification.collection.legacy.VisualStabi import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; +import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.SilentHeader; @@ -192,6 +195,8 @@ public class NotificationStackScrollLayoutController { private final NotificationListContainerImpl mNotificationListContainer = new NotificationListContainerImpl(); + private final NotifStackController mNotifStackController = + new NotifStackControllerImpl(); @Nullable private NotificationActivityStarter mNotificationActivityStarter; @@ -294,6 +299,8 @@ public class NotificationStackScrollLayoutController { } }; + private NotifStats mNotifStats = NotifStats.getEmpty(); + private void updateResources() { mNotificationDragDownMovement = mResources.getDimensionPixelSize( R.dimen.lockscreen_shade_notification_movement); @@ -866,6 +873,14 @@ public class NotificationStackScrollLayoutController { mView.setHeadsUpAppearanceController(controller); } + public float getAppearFraction() { + return mView.getAppearFraction(); + } + + public float getExpandedHeight() { + return mView.getExpandedHeight(); + } + public void requestLayout() { mView.requestLayout(); } @@ -988,7 +1003,7 @@ public class NotificationStackScrollLayoutController { } public int getVisibleNotificationCount() { - return mView.getVisibleNotificationCount(); + return mNotifStats.getNumActiveNotifs(); } public int getIntrinsicContentHeight() { @@ -1183,7 +1198,7 @@ public class NotificationStackScrollLayoutController { public void updateShowEmptyShadeView() { mShowEmptyShadeView = mBarState != KEYGUARD && (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade()) - && mView.getVisibleNotificationCount() == 0; + && getVisibleNotificationCount() == 0; mView.updateEmptyShadeView( mShowEmptyShadeView, @@ -1246,29 +1261,22 @@ public class NotificationStackScrollLayoutController { } public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { - if (mDynamicPrivacyController.isInLockedDownShade()) { - return false; + boolean hasAlertingMatchingClearable = isClearable + ? mNotifStats.getHasClearableAlertingNotifs() + : mNotifStats.getHasNonClearableAlertingNotifs(); + boolean hasSilentMatchingClearable = isClearable + ? mNotifStats.getHasClearableSilentNotifs() + : mNotifStats.getHasNonClearableSilentNotifs(); + switch (selection) { + case ROWS_GENTLE: + return hasSilentMatchingClearable; + case ROWS_HIGH_PRIORITY: + return hasAlertingMatchingClearable; + case ROWS_ALL: + return hasSilentMatchingClearable || hasAlertingMatchingClearable; + default: + throw new IllegalStateException("Bad selection: " + selection); } - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (!(child instanceof ExpandableNotificationRow)) { - continue; - } - final ExpandableNotificationRow row = (ExpandableNotificationRow) child; - final boolean matchClearable = - isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed(); - final boolean inSection = - NotificationStackScrollLayout.matchesSelection(row, selection); - if (matchClearable && inSection) { - if (mLegacyGroupManager == null - || !mLegacyGroupManager.isSummaryOfSuppressedGroup( - row.getEntry().getSbn())) { - return true; - } - } - } - return false; } /** @@ -1383,6 +1391,10 @@ public class NotificationStackScrollLayoutController { return mNotificationListContainer; } + public NotifStackController getNotifStackController() { + return mNotifStackController; + } + public void resetCheckSnoozeLeavebehind() { mView.resetCheckSnoozeLeavebehind(); } @@ -1394,17 +1406,6 @@ public class NotificationStackScrollLayoutController { mVisibilityProvider.obtain(entry, true)); } - /** - * @return if the shade has currently any active notifications. - */ - public boolean hasActiveNotifications() { - if (mNotifPipelineFlags.isNewPipelineEnabled()) { - return !mNotifPipeline.getShadeList().isEmpty(); - } else { - return mNotificationEntryManager.hasActiveNotifications(); - } - } - public void closeControlsIfOutsideTouch(MotionEvent ev) { NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); @@ -1876,4 +1877,13 @@ public class NotificationStackScrollLayoutController { } } } + + private class NotifStackControllerImpl implements NotifStackController { + @Override + public void setNotifStats(@NonNull NotifStats notifStats) { + mNotifStats = notifStats; + updateFooter(); + updateShowEmptyShadeView(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java index 6d82a45313d1..83bea84c8d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java @@ -29,6 +29,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.policy.HeadsUpUtil; @@ -206,14 +207,26 @@ public class ViewState implements Dumpable { } else if (view.getAlpha() != this.alpha) { // apply layer type boolean becomesFullyVisible = this.alpha == 1.0f; - boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible - && view.hasOverlappingRendering(); - int layerType = view.getLayerType(); - int newLayerType = newLayerTypeIsHardware - ? View.LAYER_TYPE_HARDWARE - : View.LAYER_TYPE_NONE; - if (layerType != newLayerType) { - view.setLayerType(newLayerType, null); + boolean becomesFaded = !becomesInvisible && !becomesFullyVisible; + if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED + && view instanceof FadeOptimizedNotification) { + // NOTE: A view that's going to utilize this interface to avoid having a hardware + // layer will have to return false from hasOverlappingRendering(), so we + // intentionally do not check that value in this if, even though we do in the else. + FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view; + boolean isFaded = fadeOptimizedView.isNotificationFaded(); + if (isFaded != becomesFaded) { + fadeOptimizedView.setNotificationFaded(becomesFaded); + } + } else { + boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering(); + int layerType = view.getLayerType(); + int newLayerType = newLayerTypeIsHardware + ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_NONE; + if (layerType != newLayerType) { + view.setLayerType(newLayerType, null); + } } // apply alpha diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 67f51cb9ab43..aa3b3e12b8f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; +import android.hardware.fingerprint.FingerprintManager; import android.metrics.LogMaker; import android.os.Handler; import android.os.PowerManager; @@ -46,9 +47,11 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.FileDescriptor; @@ -71,6 +74,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000; private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock"; private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); + private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3; @IntDef(prefix = { "MODE_" }, value = { MODE_NONE, @@ -167,6 +171,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; + private final StatusBarStateController mStatusBarStateController; + + private long mLastFpFailureUptimeMillis; + private int mNumConsecutiveFpFailures; private static final class PendingAuthenticated { public final int userId; @@ -209,7 +217,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp BIOMETRIC_IRIS_FAILURE(403), @UiEvent(doc = "A biometric event of type iris errored.") - BIOMETRIC_IRIS_ERROR(404); + BIOMETRIC_IRIS_ERROR(404), + + @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.") + BIOMETRIC_BOUNCER_SHOWN(916); private final int mId; @@ -257,7 +268,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp NotificationMediaManager notificationMediaManager, WakefulnessLifecycle wakefulnessLifecycle, ScreenLifecycle screenLifecycle, - AuthController authController) { + AuthController authController, + StatusBarStateController statusBarStateController) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -279,6 +291,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardBypassController.setUnlockController(this); mMetricsLogger = metricsLogger; mAuthController = authController; + mStatusBarStateController = statusBarStateController; dumpManager.registerDumpable(getClass().getName(), this); } @@ -620,6 +633,22 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType))); Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + long currUptimeMillis = SystemClock.uptimeMillis(); + if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds + mNumConsecutiveFpFailures += 1; + } else { + mNumConsecutiveFpFailures = 1; + } + mLastFpFailureUptimeMillis = currUptimeMillis; + + if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT) + && mUpdateMonitor.isUdfpsSupported() + && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) { + mKeyguardViewController.showBouncer(true); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + mNumConsecutiveFpFailures = 0; + } cleanup(); } @@ -631,6 +660,16 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId)); Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(UI_EVENT_LOGGER::log); + + // if we're on the shade and we're locked out, immediately show the bouncer + if (biometricSourceType == BiometricSourceType.FINGERPRINT + && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT + || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) + && mUpdateMonitor.isUdfpsSupported() + && (mStatusBarStateController.getState() == StatusBarState.SHADE + || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) { + mKeyguardViewController.showBouncer(true); + } cleanup(); } @@ -664,6 +703,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mBiometricModeListener.onResetMode(); mBiometricModeListener.notifyBiometricAuthModeChanged(); } + mNumConsecutiveFpFailures = 0; + mLastFpFailureUptimeMillis = 0; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 927b4c8cc919..8a7cf360a254 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -24,6 +24,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -35,23 +36,29 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.util.ViewController; import java.util.function.BiConsumer; import java.util.function.Consumer; +import javax.inject.Inject; + /** * Controls the appearance of heads up notifications in the icon area and the header itself. */ -public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, - DarkIconDispatcher.DarkReceiver, NotificationWakeUpCoordinator.WakeUpListener { +@StatusBarFragmentScope +public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView> + implements OnHeadsUpChangedListener, + DarkIconDispatcher.DarkReceiver, + NotificationWakeUpCoordinator.WakeUpListener { public static final int CONTENT_FADE_DURATION = 110; public static final int CONTENT_FADE_DELAY = 100; private final NotificationIconAreaController mNotificationIconAreaController; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationStackScrollLayoutController mStackScrollerController; - private final HeadsUpStatusBarView mHeadsUpStatusBarView; private final View mCenteredIconView; private final View mClockView; private final View mOperatorNameView; @@ -67,8 +74,6 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, @VisibleForTesting float mExpandedHeight; @VisibleForTesting - boolean mIsExpanded; - @VisibleForTesting float mAppearFraction; private ExpandableNotificationRow mTrackedChild; private boolean mShown; @@ -83,7 +88,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, Point mPoint; private KeyguardStateController mKeyguardStateController; - + @Inject public HeadsUpAppearanceController( NotificationIconAreaController notificationIconAreaController, HeadsUpManagerPhone headsUpManager, @@ -92,11 +97,15 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, KeyguardBypassController keyguardBypassController, KeyguardStateController keyguardStateController, NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue, - NotificationPanelViewController notificationPanelViewController, View statusBarView) { + NotificationPanelViewController notificationPanelViewController, + @RootView PhoneStatusBarView statusBarView) { this(notificationIconAreaController, headsUpManager, statusBarStateController, keyguardBypassController, wakeUpCoordinator, keyguardStateController, commandQueue, notificationStackScrollLayoutController, notificationPanelViewController, + // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these + // four views, and then we can delete this constructor and just use the one below + // (which also removes the undesirable @VisibleForTesting). statusBarView.findViewById(R.id.heads_up_status_bar_view), statusBarView.findViewById(R.id.clock), statusBarView.findViewById(R.id.operator_name_frame), @@ -118,25 +127,27 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, View clockView, View operatorNameView, View centeredIconView) { + super(headsUpStatusBarView); mNotificationIconAreaController = notificationIconAreaController; mHeadsUpManager = headsUpManager; - mHeadsUpManager.addListener(this); - mHeadsUpStatusBarView = headsUpStatusBarView; mCenteredIconView = centeredIconView; - headsUpStatusBarView.setOnDrawingRectChangedListener( - () -> updateIsolatedIconLocation(true /* requireUpdate */)); + + // We may be mid-HUN-expansion when this controller is re-created (for example, if the user + // has started pulling down the notification shade from the HUN and then the font size + // changes). We need to re-fetch these values since they're used to correctly display the + // HUN during this shade expansion. + mTrackedChild = notificationPanelViewController.getTrackedHeadsUpNotification(); + mAppearFraction = stackScrollerController.getAppearFraction(); + mExpandedHeight = stackScrollerController.getExpandedHeight(); + mStackScrollerController = stackScrollerController; mNotificationPanelViewController = notificationPanelViewController; - notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp); - notificationPanelViewController.setHeadsUpAppearanceController(this); - mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); mStackScrollerController.setHeadsUpAppearanceController(this); mClockView = clockView; mOperatorNameView = operatorNameView; mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); - mDarkIconDispatcher.addDarkReceiver(this); - mHeadsUpStatusBarView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { @@ -146,21 +157,32 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, // trigger scroller to notify the latest panel translation mStackScrollerController.requestLayout(); } - mHeadsUpStatusBarView.removeOnLayoutChangeListener(this); + mView.removeOnLayoutChangeListener(this); } }); mBypassController = bypassController; mStatusBarStateController = stateController; mWakeUpCoordinator = wakeUpCoordinator; - wakeUpCoordinator.addListener(this); mCommandQueue = commandQueue; mKeyguardStateController = keyguardStateController; } + @Override + protected void onViewAttached() { + mHeadsUpManager.addListener(this); + mView.setOnDrawingRectChangedListener( + () -> updateIsolatedIconLocation(true /* requireUpdate */)); + mWakeUpCoordinator.addListener(this); + mNotificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp); + mNotificationPanelViewController.setHeadsUpAppearanceController(this); + mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); + mDarkIconDispatcher.addDarkReceiver(this); + } - public void destroy() { + @Override + protected void onViewDetached() { mHeadsUpManager.removeListener(this); - mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null); + mView.setOnDrawingRectChangedListener(null); mWakeUpCoordinator.removeListener(this); mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp); mNotificationPanelViewController.setHeadsUpAppearanceController(null); @@ -170,7 +192,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, private void updateIsolatedIconLocation(boolean requireStateUpdate) { mNotificationIconAreaController.setIsolatedIconLocation( - mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate); + mView.getIconDrawingRect(), requireStateUpdate); } @Override @@ -184,20 +206,20 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, if (shouldBeVisible()) { newEntry = mHeadsUpManager.getTopEntry(); } - NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); - mHeadsUpStatusBarView.setEntry(newEntry); + NotificationEntry previousEntry = mView.getShowingEntry(); + mView.setEntry(newEntry); if (newEntry != previousEntry) { boolean animateIsolation = false; if (newEntry == null) { // no heads up anymore, lets start the disappear animation setShown(false); - animateIsolation = !mIsExpanded; + animateIsolation = !isExpanded(); } else if (previousEntry == null) { // We now have a headsUp and didn't have one before. Let's start the disappear // animation setShown(true); - animateIsolation = !mIsExpanded; + animateIsolation = !isExpanded(); } updateIsolatedIconLocation(false /* requireUpdate */); mNotificationIconAreaController.showIconIsolated(newEntry == null ? null @@ -210,8 +232,8 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, mShown = isShown; if (isShown) { updateParentClipping(false /* shouldClip */); - mHeadsUpStatusBarView.setVisibility(View.VISIBLE); - show(mHeadsUpStatusBarView); + mView.setVisibility(View.VISIBLE); + show(mView); hide(mClockView, View.INVISIBLE); if (mCenteredIconView.getVisibility() != View.GONE) { hide(mCenteredIconView, View.INVISIBLE); @@ -227,21 +249,21 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, if (mOperatorNameView != null) { show(mOperatorNameView); } - hide(mHeadsUpStatusBarView, View.GONE, () -> { + hide(mView, View.GONE, () -> { updateParentClipping(true /* shouldClip */); }); } // Show the status bar icons when the view gets shown / hidden if (mStatusBarStateController.getState() != StatusBarState.SHADE) { mCommandQueue.recomputeDisableFlags( - mHeadsUpStatusBarView.getContext().getDisplayId(), false); + mView.getContext().getDisplayId(), false); } } } private void updateParentClipping(boolean shouldClip) { ViewClippingUtil.setClippingDeactivated( - mHeadsUpStatusBarView, !shouldClip, mParentClippingParams); + mView, !shouldClip, mParentClippingParams); } /** @@ -310,7 +332,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, */ public boolean shouldBeVisible() { boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden(); - boolean canShow = !mIsExpanded && notificationsShown; + boolean canShow = !isExpanded() && notificationsShown; if (mBypassController.getBypassEnabled() && (mStatusBarStateController.getState() == StatusBarState.KEYGUARD || mKeyguardStateController.isKeyguardGoingAway()) @@ -328,17 +350,17 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, public void setAppearFraction(float expandedHeight, float appearFraction) { boolean changed = expandedHeight != mExpandedHeight; + boolean oldIsExpanded = isExpanded(); + mExpandedHeight = expandedHeight; mAppearFraction = appearFraction; - boolean isExpanded = expandedHeight > 0; // We only notify if the expandedHeight changed and not on the appearFraction, since // otherwise we may run into an infinite loop where the panel and this are constantly // updating themselves over just a small fraction if (changed) { updateHeadsUpHeaders(); } - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; + if (isExpanded() != oldIsExpanded) { updateTopEntry(); } } @@ -358,6 +380,10 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, } } + private boolean isExpanded() { + return mExpandedHeight > 0; + } + private void updateHeadsUpHeaders() { mHeadsUpManager.getAllEntries().forEach(entry -> { updateHeader(entry); @@ -376,22 +402,13 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint); + mView.onDarkChanged(area, darkIntensity, tint); } public void onStateChanged() { updateTopEntry(); } - void readFrom(HeadsUpAppearanceController oldController) { - if (oldController != null) { - mTrackedChild = oldController.mTrackedChild; - mExpandedHeight = oldController.mExpandedHeight; - mIsExpanded = oldController.mIsExpanded; - mAppearFraction = oldController.mAppearFraction; - } - } - @Override public void onFullyHiddenChanged(boolean isFullyHidden) { updateTopEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 407d28784a8d..88fe1cae2d76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -72,6 +72,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -154,6 +155,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsComponent mControlsComponent; private boolean mControlServicesAvailable = false; + @Nullable private View mAmbientIndicationArea; private ViewGroup mIndicationArea; private TextView mIndicationText; private TextView mIndicationTextBottom; @@ -274,6 +276,29 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void initFrom(KeyguardBottomAreaView oldBottomArea) { setStatusBar(oldBottomArea.mStatusBar); + + // if it exists, continue to use the original ambient indication container + // instead of the newly inflated one + if (mAmbientIndicationArea != null) { + // remove old ambient indication from its parent + View originalAmbientIndicationView = + oldBottomArea.findViewById(R.id.ambient_indication_container); + ((ViewGroup) originalAmbientIndicationView.getParent()) + .removeView(originalAmbientIndicationView); + + // remove current ambient indication from its parent (discard) + ViewGroup ambientIndicationParent = (ViewGroup) mAmbientIndicationArea.getParent(); + int ambientIndicationIndex = + ambientIndicationParent.indexOfChild(mAmbientIndicationArea); + ambientIndicationParent.removeView(mAmbientIndicationArea); + + // add the old ambient indication to this view + ambientIndicationParent.addView(originalAmbientIndicationView, ambientIndicationIndex); + mAmbientIndicationArea = originalAmbientIndicationView; + + // update burn-in offsets + dozeTimeTick(); + } } @Override @@ -288,6 +313,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button); mControlsButton = findViewById(R.id.controls_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); + mAmbientIndicationArea = findViewById(R.id.ambient_indication_container); mIndicationText = findViewById(R.id.keyguard_indication_text); mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom); mIndicationBottomMargin = getResources().getDimensionPixelSize( @@ -923,6 +949,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */) - mBurnInYOffset; mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount); + if (mAmbientIndicationArea != null) { + mAmbientIndicationArea.setTranslationY(burnInYOffset * mDarkAmount); + } } public void setAntiBurnInOffsetX(int burnInXOffset) { @@ -931,6 +960,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } mBurnInXOffset = burnInXOffset; mIndicationArea.setTranslationX(burnInXOffset); + if (mAmbientIndicationArea != null) { + mAmbientIndicationArea.setTranslationX(burnInXOffset); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 353868ba969f..9647486be992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -21,6 +21,7 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import android.content.Context; import android.content.res.ColorStateList; +import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; @@ -81,6 +82,13 @@ public class KeyguardBouncer { public void onStrongAuthStateChanged(int userId) { mBouncerPromptReason = mCallback.getBouncerPromptReason(); } + + @Override + public void onLockedOutStateChanged(BiometricSourceType type) { + if (type == BiometricSourceType.FINGERPRINT) { + mBouncerPromptReason = mCallback.getBouncerPromptReason(); + } + } }; private final Runnable mRemoveViewRunnable = this::removeView; private final KeyguardBypassController mKeyguardBypassController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index 570b0ca3564c..88ae0db5bad0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -37,7 +37,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; @@ -251,7 +250,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private void updateNavigation() { if (mNavigationBarController != null - && !QuickStepContract.isGesturalMode(mNavigationMode)) { + && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) { mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index 9021b74d1518..415fb92e37ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -28,6 +28,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -230,6 +231,14 @@ public class LightBarTransitionsController implements Dumpable, Callbacks, } /** + * Return whether to use the tint calculated in this class for nav icons. + */ + public boolean supportsIconTintForNavMode(int navigationMode) { + // In gesture mode, we already do region sampling to update tint based on content beneath. + return !QuickStepContract.isGesturalMode(navigationMode); + } + + /** * Interface to apply a specific dark intensity. */ public interface DarkIntensityApplier { 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 3f3328172e12..68ab07798520 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java @@ -124,6 +124,9 @@ public class LightsOutNotifController { public void onAnimationEnd(Animator a) { mLightsOutNotifView.setAlpha(showDot ? 1 : 0); mLightsOutNotifView.setVisibility(showDot ? View.VISIBLE : View.GONE); + // Unset the listener, otherwise this may persist for + // another view property animation + mLightsOutNotifView.animate().setListener(null); } }) .start(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 16aac4d765a7..db68ecf1fd18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -288,6 +288,7 @@ public class NotificationPanelViewController extends PanelViewController { private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); private static final Rect EMPTY_RECT = new Rect(); + private final InteractionJankMonitor mInteractionJankMonitor; private final LayoutInflater mLayoutInflater; private final FeatureFlags mFeatureFlags; private final PowerManager mPowerManager; @@ -480,9 +481,13 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mUserSetupComplete; private boolean mHideIconsDuringLaunchAnimation = true; private int mStackScrollerMeasuringPass; - private ArrayList<Consumer<ExpandableNotificationRow>> - mTrackingHeadsUpListeners = - new ArrayList<>(); + /** + * Non-null if there's a heads-up notification that we're currently tracking the position of. + */ + @Nullable + private ExpandableNotificationRow mTrackedHeadsUpNotification; + private final ArrayList<Consumer<ExpandableNotificationRow>> + mTrackingHeadsUpListeners = new ArrayList<>(); private HeadsUpAppearanceController mHeadsUpAppearanceController; private int mPanelAlpha; @@ -769,7 +774,8 @@ public class NotificationPanelViewController extends PanelViewController { PanelExpansionStateManager panelExpansionStateManager, NotificationRemoteInputManager remoteInputManager, Optional<SysUIUnfoldComponent> unfoldComponent, - ControlsComponent controlsComponent) { + ControlsComponent controlsComponent, + InteractionJankMonitor interactionJankMonitor) { super(view, falsingManager, dozeLog, @@ -782,7 +788,8 @@ public class NotificationPanelViewController extends PanelViewController { statusBarTouchableRegionManager, lockscreenGestureLogger, panelExpansionStateManager, - ambientState); + ambientState, + interactionJankMonitor); mView = view; mVibratorHelper = vibratorHelper; mKeyguardMediaController = keyguardMediaController; @@ -838,6 +845,7 @@ public class NotificationPanelViewController extends PanelViewController { mTapAgainViewController = tapAgainViewController; mUiExecutor = uiExecutor; mSecureSettings = secureSettings; + mInteractionJankMonitor = interactionJankMonitor; // TODO: inject via dagger instead of Dependency mSysUiState = Dependency.get(SysUiState.class); pulseExpansionHandler.setPulseExpandAbortListener(() -> { @@ -1892,14 +1900,16 @@ public class NotificationPanelViewController extends PanelViewController { } private void traceQsJank(boolean startTracing, boolean wasCancelled) { - InteractionJankMonitor monitor = InteractionJankMonitor.getInstance(); + if (mInteractionJankMonitor == null) { + return; + } if (startTracing) { - monitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + mInteractionJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } else { if (wasCancelled) { - monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } else { - monitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } } } @@ -3198,18 +3208,24 @@ public class NotificationPanelViewController extends PanelViewController { mQsExpandImmediate = false; mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false); mTwoFingerQsExpandPossible = false; - notifyListenersTrackingHeadsUp(null); + updateTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); } - private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) { + private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) { + mTrackedHeadsUpNotification = pickedChild; for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) { Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i); listener.accept(pickedChild); } } + @Nullable + public ExpandableNotificationRow getTrackedHeadsUpNotification() { + return mTrackedHeadsUpNotification; + } + private void setListening(boolean listening) { mKeyguardStatusBarViewController.setBatteryListening(listening); if (mQs == null) return; @@ -3446,7 +3462,7 @@ public class NotificationPanelViewController extends PanelViewController { public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { if (pickedChild != null) { - notifyListenersTrackingHeadsUp(pickedChild); + updateTrackingHeadsUp(pickedChild); mExpandingFromHeadsUp = true; } // otherwise we update the state when the expansion is finished diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 2823d985102f..6a00591ea0e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -184,6 +184,7 @@ public abstract class PanelViewController { protected final LockscreenGestureLogger mLockscreenGestureLogger; private final PanelExpansionStateManager mPanelExpansionStateManager; private final TouchHandler mTouchHandler; + private final InteractionJankMonitor mInteractionJankMonitor; protected abstract void onExpandingFinished(); @@ -222,7 +223,8 @@ public abstract class PanelViewController { StatusBarTouchableRegionManager statusBarTouchableRegionManager, LockscreenGestureLogger lockscreenGestureLogger, PanelExpansionStateManager panelExpansionStateManager, - AmbientState ambientState) { + AmbientState ambientState, + InteractionJankMonitor interactionJankMonitor) { mAmbientState = ambientState; mView = view; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; @@ -273,6 +275,7 @@ public abstract class PanelViewController { mVibratorHelper = vibratorHelper; mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; + mInteractionJankMonitor = interactionJankMonitor; } protected void loadDimens() { @@ -1411,17 +1414,26 @@ public abstract class PanelViewController { } private void beginJankMonitoring(int cuj) { + if (mInteractionJankMonitor == null) { + return; + } InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withView(cuj, mView) .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); - InteractionJankMonitor.getInstance().begin(builder); + mInteractionJankMonitor.begin(builder); } private void endJankMonitoring(int cuj) { + if (mInteractionJankMonitor == null) { + return; + } InteractionJankMonitor.getInstance().end(cuj); } private void cancelJankMonitoring(int cuj) { + if (mInteractionJankMonitor == null) { + return; + } InteractionJankMonitor.getInstance().cancel(cuj); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java index 9cefded72365..bf5467716910 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java @@ -120,6 +120,9 @@ public class SettingsButton extends AlphaOptimizedImageButton { setAlpha(1f); setTranslationX(0); cancelLongClick(); + // Unset the listener, otherwise this may persist for + // another view property animation + animate().setListener(null); } @Override 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 94b010df2218..316b99e176de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -90,6 +90,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.util.Slog; @@ -133,6 +134,7 @@ import com.android.systemui.EventLogTags; import com.android.systemui.InitController; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.assist.AssistManager; @@ -521,7 +523,6 @@ public class StatusBar extends CoreStartable implements private QSPanelController mQSPanelController; private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; - private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; KeyguardIndicationController mKeyguardIndicationController; private View mReportRejectedTouch; @@ -666,7 +667,6 @@ public class StatusBar extends CoreStartable implements private boolean mNoAnimationOnNextBarModeChange; private final SysuiStatusBarStateController mStatusBarStateController; - private HeadsUpAppearanceController mHeadsUpAppearanceController; private final ActivityLaunchAnimator mActivityLaunchAnimator; private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; protected StatusBarNotificationPresenter mPresenter; @@ -737,6 +737,7 @@ public class StatusBar extends CoreStartable implements VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, + AccessibilityFloatingMenuController accessibilityFloatingMenuController, Lazy<AssistManager> assistManagerLazy, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, @@ -768,7 +769,6 @@ public class StatusBar extends CoreStartable implements ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, - PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -808,7 +808,6 @@ public class StatusBar extends CoreStartable implements mKeyguardStateController = keyguardStateController; mHeadsUpManager = headsUpManagerPhone; mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; - mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory; mKeyguardIndicationController = keyguardIndicationController; mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; mDynamicPrivacyController = dynamicPrivacyController; @@ -842,6 +841,7 @@ public class StatusBar extends CoreStartable implements mVisualStabilityManager = visualStabilityManager; mDeviceProvisionedController = deviceProvisionedController; mNavigationBarController = navigationBarController; + mAccessibilityFloatingMenuController = accessibilityFloatingMenuController; mAssistManagerLazy = assistManagerLazy; mConfigurationController = configurationController; mNotificationShadeWindowController = notificationShadeWindowController; @@ -1057,6 +1057,8 @@ public class StatusBar extends CoreStartable implements mBatteryController.observe(mLifecycle, mBatteryStateChangeCallback); mLifecycle.setCurrentState(RESUMED); + mAccessibilityFloatingMenuController.init(); + // set the initial view visibility int disabledFlags1 = result.mDisabledFlags1; int disabledFlags2 = result.mDisabledFlags2; @@ -1154,12 +1156,8 @@ public class StatusBar extends CoreStartable implements } mStatusBarView = statusBarFragmentComponent.getPhoneStatusBarView(); - - // TODO(b/205609837): Migrate this to StatusBarFragmentComponent. - mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory - .create(mStatusBarView, mNotificationPanelViewController - .getStatusBarTouchEventHandler()); - mPhoneStatusBarViewController.init(); + mPhoneStatusBarViewController = + statusBarFragmentComponent.getPhoneStatusBarViewController(); // Ensure we re-propagate panel expansion values to the panel controller and // any listeners it may have, such as PanelBar. This will also ensure we @@ -1169,21 +1167,6 @@ public class StatusBar extends CoreStartable implements mNotificationPanelViewController.updatePanelExpansionAndVisibility(); setBouncerShowingForStatusBarComponents(mBouncerShowing); - HeadsUpAppearanceController oldController = mHeadsUpAppearanceController; - if (mHeadsUpAppearanceController != null) { - // This view is being recreated, let's destroy the old one - mHeadsUpAppearanceController.destroy(); - } - // TODO (b/136993073) Separate notification shade and status bar - // TODO(b/205609837): Migrate this to StatusBarFragmentComponent. - mHeadsUpAppearanceController = new HeadsUpAppearanceController( - mNotificationIconAreaController, mHeadsUpManager, - mStackScrollerController, - mStatusBarStateController, mKeyguardBypassController, - mKeyguardStateController, mWakeUpCoordinator, mCommandQueue, - mNotificationPanelViewController, mStatusBarView); - mHeadsUpAppearanceController.readFrom(oldController); - mLightsOutNotifController.setLightsOutNotifView( mStatusBarView.findViewById(R.id.notification_lights_out)); mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); @@ -1488,6 +1471,7 @@ public class StatusBar extends CoreStartable implements mBubblesOptional, mPresenter, mStackScrollerController.getNotificationListContainer(), + mStackScrollerController.getNotifStackController(), mNotificationActivityStarter, mPresenter); } @@ -1884,10 +1868,6 @@ public class StatusBar extends CoreStartable implements return mDozeServiceHost.isPulsing(); } - public boolean hideStatusBarIconsWhenExpanded() { - return mNotificationPanelViewController.hideStatusBarIconsWhenExpanded(); - } - @Nullable public View getAmbientIndicationContainer() { return mAmbientIndicationContainer; @@ -1909,10 +1889,6 @@ public class StatusBar extends CoreStartable implements mScrimController.setKeyguardOccluded(occluded); } - public boolean headsUpShouldBeVisible() { - return mHeadsUpAppearanceController.shouldBeVisible(); - } - /** A launch animation was cancelled. */ //TODO: These can / should probably be moved to NotificationPresenter or ShadeController public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { @@ -1992,7 +1968,7 @@ public class StatusBar extends CoreStartable implements } } - public void maybeEscalateHeadsUp() { + private void maybeEscalateHeadsUp() { mHeadsUpManager.getAllEntries().forEach(entry -> { final StatusBarNotification sbn = entry.getSbn(); final Notification notification = sbn.getNotification(); @@ -2003,6 +1979,7 @@ public class StatusBar extends CoreStartable implements try { EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); + wakeUpForFullScreenIntent(); notification.fullScreenIntent.send(); entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { @@ -2012,6 +1989,17 @@ public class StatusBar extends CoreStartable implements mHeadsUpManager.releaseAllImmediately(); } + void wakeUpForFullScreenIntent() { + if (isGoingToSleep() || mDozing) { + mPowerManager.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "com.android.systemui:full_screen_intent"); + mWakeUpComingFromTouch = false; + mWakeUpTouchLocation = null; + } + } + void makeExpandedVisible(boolean force) { if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { @@ -2306,7 +2294,8 @@ public class StatusBar extends CoreStartable implements } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(FileDescriptor fd, PrintWriter pwOriginal, String[] args) { + IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); synchronized (mQueueLock) { pw.println("Current Status Bar state:"); pw.println(" mExpandedVisible=" + mExpandedVisible); @@ -2347,14 +2336,12 @@ public class StatusBar extends CoreStartable implements } pw.println(" mStackScroller: "); if (mStackScroller != null) { - DumpUtilsKt.withIndenting(pw, ipw -> { - // Triple indent until we rewrite the rest of this dump() - ipw.increaseIndent(); - ipw.increaseIndent(); - mStackScroller.dump(fd, ipw, args); - ipw.decreaseIndent(); - ipw.decreaseIndent(); - }); + // Double indent until we rewrite the rest of this dump() + pw.increaseIndent(); + pw.increaseIndent(); + mStackScroller.dump(fd, pw, args); + pw.decreaseIndent(); + pw.decreaseIndent(); } pw.println(" Theme:"); String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + ""; @@ -3541,7 +3528,7 @@ public class StatusBar extends CoreStartable implements DejankUtils.startDetectingBlockingIpcs(tag); updateRevealEffect(false /* wakingUp */); updateNotificationPanelTouchState(); - notifyHeadsUpGoingToSleep(); + maybeEscalateHeadsUp(); dismissVolumeDialog(); mWakeUpCoordinator.setFullyAwake(false); mBypassHeadsUpNotifier.setFullyAwake(false); @@ -3827,6 +3814,7 @@ public class StatusBar extends CoreStartable implements private final DeviceProvisionedController mDeviceProvisionedController; private final NavigationBarController mNavigationBarController; + private final AccessibilityFloatingMenuController mAccessibilityFloatingMenuController; // UI-specific methods @@ -4086,10 +4074,6 @@ public class StatusBar extends CoreStartable implements } } - protected void notifyHeadsUpGoingToSleep() { - maybeEscalateHeadsUp(); - } - /** * @return Whether the security bouncer from Keyguard is showing. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index a77a097f0453..ae3b7ee1c809 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -30,11 +30,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.media.AudioAttributes; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; import android.os.UserHandle; +import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; @@ -106,10 +106,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { private final VibrationEffect mCameraLaunchGestureVibrationEffect; - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @Inject StatusBarCommandQueueCallbacks( @@ -611,9 +609,9 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { } private void vibrateForCameraGesture() { - // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. mVibratorOptional.ifPresent( - v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES)); + v -> v.vibrate(mCameraLaunchGestureVibrationEffect, + HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES)); } private static VibrationEffect getCameraGestureVibrationEffect( @@ -627,6 +625,8 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { .compose(); } if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) { + // Make sure to pass -1 for repeat so VibratorManagerService doesn't stop us when going + // to sleep. return VibrationEffect.createWaveform( StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS, StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 7ab4a1ec237e..e2bf0db6eb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -441,6 +441,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)} */ public void showBouncer(boolean scrimmed) { + resetAlternateAuth(false); + if (mShowing && !mBouncer.isShowing()) { mBouncer.show(false /* resetSecuritySelection */, scrimmed); } @@ -553,12 +555,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onStartedWakingUp() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(false); - View currentView = getCurrentNavBarView(); - if (currentView != null) { - currentView.animate() - .alpha(1f) - .setDuration(NAV_BAR_CONTENT_FADE_DURATION) - .start(); + NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + if (navBarView != null) { + navBarView.forEachView(view -> + view.animate() + .alpha(1f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start()); } } @@ -566,12 +569,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onStartedGoingToSleep() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(true); - View currentView = getCurrentNavBarView(); - if (currentView != null) { - currentView.animate() - .alpha(0f) - .setDuration(NAV_BAR_CONTENT_FADE_DURATION) - .start(); + NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + if (navBarView != null) { + navBarView.forEachView(view -> + view.animate() + .alpha(0f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start()); } } @@ -1013,17 +1017,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBar.onKeyguardViewManagerStatesUpdated(); } - /** - * Updates the visibility of the nav bar content views. - */ - private void updateNavigationBarContentVisibility(boolean navBarContentVisible) { - final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); - if (navBarView != null && navBarView.getCurrentView() != null) { - final View currentView = navBarView.getCurrentView(); - currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE); - } - } - private View getCurrentNavBarView() { final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); return navBarView != null ? navBarView.getCurrentView() : null; 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 11ed8cdc7072..863ce5758f9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -41,6 +41,8 @@ import android.text.TextUtils; import android.util.EventLog; import android.view.View; +import androidx.annotation.VisibleForTesting; + import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.NotificationVisibility; @@ -588,7 +590,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } } - private void handleFullScreenIntent(NotificationEntry entry) { + @VisibleForTesting + void handleFullScreenIntent(NotificationEntry entry) { if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { if (shouldSuppressFullScreenIntent(entry)) { mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey()); @@ -612,6 +615,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit try { EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, entry.getKey()); + mStatusBar.wakeUpForFullScreenIntent(); fullscreenIntent.send(); entry.notifyFullScreenIntentLaunched(); mMetricsLogger.count("note_fullscreen", 1); 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 9682c605e1e6..c8e1cdc75242 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -192,6 +192,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, initController.addPostInitTask(() -> { mKeyguardIndicationController.init(); mViewHierarchyManager.setUpWithPresenter(this, + stackScrollerController.getNotifStackController(), stackScrollerController.getNotificationListContainer()); mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 1130ec24108a..ed52a81751dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -36,6 +36,8 @@ import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import androidx.annotation.Nullable; + import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.DialogListener; @@ -303,13 +305,32 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog, * the screen off / close system dialogs broadcast. * <p> * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after - * calling this because it causes a leak of BroadcastReceiver. + * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that + * takes an extra Runnable as a parameter. * * @param dialog The dialog to be associated with the listener. */ public static void registerDismissListener(Dialog dialog) { + registerDismissListener(dialog, null); + } + + + /** + * Registers a listener that dismisses the given dialog when it receives + * the screen off / close system dialogs broadcast. + * <p> + * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after + * calling this because it causes a leak of BroadcastReceiver. + * + * @param dialog The dialog to be associated with the listener. + * @param dismissAction An action to run when the dialog is dismissed. + */ + public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) { DismissReceiver dismissReceiver = new DismissReceiver(dialog); - dialog.setOnDismissListener(d -> dismissReceiver.unregister()); + dialog.setOnDismissListener(d -> { + dismissReceiver.unregister(); + if (dismissAction != null) dismissAction.run(); + }); dismissReceiver.register(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index b3f59b461aa9..33171b233499 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -28,6 +28,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -89,7 +90,6 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -186,6 +186,7 @@ public interface StatusBarPhoneModule { VisualStabilityManager visualStabilityManager, DeviceProvisionedController deviceProvisionedController, NavigationBarController navigationBarController, + AccessibilityFloatingMenuController accessibilityFloatingMenuController, Lazy<AssistManager> assistManagerLazy, ConfigurationController configurationController, NotificationShadeWindowController notificationShadeWindowController, @@ -217,7 +218,6 @@ public interface StatusBarPhoneModule { ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, - PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -289,6 +289,7 @@ public interface StatusBarPhoneModule { visualStabilityManager, deviceProvisionedController, navigationBarController, + accessibilityFloatingMenuController, assistManagerLazy, configurationController, notificationShadeWindowController, @@ -319,7 +320,6 @@ public interface StatusBarPhoneModule { extensionController, userInfoControllerImpl, operatorNameViewControllerFactory, - phoneStatusBarViewControllerFactory, phoneStatusBarPolicy, keyguardIndicationController, demoModeController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 8f11819b9677..26c9458bf3aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -46,7 +46,6 @@ import com.android.systemui.statusbar.phone.NotificationPanelView; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; @@ -61,15 +60,12 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tuner.TunerService; -import java.util.Optional; - import javax.inject.Named; -import dagger.Lazy; import dagger.Module; import dagger.Provides; -@Module +@Module(subcomponents = StatusBarFragmentComponent.class) public abstract class StatusBarViewModule { public static final String SPLIT_SHADE_HEADER = "split_shade_header"; @@ -243,7 +239,6 @@ public abstract class StatusBarViewModule { NotificationPanelViewController notificationPanelViewController, NetworkController networkController, StatusBarStateController statusBarStateController, - Lazy<Optional<StatusBar>> statusBarOptionalLazy, CommandQueue commandQueue, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, OperatorNameViewController.Factory operatorNameViewControllerFactory @@ -261,7 +256,6 @@ public abstract class StatusBarViewModule { notificationPanelViewController, networkController, statusBarStateController, - statusBarOptionalLazy, commandQueue, collapsedStatusBarFragmentLogger, operatorNameViewControllerFactory); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 2e3893a8c21b..d6ba6f3ff97a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -55,7 +55,6 @@ import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; @@ -71,12 +70,9 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import javax.inject.Inject; -import dagger.Lazy; - /** * Contains the collapsed status bar and handles hiding/showing based on disable flags * and keyguard state. Also manages lifecycle to make sure the views it contains are being @@ -104,7 +100,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private View mCenteredIconArea; private int mDisabled1; private int mDisabled2; - private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; private DarkIconManager mDarkIconManager; private final StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory; private final CommandQueue mCommandQueue; @@ -151,7 +146,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue NotificationPanelViewController notificationPanelViewController, NetworkController networkController, StatusBarStateController statusBarStateController, - Lazy<Optional<StatusBar>> statusBarOptionalLazy, CommandQueue commandQueue, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, OperatorNameViewController.Factory operatorNameViewControllerFactory @@ -169,7 +163,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mNotificationPanelViewController = notificationPanelViewController; mNetworkController = networkController; mStatusBarStateController = statusBarStateController; - mStatusBarOptionalLazy = statusBarOptionalLazy; mCommandQueue = commandQueue; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; @@ -329,8 +322,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } protected int adjustDisableFlags(int state) { - boolean headsUpVisible = mStatusBarOptionalLazy.get() - .map(StatusBar::headsUpShouldBeVisible).orElse(false); + boolean headsUpVisible = + mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); if (headsUpVisible) { state |= DISABLE_CLOCK; } @@ -399,10 +392,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private boolean shouldHideNotificationIcons() { - final Optional<StatusBar> statusBarOptional = mStatusBarOptionalLazy.get(); if (!mPanelExpansionStateManager.isClosed() - && statusBarOptional.map( - StatusBar::hideStatusBarIconsWhenExpanded).orElse(false)) { + && mNotificationPanelViewController.hideStatusBarIconsWhenExpanded()) { return true; } return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java index 47c187540740..3656ed116b32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import dagger.BindsInstance; @@ -56,6 +58,8 @@ public interface StatusBarFragmentComponent { // No one accesses this controller, so we need to make sure we reference it here so it does // get initialized. getBatteryMeterViewController().init(); + getHeadsUpAppearanceController().init(); + getPhoneStatusBarViewController().init(); } /** */ @@ -66,4 +70,12 @@ public interface StatusBarFragmentComponent { @StatusBarFragmentScope @RootView PhoneStatusBarView getPhoneStatusBarView(); + + /** */ + @StatusBarFragmentScope + PhoneStatusBarViewController getPhoneStatusBarViewController(); + + /** */ + @StatusBarFragmentScope + HeadsUpAppearanceController getHeadsUpAppearanceController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 969361bb6100..d2445580d56b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import dagger.Module; @@ -43,4 +45,16 @@ public interface StatusBarFragmentModule { static BatteryMeterView provideBatteryMeterView(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.battery); } + + /** */ + @Provides + @StatusBarFragmentScope + static PhoneStatusBarViewController providePhoneStatusBarViewController( + PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, + @RootView PhoneStatusBarView phoneStatusBarView, + NotificationPanelViewController notificationPanelViewController) { + return phoneStatusBarViewControllerFactory.create( + phoneStatusBarView, + notificationPanelViewController.getStatusBarTouchEventHandler()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index 530da431faeb..ef0a5b4235c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -95,7 +95,6 @@ class RemoteInputViewControllerImpl @Inject constructor( private val uiEventLogger: UiEventLogger ) : RemoteInputViewController { - private object Token private val onSendListeners = ArraySet<OnSendRemoteInputListener>() private val resources get() = view.resources @@ -179,8 +178,8 @@ class RemoteInputViewControllerImpl @Inject constructor( entry.lastRemoteInputSent = SystemClock.elapsedRealtime() entry.mRemoteEditImeAnimatingAway = true - remoteInputController.addSpinning(entry.key, Token) - remoteInputController.removeRemoteInput(entry, Token) + remoteInputController.addSpinning(entry.key, view.mToken) + remoteInputController.removeRemoteInput(entry, view.mToken) remoteInputController.remoteInputSent(entry) entry.setHasSentReply() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt new file mode 100644 index 000000000000..c6dbdb1b09a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsController.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +/** + * Interface for tracking packages with running foreground services and demoting foreground status + */ +interface RunningFgsController : CallbackController<RunningFgsController.Callback> { + + /** + * @return A list of [UserPackageTime]s which have running foreground service(s) + */ + fun getPackagesWithFgs(): List<UserPackageTime> + + /** + * Stops all foreground services running as a package + * @param userId the userId the package is running under + * @param packageName the packageName + */ + fun stopFgs(userId: Int, packageName: String) + + /** + * Returns when the list of packages with foreground services changes + */ + interface Callback { + /** + * The thing that + * @param packages the list of packages + */ + fun onFgsPackagesChanged(packages: List<UserPackageTime>) + } + + /** + * A triplet <user, packageName, timeMillis> where each element is a package running + * under a user that has had at least one foreground service running since timeMillis. + * Time should be derived from [SystemClock.elapsedRealtime]. + */ + data class UserPackageTime(val userId: Int, val packageName: String, val startTimeMillis: Long) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt new file mode 100644 index 000000000000..d44d36503e30 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RunningFgsControllerImpl.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.policy + +import android.app.IActivityManager +import android.app.IForegroundServiceObserver +import android.os.IBinder +import android.os.RemoteException +import android.util.Log +import androidx.annotation.GuardedBy +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.RunningFgsController.Callback +import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime +import com.android.systemui.util.time.SystemClock +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Implementation for [RunningFgsController] + */ +@SysUISingleton +class RunningFgsControllerImpl @Inject constructor( + @Background private val executor: Executor, + private val systemClock: SystemClock, + private val activityManager: IActivityManager +) : RunningFgsController, IForegroundServiceObserver.Stub() { + + companion object { + private val LOG_TAG = RunningFgsControllerImpl::class.java.simpleName + } + + private val lock = Any() + + @GuardedBy("lock") + var initialized = false + + @GuardedBy("lock") + private val runningServiceTokens = mutableMapOf<UserPackageKey, StartTimeAndTokensValue>() + + @GuardedBy("lock") + private val callbacks = mutableSetOf<Callback>() + + fun init() { + synchronized(lock) { + if (initialized) { + return + } + try { + activityManager.registerForegroundServiceObserver(this) + } catch (e: RemoteException) { + e.rethrowFromSystemServer() + } + + initialized = true + } + } + + override fun addCallback(listener: Callback) { + init() + synchronized(lock) { callbacks.add(listener) } + } + + override fun removeCallback(listener: Callback) { + init() + synchronized(lock) { + if (!callbacks.remove(listener)) { + Log.e(LOG_TAG, "Callback was not registered.", RuntimeException()) + } + } + } + + override fun observe(lifecycle: Lifecycle?, listener: Callback?): Callback { + init() + return super.observe(lifecycle, listener) + } + + override fun observe(owner: LifecycleOwner?, listener: Callback?): Callback { + init() + return super.observe(owner, listener) + } + + override fun getPackagesWithFgs(): List<UserPackageTime> { + init() + return synchronized(lock) { getPackagesWithFgsLocked() } + } + + private fun getPackagesWithFgsLocked(): List<UserPackageTime> = + runningServiceTokens.map { + UserPackageTime(it.key.userId, it.key.packageName, it.value.fgsStartTime) + } + + override fun stopFgs(userId: Int, packageName: String) { + init() + try { + activityManager.makeServicesNonForeground(packageName, userId) + } catch (e: RemoteException) { + e.rethrowFromSystemServer() + } + } + + private data class UserPackageKey( + val userId: Int, + val packageName: String + ) + + private class StartTimeAndTokensValue(systemClock: SystemClock) { + val fgsStartTime = systemClock.elapsedRealtime() + var tokens = mutableSetOf<IBinder>() + fun addToken(token: IBinder): Boolean { + return tokens.add(token) + } + + fun removeToken(token: IBinder): Boolean { + return tokens.remove(token) + } + + val isEmpty: Boolean + get() = tokens.isEmpty() + } + + override fun onForegroundStateChanged( + token: IBinder, + packageName: String, + userId: Int, + isForeground: Boolean + ) { + val result = synchronized(lock) { + val userPackageKey = UserPackageKey(userId, packageName) + if (isForeground) { + var addedNew = false + runningServiceTokens.getOrPut(userPackageKey) { + addedNew = true + StartTimeAndTokensValue(systemClock) + }.addToken(token) + if (!addedNew) { + return + } + } else { + val startTimeAndTokensValue = runningServiceTokens[userPackageKey] + if (startTimeAndTokensValue?.removeToken(token) == false) { + Log.e(LOG_TAG, + "Stopped foreground service was not known to be running.") + return + } + if (!startTimeAndTokensValue!!.isEmpty) { + return + } + runningServiceTokens.remove(userPackageKey) + } + getPackagesWithFgsLocked().toList() + } + + callbacks.forEach { executor.execute { it.onFgsPackagesChanged(result) } } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 85add6c21b72..a537b2a238cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -219,6 +219,7 @@ public class SmartReplyView extends ViewGroup { private void clearLayoutLineCount(View view) { if (view instanceof TextView) { ((TextView) view).nullLayouts(); + view.forceLayout(); } } @@ -264,18 +265,29 @@ public class SmartReplyView extends ViewGroup { if (maxNumActions != -1 // -1 means 'no limit' && lp.mButtonType == SmartButtonType.ACTION && numShownActions >= maxNumActions) { + lp.mNoShowReason = "max-actions-shown"; // We've reached the maximum number of actions, don't add another one! continue; } clearLayoutLineCount(child); child.measure(MEASURE_SPEC_ANY_LENGTH, heightMeasureSpec); + if (((Button) child).getLayout() == null) { + Log.wtf(TAG, "Button layout is null after measure."); + } coveredSuggestions.add(child); final int lineCount = ((Button) child).getLineCount(); - if (lineCount < 1 || lineCount > 2) { - // If smart reply has no text, or more than two lines, then don't show it. + if (lineCount < 1) { + // If smart reply has no text, then don't show it. + lp.mNoShowReason = "line-count-0"; + continue; + + } + if (lineCount > 2) { + // If smart reply has more than two lines, then don't show it. + lp.mNoShowReason = "line-count-3+"; continue; } @@ -324,6 +336,7 @@ public class SmartReplyView extends ViewGroup { markButtonsWithPendingSqueezeStatusAs( LayoutParams.SQUEEZE_STATUS_FAILED, coveredSuggestions); + lp.mNoShowReason = "overflow"; // The current button doesn't fit, keep on adding lower-priority buttons in case // any of those fit. continue; @@ -336,6 +349,7 @@ public class SmartReplyView extends ViewGroup { } lp.show = true; + lp.mNoShowReason = "n/a"; displayedChildCount++; if (lp.mButtonType == SmartButtonType.ACTION) { numShownActions++; @@ -349,6 +363,7 @@ public class SmartReplyView extends ViewGroup { for (View smartReplyButton : smartReplies) { final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams(); lp.show = false; + lp.mNoShowReason = "not-enough-system-replies"; } // Reset our measures back to when we had only added actions (before adding // replies). @@ -427,6 +442,8 @@ public class SmartReplyView extends ViewGroup { pw.print(lp.squeezeStatus); pw.print(" show="); pw.print(lp.show); + pw.print(" noShowReason="); + pw.print(lp.mNoShowReason); pw.print(" view="); pw.println(child); } @@ -498,6 +515,7 @@ public class SmartReplyView extends ViewGroup { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.show = false; lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_NONE; + lp.mNoShowReason = "reset"; } } @@ -590,6 +608,9 @@ public class SmartReplyView extends ViewGroup { button.getPaddingLeft() + button.getPaddingRight() + textWidth + getLeftCompoundDrawableWidthWithPadding(button), MeasureSpec.AT_MOST); button.measure(widthMeasureSpec, heightMeasureSpec); + if (button.getLayout() == null) { + Log.wtf(TAG, "Button layout is null after measure."); + } final int newWidth = button.getMeasuredWidth(); @@ -772,6 +793,7 @@ public class SmartReplyView extends ViewGroup { private boolean show = false; private int squeezeStatus = SQUEEZE_STATUS_NONE; SmartButtonType mButtonType = SmartButtonType.REPLY; + String mNoShowReason = "new"; private LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 364c9311ef96..cdbdb23531ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -351,7 +351,7 @@ public class UserSwitcherController implements Dumpable { boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers) && guestRecord == null; boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers) - && mUserManager.canAddMoreUsers(); + && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); boolean createIsRestricted = !addUsersWhenLocked; if (guestRecord == null) { diff --git a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt index 9f33c271881d..f9524769d304 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt @@ -19,37 +19,26 @@ package com.android.systemui.util import android.util.IndentingPrintWriter import android.view.View import java.io.PrintWriter -import java.util.function.Consumer /** - * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter]. + * Get an [IndentingPrintWriter] which either is or wraps the given [PrintWriter]. * - * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same - * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling - * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter] - * should not be used before the block completes. + * The original [PrintWriter] should not be used until the returned [IndentingPrintWriter] is no + * longer being used, to avoid inconsistent writing. */ -inline fun PrintWriter.withIndenting(block: (IndentingPrintWriter) -> Unit) { - if (this is IndentingPrintWriter) { - this.withIncreasedIndent { block(this) } - } else { - block(IndentingPrintWriter(this)) - } -} +fun PrintWriter.asIndenting(): IndentingPrintWriter = + (this as? IndentingPrintWriter) ?: IndentingPrintWriter(this) /** - * Run some code that will print to an [IndentingPrintWriter] that wraps the given [PrintWriter]. - * - * If the given [PrintWriter] is an [IndentingPrintWriter], the block will be passed that same - * instance with [IndentingPrintWriter.increaseIndent] having been called, and calling - * [IndentingPrintWriter.decreaseIndent] after completion of the block, so the passed [PrintWriter] - * should not be used before the block completes. + * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on + * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion. */ -fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) { - if (this is IndentingPrintWriter) { - this.withIncreasedIndent { consumer.accept(this) } - } else { - consumer.accept(IndentingPrintWriter(this)) +inline fun IndentingPrintWriter.withIncreasedIndent(block: () -> Unit) { + this.increaseIndent() + try { + block() + } finally { + this.decreaseIndent() } } @@ -57,10 +46,10 @@ fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) { * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion. */ -inline fun IndentingPrintWriter.withIncreasedIndent(block: () -> Unit) { +fun IndentingPrintWriter.withIncreasedIndent(runnable: Runnable) { this.increaseIndent() try { - block() + runnable.run() } finally { this.decreaseIndent() } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 63ca94cde7cf..6dd6d6d17d58 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.ShellCommandHandler; +import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.nano.WmShellTraceProto; @@ -114,6 +115,7 @@ public final class WMShell extends CoreStartable private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional; private final Optional<ShellCommandHandler> mShellCommandHandler; private final Optional<SizeCompatUI> mSizeCompatUIOptional; + private final Optional<DragAndDrop> mDragAndDropOptional; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; @@ -142,6 +144,7 @@ public final class WMShell extends CoreStartable Optional<HideDisplayCutout> hideDisplayCutoutOptional, Optional<ShellCommandHandler> shellCommandHandler, Optional<SizeCompatUI> sizeCompatUIOptional, + Optional<DragAndDrop> dragAndDropOptional, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -167,6 +170,7 @@ public final class WMShell extends CoreStartable mProtoTracer = protoTracer; mShellCommandHandler = shellCommandHandler; mSizeCompatUIOptional = sizeCompatUIOptional; + mDragAndDropOptional = dragAndDropOptional; mSysUiMainExecutor = sysUiMainExecutor; } @@ -182,6 +186,7 @@ public final class WMShell extends CoreStartable mOneHandedOptional.ifPresent(this::initOneHanded); mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi); + mDragAndDropOptional.ifPresent(this::initDragAndDrop); } @VisibleForTesting @@ -396,6 +401,20 @@ public final class WMShell extends CoreStartable mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback); } + void initDragAndDrop(DragAndDrop dragAndDrop) { + mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() { + @Override + public void onConfigChanged(Configuration newConfig) { + dragAndDrop.onConfigChanged(newConfig); + } + + @Override + public void onThemeChanged() { + dragAndDrop.onThemeChanged(); + } + }); + } + @Override public void writeToProto(SystemUiTraceProto proto) { if (proto.wmShell == null) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java index 6f2c56515767..ac1a83c269e0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java @@ -80,8 +80,6 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { // Explicitly disable one handed keyguard. mTestableResources.addOverride( R.bool.can_use_one_handed_bouncer, false); - mTestableResources.addOverride( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, false); when(mKeyguardSecurityContainerControllerFactory.create(any( KeyguardSecurityContainer.SecurityCallback.class))) @@ -149,8 +147,6 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { // Start disabled. mTestableResources.addOverride( R.bool.can_use_one_handed_bouncer, false); - mTestableResources.addOverride( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, false); mKeyguardHostViewController.init(); assertEquals( @@ -160,8 +156,6 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase { // And enable mTestableResources.addOverride( R.bool.can_use_one_handed_bouncer, true); - mTestableResources.addOverride( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning, true); mKeyguardHostViewController.updateResources(); assertEquals( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 64bdc2e9dc5d..030464a0bd95 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -18,8 +18,10 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; +import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; +import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; + import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -27,7 +29,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -49,6 +50,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; import org.junit.Rule; @@ -107,6 +109,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private Resources mResources; @Mock private FalsingCollector mFalsingCollector; + @Mock + private GlobalSettings mGlobalSettings; private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -140,7 +144,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, mKeyguardSecurityViewFlipperController, - mConfigurationController, mFalsingCollector) + mConfigurationController, mFalsingCollector, mGlobalSettings) .create(mSecurityCallback); } @@ -178,30 +182,13 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { public void onResourcesUpdate_callsThroughOnRotationChange() { // Rotation is the same, shouldn't cause an update mKeyguardSecurityContainerController.updateResources(); - verify(mView, times(0)).setOneHandedMode(anyBoolean()); + verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings); // Update rotation. Should trigger update mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; mKeyguardSecurityContainerController.updateResources(); - verify(mView, times(1)).setOneHandedMode(anyBoolean()); - } - - @Test - public void updateKeyguardPosition_callsThroughToViewInOneHandedMode() { - when(mView.isOneHandedMode()).thenReturn(true); - mKeyguardSecurityContainerController.updateKeyguardPosition(VIEW_WIDTH / 3f); - verify(mView).setOneHandedModeLeftAligned(true, false); - - mKeyguardSecurityContainerController.updateKeyguardPosition((VIEW_WIDTH / 3f) * 2); - verify(mView).setOneHandedModeLeftAligned(false, false); - } - - @Test - public void updateKeyguardPosition_ignoredInTwoHandedMode() { - when(mView.isOneHandedMode()).thenReturn(false); - mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f); - verify(mView, never()).setOneHandedModeLeftAligned(anyBoolean(), anyBoolean()); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); } private void touchDownLeftSide() { @@ -228,7 +215,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void onInterceptTap_inhibitsFalsingInOneHandedMode() { - when(mView.isOneHandedMode()).thenReturn(true); + when(mView.getMode()).thenReturn(MODE_ONE_HANDED); when(mView.isOneHandedModeLeftAligned()).thenReturn(true); touchDownLeftSide(); @@ -251,83 +238,35 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test - public void showSecurityScreen_oneHandedMode_bothFlagsDisabled_noOneHandedMode() { - setUpKeyguardFlags( - /* deviceConfigCanUseOneHandedKeyguard= */false, - /* sysuiResourceCanUseOneHandedKeyguard= */false); - - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); - - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).setOneHandedMode(false); - } - - @Test - public void showSecurityScreen_oneHandedMode_deviceFlagDisabled_noOneHandedMode() { - setUpKeyguardFlags( - /* deviceConfigCanUseOneHandedKeyguard= */false, - /* sysuiResourceCanUseOneHandedKeyguard= */true); - - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); - - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).setOneHandedMode(false); - } - - @Test - public void showSecurityScreen_oneHandedMode_sysUiFlagDisabled_noOneHandedMode() { - setUpKeyguardFlags( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */false); - + public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() { + when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(false); when(mKeyguardSecurityViewFlipperController.getSecurityView( eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).setOneHandedMode(false); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); } @Test - public void showSecurityScreen_oneHandedMode_bothFlagsEnabled_oneHandedMode() { - setUpKeyguardFlags( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */true); - + public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() { + when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(true); when(mKeyguardSecurityViewFlipperController.getSecurityView( eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).setOneHandedMode(true); + verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings); } @Test - public void showSecurityScreen_twoHandedMode_bothFlagsEnabled_noOneHandedMode() { - setUpKeyguardFlags( - /* deviceConfigCanUseOneHandedKeyguard= */true, - /* sysuiResourceCanUseOneHandedKeyguard= */true); - + public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() { + when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(true); when(mKeyguardSecurityViewFlipperController.getSecurityView( eq(SecurityMode.Password), any(KeyguardSecurityCallback.class))) .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); - verify(mView).setOneHandedMode(false); - } - - private void setUpKeyguardFlags( - boolean deviceConfigCanUseOneHandedKeyguard, - boolean sysuiResourceCanUseOneHandedKeyguard) { - when(mResources.getBoolean( - com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) - .thenReturn(deviceConfigCanUseOneHandedKeyguard); - when(mResources.getBoolean( - R.bool.can_use_one_handed_bouncer)) - .thenReturn(sysuiResourceCanUseOneHandedKeyguard); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 2efd3697f633..c75108186f4e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -19,6 +19,9 @@ package com.android.keyguard; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; +import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -37,6 +40,7 @@ import android.widget.FrameLayout; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; import org.junit.Rule; @@ -59,9 +63,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Mock private WindowInsetsController mWindowInsetsController; - @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; + @Mock + private GlobalSettings mGlobalSettings; private KeyguardSecurityContainer mKeyguardSecurityContainer; @@ -83,7 +88,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() { - mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */true); + mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings); int halfWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY); @@ -94,7 +99,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void onMeasure_usesFullWidthWithOneHandedModeDisabled() { - mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); @@ -105,7 +110,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int imeInsetAmount = 100; int systemBarInsetAmount = 10; - mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -129,7 +134,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int imeInsetAmount = 0; int systemBarInsetAmount = 10; - mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -148,8 +153,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { } private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { - mKeyguardSecurityContainer.setOneHandedMode(oneHandedMode); - mKeyguardSecurityContainer.setOneHandedModeLeftAligned(true, false); + int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; + mKeyguardSecurityContainer.initMode(mode, mGlobalSettings); mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH); @@ -160,29 +165,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { } @Test - public void setIsLeftAligned_movesKeyguard() { + public void updatePosition_movesKeyguard() { setupForUpdateKeyguardPosition(/* oneHandedMode= */ true); + mKeyguardSecurityContainer.updatePositionByTouchX( + mKeyguardSecurityContainer.getWidth() - 1f); - mKeyguardSecurityContainer.setOneHandedModeLeftAligned( - /* leftAligned= */false, /* animate= */false); verify(mSecurityViewFlipper).setTranslationX( mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); - mKeyguardSecurityContainer.setOneHandedModeLeftAligned( - /* leftAligned= */true, /* animate= */false); + mKeyguardSecurityContainer.updatePositionByTouchX(1f); + verify(mSecurityViewFlipper).setTranslationX(0.0f); } @Test - public void setIsLeftAligned_doesntMoveTwoHandedKeyguard() { + public void updatePosition_doesntMoveTwoHandedKeyguard() { setupForUpdateKeyguardPosition(/* oneHandedMode= */ false); - mKeyguardSecurityContainer.setOneHandedModeLeftAligned( - /* leftAligned= */false, /* animate= */false); + mKeyguardSecurityContainer.updatePositionByTouchX( + mKeyguardSecurityContainer.getWidth() - 1f); verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); - mKeyguardSecurityContainer.setOneHandedModeLeftAligned( - /* leftAligned= */true, /* animate= */false); + mKeyguardSecurityContainer.updatePositionByTouchX(1f); verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index de8cc8992da0..ef9b850c99c2 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -1056,6 +1056,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(callback, atLeastOnce()).onRequireUnlockForNfc(); } + @Test + public void testFaceDoesNotAuth_afterPinAttempt() { + mTestableLooper.processAllMessages(); + mKeyguardUpdateMonitor.setCredentialAttempted(); + verify(mFingerprintManager, never()).authenticate(any(), any(), any(), + any(), anyInt()); + verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), + anyBoolean()); + } + private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt b/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt new file mode 100644 index 000000000000..6fbe3ada2406 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence + +/** + * Fake [InstanceId] generator. + */ +class InstanceIdSequenceFake(instanceIdMax: Int) : InstanceIdSequence(instanceIdMax) { + + /** + * Last id used to generate a [InstanceId]. `-1` if no [InstanceId] has been generated. + */ + var lastInstanceId = -1 + private set + + override fun newInstanceId(): InstanceId { + if (lastInstanceId == -1 || lastInstanceId == mInstanceIdMax - 1) { + lastInstanceId = 1 + } else { + lastInstanceId++ + } + return newInstanceIdInternal(lastInstanceId) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index 326d90260137..796af115bf68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -100,11 +100,11 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void enableWindowMagnification_passThrough() throws RemoteException { mIWindowMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, - Float.NaN, mAnimationCallback); + Float.NaN, 0f, 0f, mAnimationCallback); waitForIdleSync(); verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f), - eq(Float.NaN), eq(Float.NaN), eq(mAnimationCallback)); + eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java index 8bb9d423fa92..44770fab2d30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java @@ -110,7 +110,7 @@ public class TestableWindowManager implements WindowManager { } /** - * Sets the given window insets to the current window metics. + * Sets the given window insets to the current window metrics. * * @param insets the window insets. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index 854fc33d768d..3cc177dd8d91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -17,22 +17,27 @@ package com.android.systemui.accessibility; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Instrumentation; import android.content.Context; +import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; import android.testing.AndroidTestingRunner; import android.view.SurfaceControl; +import android.view.View; +import android.view.WindowManager; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.view.animation.AccelerateInterpolator; @@ -40,6 +45,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; @@ -56,7 +62,6 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.atomic.AtomicReference; - @Ignore @LargeTest @RunWith(AndroidTestingRunner.class) @@ -74,6 +79,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { private ArgumentCaptor<Float> mScaleCaptor = ArgumentCaptor.forClass(Float.class); private ArgumentCaptor<Float> mCenterXCaptor = ArgumentCaptor.forClass(Float.class); private ArgumentCaptor<Float> mCenterYCaptor = ArgumentCaptor.forClass(Float.class); + private final ArgumentCaptor<Float> mOffsetXCaptor = ArgumentCaptor.forClass(Float.class); + private final ArgumentCaptor<Float> mOffsetYCaptor = ArgumentCaptor.forClass(Float.class); @Mock Handler mHandler; @@ -94,10 +101,16 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { private long mWaitingAnimationPeriod; private long mWaitIntermediateAnimationPeriod; + private TestableWindowManager mWindowManager; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mInstrumentation = InstrumentationRegistry.getInstrumentation(); + final WindowManager wm = mContext.getSystemService(WindowManager.class); + mWindowManager = spy(new TestableWindowManager(wm)); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; mWaitIntermediateAnimationPeriod = ANIMATION_DURATION_MS / 2; mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( @@ -119,12 +132,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); - verify(mSpyController, atLeast(2)).enableWindowMagnification( + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, 1.0f); verifyStartValue(mCenterXCaptor, DEFAULT_CENTER_X); verifyStartValue(mCenterYCaptor, DEFAULT_CENTER_Y); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y); verify(mAnimationCallback).onResult(true); } @@ -162,8 +178,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { }); SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController).enableWindowMagnification(1, DEFAULT_CENTER_X, - DEFAULT_CENTER_Y); + verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X, + DEFAULT_CENTER_Y, 0f, 0f); verify(mAnimationCallback).onResult(true); } @@ -187,11 +203,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); @@ -213,11 +233,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); // It presents the window magnification is disabled. verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); @@ -256,7 +280,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { }); SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), + verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); @@ -286,9 +310,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { verify(mAnimationCallback).onResult(false); SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, atLeast(2)).enableWindowMagnification( + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); //Animating in reverse, so we only check if the start values are greater than current. assertTrue(mScaleCaptor.getAllValues().get(0) > mCurrentScale.get()); assertEquals(targetScale, mScaleCaptor.getValue(), 0f); @@ -336,7 +361,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { }); SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), + verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mSpyController, never()).deleteWindowMagnification(); verify(mAnimationCallback).onResult(false); @@ -362,23 +387,51 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, mCurrentScale.get()); verifyStartValue(mCenterXCaptor, mCurrentCenterX.get()); verifyStartValue(mCenterYCaptor, mCurrentCenterY.get()); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(targetScale, targetCenterX, targetCenterY); verify(mAnimationCallback2).onResult(true); } @Test + public void enableWindowMagnificationWithOffset_expectedValues() { + final float offsetRatio = -0.1f; + final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds()); + mInstrumentation.runOnMainSync(() -> { + Mockito.reset(mSpyController); + mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE, + windowBounds.exactCenterX(), windowBounds.exactCenterY(), + offsetRatio, offsetRatio, mAnimationCallback); + }); + SystemClock.sleep(mWaitingAnimationPeriod); + final View attachedView = mWindowManager.getAttachedView(); + assertNotNull(attachedView); + final Rect mirrorViewBound = new Rect(); + final View mirrorView = attachedView.findViewById(R.id.surface_view); + assertNotNull(mirrorView); + mirrorView.getBoundsOnScreen(mirrorViewBound); + + assertEquals(mirrorViewBound.exactCenterX() - windowBounds.exactCenterX(), + Math.round(offsetRatio * mirrorViewBound.width() / 2), 0.1f); + assertEquals(mirrorViewBound.exactCenterY() - windowBounds.exactCenterY(), + Math.round(offsetRatio * mirrorViewBound.height() / 2), 0.1f); + } + + @Test public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback() throws RemoteException { enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null); enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); - verify(mSpyController, never()).enableWindowMagnification(anyFloat(), anyFloat(), + verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(), anyFloat()); verify(mAnimationCallback).onResult(true); } @@ -390,11 +443,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); verifyStartValue(mScaleCaptor, DEFAULT_SCALE); verifyStartValue(mCenterXCaptor, Float.NaN); verifyStartValue(mCenterYCaptor, Float.NaN); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(true); } @@ -433,8 +490,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mCurrentCenterY.set(mController.getCenterY()); }); SystemClock.sleep(mWaitingAnimationPeriod); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); //The animation is in verse, so we only check the start values should no be greater than // the current one. @@ -442,6 +501,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { assertEquals(1.0f, mScaleCaptor.getValue(), 0f); verifyStartValue(mCenterXCaptor, Float.NaN); verifyStartValue(mCenterYCaptor, Float.NaN); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); @@ -471,9 +532,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2); - verify(mSpyController, atLeast(2)).enableWindowMagnification(mScaleCaptor.capture(), - mCenterXCaptor.capture(), mCenterYCaptor.capture()); + verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal( + mScaleCaptor.capture(), + mCenterXCaptor.capture(), mCenterYCaptor.capture(), + mOffsetXCaptor.capture(), mOffsetYCaptor.capture()); assertEquals(1.0f, mScaleCaptor.getValue(), 0f); + verifyStartValue(mOffsetXCaptor, 0f); + verifyStartValue(mOffsetYCaptor, 0f); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); verify(mAnimationCallback).onResult(false); verify(mAnimationCallback2).onResult(true); @@ -571,9 +636,18 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { } @Override - void enableWindowMagnification(float scale, float centerX, float centerY) { - super.enableWindowMagnification(scale, centerX, centerY); - mSpyController.enableWindowMagnification(scale, centerX, centerY); + void enableWindowMagnificationInternal(float scale, float centerX, float centerY) { + super.enableWindowMagnificationInternal(scale, centerX, centerY); + mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY); + } + + @Override + void enableWindowMagnificationInternal(float scale, float centerX, float centerY, + float magnificationOffsetFrameRatioX, float magnificationOffsetFrameRatioY) { + super.enableWindowMagnificationInternal(scale, centerX, centerY, + magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY); + mSpyController.enableWindowMagnificationInternal(scale, centerX, centerY, + magnificationOffsetFrameRatioX, magnificationOffsetFrameRatioY); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 9a3046554e0c..8fdcaddc93fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -146,7 +146,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnification_showControlAndNotifyBoundsChanged() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -159,7 +159,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnification_systemGestureExclusionRectsIsSet() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); // Wait for Rects updated. @@ -180,7 +180,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); mInstrumentation.runOnMainSync(() -> { - controller.enableWindowMagnification(Float.NaN, Float.NaN, + controller.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -195,7 +195,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void deleteWindowMagnification_destroyControl() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -213,7 +213,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { setSystemGestureInsets(); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, bounds.bottom); }); ReferenceTestUtils.waitForCondition(this::hasMagnificationOverlapFlag); @@ -229,7 +229,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void moveMagnifier_schedulesFrame() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); mWindowMagnificationController.moveWindowMagnifier(100f, 100f); }); @@ -246,8 +246,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { }).when(mHandler).postDelayed(any(Runnable.class), anyLong()); mInstrumentation.runOnMainSync( - () -> mWindowMagnificationController.enableWindowMagnification(2.0f, Float.NaN, - Float.NaN)); + () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f, + Float.NaN, Float.NaN)); mInstrumentation.runOnMainSync(() -> mWindowMagnificationController.setScale(3.0f)); @@ -283,8 +283,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final float displayWidth = windowBounds.width(); final PointF magnifiedCenter = new PointF(center, center + 5f); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, magnifiedCenter.x, - magnifiedCenter.y); + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, + magnifiedCenter.x, magnifiedCenter.y); // Get the center again in case the center we set is out of screen. magnifiedCenter.set(mWindowMagnificationController.getCenterX(), mWindowMagnificationController.getCenterY()); @@ -327,7 +327,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { testWindowBounds.set(testWindowBounds.left, testWindowBounds.top, testWindowBounds.right + 100, testWindowBounds.bottom + 100); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); mWindowManager.setWindowBounds(testWindowBounds); @@ -347,7 +347,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); final int screenSize = mContext.getResources().getDimensionPixelSize( @@ -369,7 +369,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void onDensityChanged_enabled_updateDimensionsAndResetWindowMagnification() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); Mockito.reset(mWindowManager); Mockito.reset(mMirrorWindowControl); @@ -398,7 +398,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void initializeA11yNode_enabled_expectedValues() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN, Float.NaN); }); final View mirrorView = mWindowManager.getAttachedView(); @@ -422,7 +422,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { public void performA11yActions_visible_expectedResults() { final int displayId = mContext.getDisplayId(); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN, Float.NaN); }); @@ -449,7 +449,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { public void performA11yActions_visible_notifyAccessibilityActionPerformed() { final int displayId = mContext.getDisplayId(); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(2.5f, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(2.5f, Float.NaN, Float.NaN); }); @@ -462,7 +462,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnification_hasA11yWindowTitle() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -473,12 +473,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnificationWithScaleLessThanOne_enabled_disabled() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(0.9f, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(0.9f, Float.NaN, Float.NaN); }); @@ -489,7 +489,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { public void onLocaleChanged_enabled_updateA11yWindowTitle() { final String newA11yWindowTitle = "new a11y window title"; mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); final TestableResources testableResources = getContext().getOrCreateTestableResources(); @@ -506,7 +506,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void onSingleTap_enabled_scaleIsChanged() { mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); @@ -530,7 +530,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds(); setSystemGestureInsets(); mInstrumentation.runOnMainSync(() -> { - mWindowMagnificationController.enableWindowMagnification(Float.NaN, Float.NaN, + mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index cdf40a154827..8ca17b974100 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -291,9 +291,12 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class)); mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class)); mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); + final AccessibilityFloatingMenuController controller = + new AccessibilityFloatingMenuController(mContextWrapper, mTargetsObserver, + mModeObserver, mKeyguardUpdateMonitor); + controller.init(); - return new AccessibilityFloatingMenuController(mContextWrapper, mTargetsObserver, - mModeObserver, mKeyguardUpdateMonitor); + return controller; } private void enableAccessibilityFloatingMenuConfig() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt index 58e0cb259bb2..3696ec540baf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt @@ -16,22 +16,53 @@ package com.android.systemui.animation +import android.graphics.drawable.Drawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.widget.LinearLayout +import android.view.View +import android.view.ViewGroup +import android.view.ViewParent import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() { + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var view: View + @Mock lateinit var rootView: ViewGroup + @Mock lateinit var viewParent: ViewParent + @Mock lateinit var drawable: Drawable + lateinit var controller: GhostedViewLaunchAnimatorController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(view.rootView).thenReturn(rootView) + whenever(view.background).thenReturn(drawable) + whenever(view.height).thenReturn(0) + whenever(view.width).thenReturn(0) + whenever(view.parent).thenReturn(viewParent) + whenever(view.visibility).thenReturn(View.VISIBLE) + whenever(view.invalidate()).then { /* NO-OP */ } + whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ } + whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true) + whenever(interactionJankMonitor.end(anyInt())).thenReturn(true) + controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor) + } + @Test fun animatingOrphanViewDoesNotCrash() { - val ghostedView = LinearLayout(mContext) - val controller = GhostedViewLaunchAnimatorController(ghostedView) val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0) controller.onIntentStarted(willAnimate = true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index 0e86964147d7..1cf21ac40e31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -351,9 +351,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { mSystemClock.advanceTime(205); mController.onTouchOutsideView(); - // THEN show the bouncer and reset alt auth + // THEN show the bouncer verify(mStatusBarKeyguardViewManager).showBouncer(eq(true)); - verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 3b1c5f32a772..bf5522c50a78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -54,6 +54,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.model.SysUiState; @@ -115,6 +116,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private UserContextProvider mUserContextProvider; @Mock private StatusBar mStatusBar; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private DialogLaunchAnimator mDialogLaunchAnimator; private TestableLooper mTestableLooper; @@ -159,8 +161,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mHandler, mPackageManager, Optional.of(mStatusBar), - mKeyguardUpdateMonitor - ); + mKeyguardUpdateMonitor, + mDialogLaunchAnimator); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); @@ -218,7 +220,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); - gestureListener.onSingleTapConfirmed(null); + gestureListener.onSingleTapUp(null); verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); } @@ -444,12 +446,12 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { // When entering power menu from lockscreen, with smart lock enabled when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); - mGlobalActionsDialogLite.showOrHideDialog(true, true); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); // Then smart lock will be disabled verify(mLockPatternUtils).requireCredentialEntry(eq(user)); // hide dialog again - mGlobalActionsDialogLite.showOrHideDialog(true, true); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 6d8645e44fb0..b774daf157c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -41,6 +41,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.IKeyguardDrawnCallback; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; @@ -64,9 +65,6 @@ import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; -import java.util.Optional; -import java.util.function.Function; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,6 +73,9 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; +import java.util.function.Function; + @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest @@ -103,6 +104,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback; + private @Mock InteractionJankMonitor mInteractionJankMonitor; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -121,6 +123,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .thenReturn(mUnfoldAnimationOptional); when(mUnfoldAnimationOptional.isPresent()).thenReturn(true); when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation); + when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true); + when(mInteractionJankMonitor.end(anyInt())).thenReturn(true); mViewMediator = new KeyguardViewMediator( mContext, @@ -144,7 +148,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mKeyguardStateController, () -> mKeyguardUnlockAnimationController, mUnlockedScreenOffAnimationController, - () -> mNotificationShadeDepthController); + () -> mNotificationShadeDepthController, + mInteractionJankMonitor); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 52173c13ca6e..1b5e5eba0c84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -121,7 +121,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTitleText.getText()).isEqualTo(mContext.getText( R.string.media_output_dialog_pairing_new)); } @@ -139,7 +138,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_SESSION_NAME); } @@ -156,7 +154,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(mContext.getString( R.string.media_output_dialog_group)); } @@ -165,14 +162,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { public void onBindViewHolder_bindConnectedDevice_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); - assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_1); } @Test @@ -199,7 +195,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2); } @@ -213,13 +208,10 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo( + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo( TEST_DEVICE_NAME_2); - assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo( - mContext.getString(R.string.media_output_dialog_disconnected)); + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -233,7 +225,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(mContext.getText( @@ -248,14 +239,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { LocalMediaManager.MediaDeviceState.STATE_CONNECTING); mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); - assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_1); + assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); } @Test @@ -268,7 +258,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_1); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 053851ec385d..4dac6d502d9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.graphics.drawable.Drawable; import android.media.session.MediaSessionManager; import android.os.Bundle; import android.testing.AndroidTestingRunner; @@ -70,6 +71,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { private MediaOutputController mMediaOutputController; private int mHeaderIconRes; private IconCompat mIconCompat; + private Drawable mAppSourceDrawable; private CharSequence mHeaderTitle; private CharSequence mHeaderSubtitle; @@ -173,6 +175,11 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { } @Override + Drawable getAppSourceIcon() { + return mAppSourceDrawable; + } + + @Override int getHeaderIconRes() { return mHeaderIconRes; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 09ec4ca0e1df..d71d98ee96d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -234,7 +235,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController.onRequestFailed(0 /* reason */); - verify(mCb).onRouteChanged(); + verify(mCb, atLeastOnce()).onRouteChanged(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java index ca5d570969c8..2c883a78e009 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java @@ -100,7 +100,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(mContext.getText( @@ -114,7 +113,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); @@ -140,7 +138,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); @@ -165,7 +162,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); @@ -183,7 +179,6 @@ public class MediaOutputGroupAdapterTest extends SysuiTestCase { assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mAddIcon.getVisibility()).isEqualTo(View.GONE); - assertThat(mGroupViewHolder.mBottomDivider.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt new file mode 100644 index 000000000000..efb493123a33 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.taptotransfer + +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.any +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.PrintWriter +import java.io.StringWriter +import java.util.concurrent.Executor + +@SmallTest +class MediaTttChipControllerTest : SysuiTestCase() { + + private lateinit var mediaTttChipController: MediaTttChipController + + private val inlineExecutor = Executor { command -> command.run() } + private val commandRegistry = CommandRegistry(context, inlineExecutor) + private val pw = PrintWriter(StringWriter()) + + @Mock + private lateinit var windowManager: WindowManager + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + mediaTttChipController = MediaTttChipController(context, commandRegistry, windowManager) + } + + @Test(expected = IllegalStateException::class) + fun constructor_addCommmandAlreadyRegistered() { + // Since creating the chip controller should automatically register the add command, it + // should throw when registering it again. + commandRegistry.registerCommand( + MediaTttChipController.ADD_CHIP_COMMAND_TAG + ) { EmptyCommand() } + } + + @Test(expected = IllegalStateException::class) + fun constructor_removeCommmandAlreadyRegistered() { + // Since creating the chip controller should automatically register the remove command, it + // should throw when registering it again. + commandRegistry.registerCommand( + MediaTttChipController.REMOVE_CHIP_COMMAND_TAG + ) { EmptyCommand() } + } + + @Test + fun addChipCommand_chipAdded() { + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG)) + + verify(windowManager).addView(any(), any()) + } + + @Test + fun addChipCommand_twice_chipNotAddedTwice() { + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG)) + reset(windowManager) + + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG)) + verify(windowManager, never()).addView(any(), any()) + } + + @Test + fun removeChipCommand_chipRemoved() { + // First, add the chip + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.ADD_CHIP_COMMAND_TAG)) + + // Then, remove it + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG)) + + verify(windowManager).removeView(any()) + } + + @Test + fun removeChipCommand_noAdd_viewNotRemoved() { + commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG)) + + verify(windowManager, never()).removeView(any()) + } + + class EmptyCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + } + + override fun help(pw: PrintWriter) { + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index 734faec4ec74..a445d6f4e5c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -18,6 +18,7 @@ package com.android.systemui.navigationbar; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,6 +35,7 @@ import com.android.systemui.assist.AssistManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import org.junit.Before; @@ -42,6 +44,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Optional; + import dagger.Lazy; @RunWith(AndroidJUnit4.class) @@ -80,10 +84,10 @@ public class NavBarHelperTest extends SysuiTestCase { when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent); when(mUserTracker.getUserId()).thenReturn(1); - mNavBarHelper = new NavBarHelper(mAccessibilityManager, + mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager, mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver, - mOverviewProxyService, mAssistManagerLazy, mNavigationModeController, - mUserTracker, mDumpManager); + mOverviewProxyService, mAssistManagerLazy, () -> Optional.of(mock(StatusBar.class)), + mNavigationModeController, mUserTracker, mDumpManager); } @@ -96,13 +100,13 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void registerAssistantContentObserver() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); } @Test public void callbacksFiredWhenRegistering() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); verify(mNavbarTaskbarStateUpdater, times(1)) .updateAccessibilityServicesState(); @@ -112,7 +116,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void assistantCallbacksFiredAfterConnecting() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); // 1st set of callbacks get called when registering mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -133,7 +137,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void a11yCallbacksFiredAfterModeChange() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); // 1st set of callbacks get called when registering mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -146,7 +150,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void assistantCallbacksFiredAfterNavModeChange() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); // 1st set of callbacks get called when registering mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -159,7 +163,7 @@ public class NavBarHelperTest extends SysuiTestCase { @Test public void removeListenerNoCallbacksFired() { - mNavBarHelper.init(mContext); + mNavBarHelper.init(); // 1st set of callbacks get called when registering mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 31d88303485c..9d2541c0150f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; +import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.After; @@ -86,7 +87,8 @@ public class NavigationBarControllerTest extends SysuiTestCase { mock(TaskbarDelegate.class), mNavigationBarFactory, mock(DumpManager.class), - mock(AutoHideController.class))); + mock(AutoHideController.class), + mock(LightBarController.class))); initializeNavigationBars(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index e038b6e6dfb3..5003013358be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -76,6 +76,7 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -90,6 +91,7 @@ import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -103,6 +105,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.util.Optional; @@ -135,7 +138,6 @@ public class NavigationBarTest extends SysuiTestCase { EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; @Mock EdgeBackGestureHandler mEdgeBackGestureHandler; - @Mock NavBarHelper mNavBarHelper; @Mock private LightBarController mLightBarController; @@ -179,6 +181,12 @@ public class NavigationBarTest extends SysuiTestCase { mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); TestableLooper.get(this).runWithLooper(() -> { + mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class), + mock(AccessibilityManagerWrapper.class), + mock(AccessibilityButtonModeObserver.class), mOverviewProxyService, + () -> mock(AssistManager.class), () -> Optional.of(mStatusBar), + mock(NavigationModeController.class), mock(UserTracker.class), + mock(DumpManager.class))); mNavigationBar = createNavBar(mContext); mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal); }); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index d2bba361b1f8..26f04fc4e7b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -1,6 +1,8 @@ package com.android.systemui.qs +import android.os.Handler import android.os.UserManager +import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -16,10 +18,12 @@ import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.FooterActionsController.ExpansionState +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.MultiUserSwitchController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.UserInfoController import com.android.systemui.tuner.TunerService +import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.leaks.FakeTunerService import com.android.systemui.utils.leaks.LeakCheckedTest import com.google.common.truth.Truth.assertThat @@ -42,6 +46,8 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userManager: UserManager @Mock + private lateinit var userTracker: UserTracker + @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @@ -62,11 +68,13 @@ class FooterActionsControllerTest : LeakCheckedTest() { private lateinit var view: FooterActionsView private val falsingManager: FalsingManagerFake = FalsingManagerFake() private lateinit var testableLooper: TestableLooper + private lateinit var fakeSettings: FakeSettings @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + fakeSettings = FakeSettings() injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES) val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService @@ -74,10 +82,11 @@ class FooterActionsControllerTest : LeakCheckedTest() { .inflate(R.layout.footer_actions, null) as FooterActionsView controller = FooterActionsController(view, qsPanelController, activityStarter, - userManager, userInfoController, multiUserSwitchController, + userManager, userTracker, userInfoController, multiUserSwitchController, deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, globalActionsDialog, uiEventLogger, showPMLiteButton = true, - buttonsVisibleState = ExpansionState.EXPANDED) + buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings, + Handler(testableLooper.looper)) controller.init() ViewUtils.attachView(view) // View looper is the testable looper associated with the test @@ -122,4 +131,24 @@ class FooterActionsControllerTest : LeakCheckedTest() { assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) } + + @Test + fun testMultiUserSwitchUpdatedWhenSettingChanged() { + // When expanded, listening is true + controller.setListening(true) + testableLooper.processAllMessages() + + val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) + assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) + + // The setting is only used as an indicator for whether the view should refresh. The actual + // value of the setting is ignored; isMultiUserEnabled is the source of truth + whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) + + // Changing the value of USER_SWITCHER_ENABLED should cause the view to update + fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId) + testableLooper.processAllMessages() + + assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt new file mode 100644 index 000000000000..64796f1a757a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.StatusBarManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.InstanceIdSequenceFake +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class TileRequestDialogEventLoggerTest : SysuiTestCase() { + + companion object { + private const val PACKAGE_NAME = "package" + } + + private lateinit var uiEventLogger: UiEventLoggerFake + private val instanceIdSequence = + InstanceIdSequenceFake(TileRequestDialogEventLogger.MAX_INSTANCE_ID) + private lateinit var logger: TileRequestDialogEventLogger + + @Before + fun setUp() { + uiEventLogger = UiEventLoggerFake() + + logger = TileRequestDialogEventLogger(uiEventLogger, instanceIdSequence) + } + + @Test + fun testInstanceIdsFromSequence() { + (1..10).forEach { + assertThat(logger.newInstanceId().id).isEqualTo(instanceIdSequence.lastInstanceId) + } + } + + @Test + fun testLogTileAlreadyAdded() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logTileAlreadyAdded(PACKAGE_NAME, instanceId) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match( + TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ALREADY_ADDED, + instanceId + ) + } + + @Test + fun testLogDialogShown() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logDialogShown(PACKAGE_NAME, instanceId) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_SHOWN, instanceId) + } + + @Test + fun testLogDialogDismissed() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED, + PACKAGE_NAME, + instanceId + ) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_DISMISSED, instanceId) + } + + @Test + fun testLogDialogTileNotAdded() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED, + PACKAGE_NAME, + instanceId + ) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0] + .match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_NOT_ADDED, instanceId) + } + + @Test + fun testLogDialogTileAdded() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, + PACKAGE_NAME, + instanceId + ) + + assertThat(uiEventLogger.numLogs()).isEqualTo(1) + uiEventLogger[0].match(TileRequestDialogEvent.TILE_REQUEST_DIALOG_TILE_ADDED, instanceId) + } + + @Test(expected = IllegalArgumentException::class) + fun testLogResponseInvalid_throws() { + val instanceId = instanceIdSequence.newInstanceId() + logger.logUserResponse( + -1, + PACKAGE_NAME, + instanceId + ) + } + + private fun UiEventLoggerFake.FakeUiEvent.match( + event: UiEventLogger.UiEventEnum, + instanceId: InstanceId + ) { + assertThat(eventId).isEqualTo(event.id) + assertThat(uid).isEqualTo(0) + assertThat(packageName).isEqualTo(PACKAGE_NAME) + assertThat(this.instanceId).isEqualTo(instanceId) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt index d49673d0f572..f56a185a19a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt @@ -60,11 +60,6 @@ class TileRequestDialogTest : SysuiTestCase() { } @Test - fun useCorrectTheme() { - assertThat(dialog.context.themeResId).isEqualTo(R.style.TileRequestDialog) - } - - @Test fun setTileData_hasCorrectViews() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt index 70e971cdbf32..a1c60a648de9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -16,18 +16,22 @@ package com.android.systemui.qs.external +import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId import com.android.internal.statusbar.IAddTileResultCallback +import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.SysuiTestCase import com.android.systemui.qs.QSTileHost import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -63,18 +67,28 @@ class TileServiceRequestControllerTest : SysuiTestCase() { @Mock private lateinit var commandQueue: CommandQueue @Mock + private lateinit var logger: TileRequestDialogEventLogger + @Mock private lateinit var icon: Icon + private val instanceIdSequence = InstanceIdSequenceFake(1_000) private lateinit var controller: TileServiceRequestController @Before fun setUp() { MockitoAnnotations.initMocks(this) + `when`(logger.newInstanceId()).thenReturn(instanceIdSequence.newInstanceId()) + // Tile not present by default `when`(qsTileHost.indexOf(anyString())).thenReturn(-1) - controller = TileServiceRequestController(qsTileHost, commandQueue, commandRegistry) { + controller = TileServiceRequestController( + qsTileHost, + commandQueue, + commandRegistry, + logger + ) { tileRequestDialog } @@ -102,6 +116,17 @@ class TileServiceRequestControllerTest : SysuiTestCase() { } @Test + fun tileAlreadyAdded_logged() { + `when`(qsTileHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) + + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + + verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any()) + verify(logger, never()).logDialogShown(anyString(), any()) + verify(logger, never()).logUserResponse(anyInt(), anyString(), any()) + } + + @Test fun showAllUsers_set() { controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) verify(tileRequestDialog).setShowForAllUsers(true) @@ -114,6 +139,13 @@ class TileServiceRequestControllerTest : SysuiTestCase() { } @Test + fun dialogShown_logged() { + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + + verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any()) + } + + @Test fun cancelListener_dismissResult() { val cancelListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) @@ -128,6 +160,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() { } @Test + fun dialogCancelled_logged() { + val cancelListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) + + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) + + verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) + verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) + + cancelListenerCaptor.value.onCancel(tileRequestDialog) + verify(logger).logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED, + TEST_COMPONENT.packageName, + instanceId + ) + } + + @Test fun positiveActionListener_tileAddedResult() { val clickListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) @@ -143,6 +194,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() { } @Test + fun tileAdded_logged() { + val clickListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) + + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) + + verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) + verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) + + clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) + verify(logger).logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, + TEST_COMPONENT.packageName, + instanceId + ) + } + + @Test fun negativeActionListener_tileNotAddedResult() { val clickListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) @@ -158,6 +228,25 @@ class TileServiceRequestControllerTest : SysuiTestCase() { } @Test + fun tileNotAdded_logged() { + val clickListenerCaptor = + ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) + + controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) + + verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor)) + verify(logger).logDialogShown(TEST_COMPONENT.packageName, instanceId) + + clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE) + verify(logger).logUserResponse( + StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED, + TEST_COMPONENT.packageName, + instanceId + ) + } + + @Test fun commandQueueCallback_registered() { verify(commandQueue).addCallback(any()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index f99703e2415d..3ea2cc582ba3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -16,22 +16,28 @@ package com.android.systemui.qs.tiles +import android.app.Dialog import android.content.ContextWrapper import android.content.SharedPreferences import android.os.Handler import android.provider.Settings +import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat @@ -40,9 +46,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.io.File +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -70,6 +79,10 @@ class DndTileTest : SysuiTestCase() { private lateinit var zenModeController: ZenModeController @Mock private lateinit var sharedPreferences: SharedPreferences + @Mock + private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock + private lateinit var hostDialog: Dialog private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper @@ -81,15 +94,17 @@ class DndTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(qsHost.userId).thenReturn(DEFAULT_USER) + whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) + whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean())) + .thenReturn(hostDialog) val wrappedContext = object : ContextWrapper(context) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { return sharedPreferences } } - Mockito.`when`(qsHost.context).thenReturn(wrappedContext) + whenever(qsHost.context).thenReturn(wrappedContext) tile = DndTile( qsHost, @@ -102,7 +117,8 @@ class DndTileTest : SysuiTestCase() { qsLogger, zenModeController, sharedPreferences, - secureSettings + secureSettings, + dialogLaunchAnimator ) } @@ -147,4 +163,32 @@ class DndTileTest : SysuiTestCase() { assertThat(tile.state.forceExpandIcon).isTrue() } + + @Test + fun testLaunchDialogFromViewWhenPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean()) + } + + @Test + fun testNoLaunchDialogWhenNotPrompt() { + whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF) + + secureSettings.putIntForUser(KEY, 60, DEFAULT_USER) + testableLooper.processAllMessages() + + val view = View(context) + tile.handleClick(view) + testableLooper.processAllMessages() + + verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean()) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index b32b4d4f3810..339d5bb04d74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -1,5 +1,7 @@ package com.android.systemui.qs.tiles.dialog; +import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -219,7 +221,7 @@ public class InternetDialogTest extends SysuiTestCase { } @Test - public void updateDialog_wifiOnAndNoWifiEntry_hideWifiEntryAndSeeAll() { + public void updateDialog_wifiOnAndNoWifiEntry_showWifiListAndSeeAllArea() { // The precondition WiFi ON is already in setUp() mInternetDialog.mConnectedWifiEntry = null; mInternetDialog.mWifiEntriesCount = 0; @@ -227,19 +229,21 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); - assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE); - assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); } @Test - public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifiAndSeeAll() { + public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mWifiEntriesCount = 0; mInternetDialog.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE); } @@ -412,4 +416,54 @@ public class InternetDialogTest extends SysuiTestCase { assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); assertThat(mInternetDialog.mIsSearchingHidden).isTrue(); } + + @Test + public void getWifiListMaxCount_returnCountCorrectly() { + // Ethernet, MobileData, ConnectedWiFi are all hidden. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT. + setNetworkVisible(false, false, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); + + // Only one of Ethernet, MobileData, ConnectedWiFi is displayed. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 1. + setNetworkVisible(true, false, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + setNetworkVisible(false, true, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + setNetworkVisible(false, false, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + // Only one of Ethernet, MobileData, ConnectedWiFi is hidden. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 2. + setNetworkVisible(true, true, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + setNetworkVisible(true, false, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + setNetworkVisible(false, true, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + // Ethernet, MobileData, ConnectedWiFi are all displayed. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 3. + setNetworkVisible(true, true, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 3); + } + + private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible, + boolean connectedWifiVisible) { + mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE); + mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE); + mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt deleted file mode 100644 index d5fe588b2115..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.user - -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.View -import android.view.ViewGroup -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper -class UserDialogTest : SysuiTestCase() { - - private lateinit var dialog: UserDialog - - @Before - fun setUp() { - dialog = UserDialog(mContext) - } - - @After - fun tearDown() { - dialog.dismiss() - } - - @Test - fun doneButtonExists() { - assertThat(dialog.doneButton).isInstanceOf(View::class.java) - } - - @Test - fun settingsButtonExists() { - assertThat(dialog.settingsButton).isInstanceOf(View::class.java) - } - - @Test - fun gridExistsAndIsViewGroup() { - assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java) - } -}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index ea3a42ce501c..3c4a557eac10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.user import android.app.Dialog +import android.content.DialogInterface import android.content.Intent import android.provider.Settings import android.testing.AndroidTestingRunner @@ -28,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PseudoGridView import com.android.systemui.qs.tiles.UserDetailView +import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -43,7 +45,6 @@ import org.mockito.Mockito.`when` import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.argThat -import org.mockito.Mockito.inOrder import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -53,27 +54,21 @@ import org.mockito.MockitoAnnotations class UserSwitchDialogControllerTest : SysuiTestCase() { @Mock - private lateinit var dialog: UserDialog + private lateinit var dialog: SystemUIDialog @Mock private lateinit var falsingManager: FalsingManager @Mock - private lateinit var settingsView: View - @Mock - private lateinit var doneView: View - @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var userDetailViewAdapter: UserDetailView.Adapter @Mock private lateinit var launchView: View @Mock - private lateinit var gridView: PseudoGridView - @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator @Mock private lateinit var hostDialog: Dialog @Captor - private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener> + private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener> private lateinit var controller: UserSwitchDialogController @@ -81,11 +76,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(dialog.settingsButton).thenReturn(settingsView) - `when`(dialog.doneButton).thenReturn(doneView) - `when`(dialog.grid).thenReturn(gridView) - `when`(launchView.context).thenReturn(mContext) + `when`(dialog.context).thenReturn(mContext) `when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean())) .thenReturn(hostDialog) @@ -105,30 +97,6 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { } @Test - fun createCalledBeforeDoneButton() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).doneButton - } - - @Test - fun createCalledBeforeSettingsButton() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).settingsButton - } - - @Test - fun createCalledBeforeGrid() { - controller.showDialog(launchView) - val inOrder = inOrder(dialog) - inOrder.verify(dialog).create() - inOrder.verify(dialog).grid - } - - @Test fun dialog_showForAllUsers() { controller.showDialog(launchView) verify(dialog).setShowForAllUsers(true) @@ -143,51 +111,44 @@ class UserSwitchDialogControllerTest : SysuiTestCase() { @Test fun adapterAndGridLinked() { controller.showDialog(launchView) - verify(userDetailViewAdapter).linkToViewGroup(gridView) + verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>()) } @Test - fun clickDoneButton_dismiss() { + fun doneButtonSetWithNullHandler() { controller.showDialog(launchView) - verify(doneView).setOnClickListener(capture(clickCaptor)) - - clickCaptor.value.onClick(doneView) - - verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) - verify(dialog).dismiss() + verify(dialog).setPositiveButton(anyInt(), eq(null)) } @Test - fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() { + fun clickSettingsButton_noFalsing_opensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false) controller.showDialog(launchView) - verify(settingsView).setOnClickListener(capture(clickCaptor)) + verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) - clickCaptor.value.onClick(settingsView) + clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter) .postStartActivityDismissingKeyguard( argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)), eq(0) ) - verify(dialog).dismiss() } @Test - fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() { + fun clickSettingsButton_Falsing_notOpensSettings() { `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true) controller.showDialog(launchView) - verify(settingsView).setOnClickListener(capture(clickCaptor)) + verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor)) - clickCaptor.value.onClick(settingsView) + clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL) verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt()) - verify(dialog).dismiss() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index b0f2a8902870..4213b070d0c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java @@ -29,6 +29,7 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; @@ -54,6 +55,7 @@ import org.mockito.MockitoAnnotations; @TestableLooper.RunWithLooper(setAsMainLooper = true) public class NonPhoneDependencyTest extends SysuiTestCase { @Mock private NotificationPresenter mPresenter; + @Mock private NotifStackController mStackController; @Mock private NotificationListContainer mListContainer; @Mock private NotificationEntryListener mEntryListener; @@ -95,7 +97,7 @@ public class NonPhoneDependencyTest extends SysuiTestCase { remoteInputManager.setUpWithCallback(mRemoteInputManagerCallback, mDelegate); lockscreenUserManager.setUpWithPresenter(mPresenter); - viewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer); + viewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer); TestableLooper.get(this).processAllMessages(); assertFalse(mDependency.hasInstantiatedDependency(NotificationShadeWindowController.class)); 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 3972f140b665..83f1d87133c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; +import com.android.systemui.statusbar.notification.collection.render.NotifStackController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -74,6 +75,7 @@ import java.util.Optional; @TestableLooper.RunWithLooper public class NotificationViewHierarchyManagerTest extends SysuiTestCase { @Mock private NotificationPresenter mPresenter; + @Mock private NotifStackController mStackController; @Spy private FakeListContainer mListContainer = new FakeListContainer(); // Dependency mocks: @@ -122,7 +124,7 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mock(LowPriorityInflationHelper.class), mock(AssistantFeedbackController.class), mNotifPipelineFlags); - mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer); + mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer); } private NotificationEntry createEntry() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index c974882ff305..b736f389ad6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -26,20 +27,35 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @Before fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true) + whenever(interactionJankMonitor.end(anyInt())).thenReturn(true) + uiEventLogger = UiEventLoggerFake() - controller = StatusBarStateControllerImpl(uiEventLogger, mock(DumpManager::class.java)) + controller = StatusBarStateControllerImpl( + uiEventLogger, + mock(DumpManager::class.java), + interactionJankMonitor + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 8e6bcb01c2be..41163bf433c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -174,6 +174,41 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testGetGroupSummary() { + assertEquals(null, mCollection.getGroupSummary("group")); + NotifEvent summary = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, "group") + .setGroupSummary(mContext, true)); + + final NotificationEntry entry = mCollection.getGroupSummary("group"); + assertEquals(summary.key, entry.getKey()); + assertEquals(summary.sbn, entry.getSbn()); + assertEquals(summary.ranking, entry.getRanking()); + } + + @Test + public void testIsOnlyChildInGroup() { + NotifEvent notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, "group")); + final NotificationEntry entry = mCollection.getEntry(notif1.key); + assertTrue(mCollection.isOnlyChildInGroup(entry)); + + // summaries are not counted + mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 0) + .setGroup(mContext, "group") + .setGroupSummary(mContext, true)); + assertTrue(mCollection.isOnlyChildInGroup(entry)); + + mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, "group")); + assertFalse(mCollection.isOnlyChildInGroup(entry)); + } + + @Test public void testEventDispatchedWhenNotifPosted() { // WHEN a notification is posted NotifEvent notif1 = mNoMan.postNotif( @@ -193,6 +228,15 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testCancelNonExistingNotification() { + NotifEvent notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + mCollection.dismissNotification(entry, defaultStats(entry)); + mCollection.dismissNotification(entry, defaultStats(entry)); + mCollection.dismissNotification(entry, defaultStats(entry)); + } + + @Test public void testEventDispatchedWhenNotifBatchPosted() { // GIVEN a NotifCollection with one notif already posted mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2) @@ -649,21 +693,6 @@ public class NotifCollectionTest extends SysuiTestCase { // THEN an exception is thrown } - @Test(expected = IllegalStateException.class) - public void testDismissingNonExistentNotificationThrows() { - // GIVEN a collection that originally had three notifs, but where one was dismissed - NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); - NotifEvent notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); - NotifEvent notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99)); - NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); - mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); - - // WHEN we try to dismiss a notification that isn't present - mCollection.dismissNotification(entry2, defaultStats(entry2)); - - // THEN an exception is thrown - } - @Test public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() { // GIVEN a collection with two grouped notifs in it diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt index cf7174eea7cf..287f50c4202a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.render.RenderStageManager import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -33,12 +34,13 @@ class NotifPipelineTest : SysuiTestCase() { @Mock private lateinit var notifCollection: NotifCollection @Mock private lateinit var shadeListBuilder: ShadeListBuilder + @Mock private lateinit var renderStageManager: RenderStageManager private lateinit var notifPipeline: NotifPipeline @Before fun setup() { MockitoAnnotations.initMocks(this) - notifPipeline = NotifPipeline(notifCollection, shadeListBuilder) + notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager) whenever(shadeListBuilder.shadeList).thenReturn(listOf( NotificationEntryBuilder().setPkg("foo").setId(1).build(), NotificationEntryBuilder().setPkg("foo").setId(2).build(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index b254ed4e3f2e..82cd9fa3ac8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -32,7 +32,6 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -52,6 +51,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.statusbar.NotificationInteractionTracker; +import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; @@ -96,7 +96,9 @@ public class ShadeListBuilderTest extends SysuiTestCase { private ShadeListBuilder mListBuilder; private FakeSystemClock mSystemClock = new FakeSystemClock(); + @Mock private NotifPipelineFlags mNotifPipelineFlags; @Mock private ShadeListBuilderLogger mLogger; + @Mock private DumpManager mDumpManager; @Mock private NotifCollection mNotifCollection; @Mock private NotificationInteractionTracker mInteractionTracker; @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener; @@ -122,7 +124,12 @@ public class ShadeListBuilderTest extends SysuiTestCase { allowTestableLooperAsMainThread(); mListBuilder = new ShadeListBuilder( - mSystemClock, mLogger, mock(DumpManager.class), mInteractionTracker); + mSystemClock, + mNotifPipelineFlags, + mLogger, + mDumpManager, + mInteractionTracker + ); mListBuilder.setOnRenderListListener(mOnRenderListListener); mListBuilder.attach(mNotifCollection); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt new file mode 100644 index 000000000000..929c3d4288d4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener +import com.android.systemui.statusbar.notification.collection.render.NotifGroupController +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class GroupCountCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: GroupCountCoordinator + private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener + private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener + + private lateinit var summaryEntry: NotificationEntry + private lateinit var childEntry1: NotificationEntry + private lateinit var childEntry2: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var groupController: NotifGroupController + + @Before + fun setUp() { + initMocks(this) + coordinator = GroupCountCoordinator() + coordinator.attach(pipeline) + beforeFinalizeFilterListener = withArgCaptor { + verify(pipeline).addOnBeforeFinalizeFilterListener(capture()) + } + afterRenderGroupListener = withArgCaptor { + verify(pipeline).addOnAfterRenderGroupListener(capture()) + } + summaryEntry = NotificationEntryBuilder().setId(0).build() + childEntry1 = NotificationEntryBuilder().setId(1).build() + childEntry2 = NotificationEntryBuilder().setId(2).build() + } + + @Test + fun testSetUntruncatedChildCount() { + val groupEntry = GroupEntryBuilder() + .setSummary(summaryEntry) + .setChildren(listOf(childEntry1, childEntry2)) + .build() + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController) + verify(groupController).setUntruncatedChildCount(eq(2)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index c3e10aa3178f..f70330dbe506 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; @@ -85,7 +86,6 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor; @Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor; - @Captor private ArgumentCaptor<NotifInflater.InflationCallback> mCallbackCaptor; @Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor; @Mock private NotifSectioner mNotifSectioner; @@ -93,7 +93,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); - private final TestableAdjustmentProvider mAdjustmentProvider = new TestableAdjustmentProvider(); + private final SectionClassifier mSectionClassifier = new SectionClassifier(); + private final NotifUiAdjustmentProvider mAdjustmentProvider = + new NotifUiAdjustmentProvider(mSectionClassifier); @NonNull private NotificationEntryBuilder getNotificationEntryBuilder() { @@ -108,7 +110,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mInflationError = new Exception(TEST_MESSAGE); mErrorManager = new NotifInflationErrorManager(); when(mNotifSection.getSectioner()).thenReturn(mNotifSectioner); - mAdjustmentProvider.setSectionIsLowPriority(false); + setSectionIsLowPriority(false); PreparationCoordinator coordinator = new PreparationCoordinator( mock(PreparationCoordinatorLogger.class), @@ -180,8 +182,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification is updated mCollectionListener.onEntryUpdated(mEntry); @@ -199,8 +201,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification ranking now has smart replies mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setSmartReplies("yes").build()); @@ -218,13 +220,12 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), - mParamsCaptor.capture(), mCallbackCaptor.capture()); + verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); assertFalse(mParamsCaptor.getValue().isLowPriority()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification moves to a min priority section - mAdjustmentProvider.setSectionIsLowPriority(true); + setSectionIsLowPriority(true); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); // THEN we rebind it @@ -238,13 +239,12 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Test public void testMinimizedEntryMovedIntoGroupWillRebindViews() { // GIVEN an inflated, minimized notification - mAdjustmentProvider.setSectionIsLowPriority(true); + setSectionIsLowPriority(true); mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), - mParamsCaptor.capture(), mCallbackCaptor.capture()); + verify(mNotifInflater).inflateViews(eq(mEntry), mParamsCaptor.capture(), any()); assertTrue(mParamsCaptor.getValue().isLowPriority()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification is moved under a parent NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class)); @@ -263,8 +263,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // WHEN notification ranking changes rank, which does not affect views mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setRank(100).build()); @@ -282,8 +282,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); - mCallbackCaptor.getValue().onInflationFinished(mEntry); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); + mNotifInflater.invokeInflateCallbackForEntry(mEntry); // THEN it isn't filtered from shade list assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); @@ -347,7 +347,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); // WHEN one of this children finishes inflating - mNotifInflater.getInflateCallback(child0).onInflationFinished(child0); + mNotifInflater.invokeInflateCallbackForEntry(child0); // THEN the inflated child is still filtered out assertTrue(mUninflatedFilter.shouldFilterOut(child0, 401)); @@ -369,8 +369,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); // WHEN all of the children (but not the summary) finish inflating - mNotifInflater.getInflateCallback(child0).onInflationFinished(child0); - mNotifInflater.getInflateCallback(child1).onInflationFinished(child1); + mNotifInflater.invokeInflateCallbackForEntry(child0); + mNotifInflater.invokeInflateCallbackForEntry(child1); // THEN the entire group is still filtered out assertTrue(mUninflatedFilter.shouldFilterOut(summary, 401)); @@ -394,9 +394,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); // WHEN all of the children (and the summary) finish inflating - mNotifInflater.getInflateCallback(child0).onInflationFinished(child0); - mNotifInflater.getInflateCallback(child1).onInflationFinished(child1); - mNotifInflater.getInflateCallback(summary).onInflationFinished(summary); + mNotifInflater.invokeInflateCallbackForEntry(child0); + mNotifInflater.invokeInflateCallbackForEntry(child1); + mNotifInflater.invokeInflateCallbackForEntry(summary); // THEN the entire group is still filtered out assertFalse(mUninflatedFilter.shouldFilterOut(summary, 401)); @@ -418,7 +418,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(group)); // WHEN one of this children finishes inflating and enough time passes - mNotifInflater.getInflateCallback(child0).onInflationFinished(child0); + mNotifInflater.invokeInflateCallbackForEntry(child0); // THEN the inflated child is not filtered out even though the rest of the group hasn't // finished inflating yet @@ -446,6 +446,10 @@ public class PreparationCoordinatorTest extends SysuiTestCase { public InflationCallback getInflateCallback(NotificationEntry entry) { return requireNonNull(mInflateCallbacks.get(entry)); } + + public void invokeInflateCallbackForEntry(NotificationEntry entry) { + getInflateCallback(entry).onInflationFinished(entry, entry.getRowController()); + } } private void fireAddEvents(List<? extends ListEntry> entries) { @@ -470,11 +474,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { private static final int TEST_CHILD_BIND_CUTOFF = 9; private static final int TEST_MAX_GROUP_DELAY = 100; - private class TestableAdjustmentProvider extends NotifUiAdjustmentProvider { - private void setSectionIsLowPriority(boolean lowPriority) { - setLowPrioritySections(lowPriority - ? Collections.singleton(mNotifSection.getSectioner()) - : Collections.emptyList()); - } + private void setSectionIsLowPriority(boolean minimized) { + mSectionClassifier.setMinimizedSections(minimized + ? Collections.singleton(mNotifSection.getSectioner()) + : Collections.emptyList()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index abe33aae7fc6..f4d8405a796e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICAT import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -39,11 +40,11 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; @@ -67,7 +68,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private NotifUiAdjustmentProvider mAdjustmentProvider; + @Mock private SectionClassifier mSectionClassifier; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; @Mock private NodeController mSilentNodeController; @@ -91,7 +92,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { mRankingCoordinator = new RankingCoordinator( mStatusBarStateController, mHighPriorityProvider, - mAdjustmentProvider, + mSectionClassifier, mAlertingHeaderController, mSilentHeaderController, mSilentNodeController); @@ -99,6 +100,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry.setRanking(getRankingForUnfilteredNotif().build()); mRankingCoordinator.attach(mNotifPipeline); + verify(mSectionClassifier).setMinimizedSections(any()); verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt new file mode 100644 index 000000000000..52fce130fe69 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.util.Pair +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.AssistantFeedbackController +import com.android.systemui.statusbar.notification.SectionClassifier +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener +import com.android.systemui.statusbar.notification.collection.render.NotifRowController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class RowAppearanceCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: RowAppearanceCoordinator + private lateinit var beforeRenderListListener: OnBeforeRenderListListener + private lateinit var afterRenderEntryListener: OnAfterRenderEntryListener + + private lateinit var entry1: NotificationEntry + private lateinit var entry2: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController + @Mock private lateinit var sectionClassifier: SectionClassifier + + @Mock private lateinit var section1: NotifSection + @Mock private lateinit var section2: NotifSection + @Mock private lateinit var controller1: NotifRowController + @Mock private lateinit var controller2: NotifRowController + + @Before + fun setUp() { + initMocks(this) + coordinator = RowAppearanceCoordinator( + mContext, + assistantFeedbackController, + sectionClassifier + ) + coordinator.attach(pipeline) + beforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + afterRenderEntryListener = withArgCaptor { + verify(pipeline).addOnAfterRenderEntryListener(capture()) + } + whenever(assistantFeedbackController.showFeedbackIndicator(any())).thenReturn(true) + whenever(assistantFeedbackController.getFeedbackResources(any())).thenReturn(Pair(1, 2)) + entry1 = NotificationEntryBuilder().setSection(section1).setLastAudiblyAlertedMs(17).build() + entry2 = NotificationEntryBuilder().setSection(section2).build() + } + + @Test + fun testSetSystemExpandedOnlyOnFirst() { + whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false) + whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false) + beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2)) + afterRenderEntryListener.onAfterRenderEntry(entry1, controller1) + verify(controller1).setSystemExpanded(eq(true)) + afterRenderEntryListener.onAfterRenderEntry(entry2, controller2) + verify(controller2).setSystemExpanded(eq(false)) + } + + @Test + fun testSetSystemExpandedNeverIfMinimized() { + whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true) + whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true) + beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2)) + afterRenderEntryListener.onAfterRenderEntry(entry1, controller1) + verify(controller1).setSystemExpanded(eq(false)) + afterRenderEntryListener.onAfterRenderEntry(entry2, controller2) + verify(controller2).setSystemExpanded(eq(false)) + } + + @Test + fun testSetLastAudiblyAlerted() { + afterRenderEntryListener.onAfterRenderEntry(entry1, controller1) + verify(controller1).setLastAudiblyAlertedMs(eq(17.toLong())) + } + + @Test + fun testShowFeedbackIcon() { + afterRenderEntryListener.onAfterRenderEntry(entry1, controller1) + verify(controller1).showFeedbackIcon(eq(true), eq(Pair(1, 2))) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt new file mode 100644 index 000000000000..70266e401f8a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.statusbar.notification.collection.render.NotifStackController +import com.android.systemui.statusbar.notification.collection.render.NotifStats +import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING +import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT +import com.android.systemui.statusbar.phone.NotificationIconAreaController +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.withArgCaptor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations.initMocks +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class StackCoordinatorTest : SysuiTestCase() { + private lateinit var coordinator: StackCoordinator + private lateinit var afterRenderListListener: OnAfterRenderListListener + + private lateinit var entry: NotificationEntry + + @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var notificationIconAreaController: NotificationIconAreaController + @Mock private lateinit var stackController: NotifStackController + @Mock private lateinit var section: NotifSection + + @Before + fun setUp() { + initMocks(this) + coordinator = StackCoordinator(notificationIconAreaController) + coordinator.attach(pipeline) + afterRenderListListener = withArgCaptor { + verify(pipeline).addOnAfterRenderListListener(capture()) + } + entry = NotificationEntryBuilder().setSection(section).build() + } + + @Test + fun testUpdateNotificationIcons() { + afterRenderListListener.onAfterRenderList(listOf(entry), stackController) + verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry))) + } + + @Test + fun testSetNotificationStats_clearableAlerting() { + whenever(section.bucket).thenReturn(BUCKET_ALERTING) + afterRenderListListener.onAfterRenderList(listOf(entry), stackController) + verify(stackController).setNotifStats(NotifStats(1, false, true, false, false)) + } + + @Test + fun testSetNotificationStats_clearableSilent() { + whenever(section.bucket).thenReturn(BUCKET_SILENT) + afterRenderListListener.onAfterRenderList(listOf(entry), stackController) + verify(stackController).setNotifStats(NotifStats(1, false, false, false, true)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index 6313d3a4c2fa..5271745a2b44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -68,7 +68,7 @@ class NodeSpecBuilderTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - `when`(viewBarn.requireView(any())).thenAnswer { + `when`(viewBarn.requireNodeController(any())).thenAnswer { fakeViewBarn.getViewByEntry(it.getArgument(0)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt new file mode 100644 index 000000000000..70d309b10338 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.render + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder +import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.ShadeListBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@SmallTest +class RenderStageManagerTest : SysuiTestCase() { + + @Mock private lateinit var shadeListBuilder: ShadeListBuilder + @Mock private lateinit var onAfterRenderListListener: OnAfterRenderListListener + @Mock private lateinit var onAfterRenderGroupListener: OnAfterRenderGroupListener + @Mock private lateinit var onAfterRenderEntryListener: OnAfterRenderEntryListener + + private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener + private lateinit var renderStageManager: RenderStageManager + private val spyViewRenderer = spy(FakeNotifViewRenderer()) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + renderStageManager = RenderStageManager() + renderStageManager.attach(shadeListBuilder) + onRenderListListener = withArgCaptor { + verify(shadeListBuilder).setOnRenderListListener(capture()) + } + } + + private fun setUpRenderer() { + renderStageManager.setViewRenderer(spyViewRenderer) + } + + private fun setUpListeners() { + renderStageManager.addOnAfterRenderListListener(onAfterRenderListListener) + renderStageManager.addOnAfterRenderGroupListener(onAfterRenderGroupListener) + renderStageManager.addOnAfterRenderEntryListener(onAfterRenderEntryListener) + } + + @Test + fun testNoCallbacksWithoutRenderer() { + // GIVEN listeners but no renderer + setUpListeners() + + // WHEN a shade list is built + onRenderListListener.onRenderList(listWith2Groups8Entries()) + + // VERIFY that no listeners are called + verifyNoMoreInteractions( + onAfterRenderListListener, + onAfterRenderGroupListener, + onAfterRenderEntryListener + ) + } + + @Test + fun testDoesNotQueryControllerIfNoListeners() { + // GIVEN a renderer but no listeners + setUpRenderer() + + // WHEN a shade list is built + onRenderListListener.onRenderList(listWith2Groups8Entries()) + + // VERIFY that the renderer is not queried for group or row controllers + inOrder(spyViewRenderer).apply { + verify(spyViewRenderer, times(1)).onRenderList(any()) + verify(spyViewRenderer, times(1)).getStackController() + verify(spyViewRenderer, never()).getGroupController(any()) + verify(spyViewRenderer, never()).getRowController(any()) + verify(spyViewRenderer, times(1)).onDispatchComplete() + verifyNoMoreInteractions(spyViewRenderer) + } + } + + @Test + fun testDoesQueryControllerIfListeners() { + // GIVEN a renderer and listeners + setUpRenderer() + setUpListeners() + + // WHEN a shade list is built + onRenderListListener.onRenderList(listWith2Groups8Entries()) + + // VERIFY that the renderer is queried once per group/entry + inOrder(spyViewRenderer).apply { + verify(spyViewRenderer, times(1)).onRenderList(any()) + verify(spyViewRenderer, times(1)).getStackController() + verify(spyViewRenderer, times(2)).getGroupController(any()) + verify(spyViewRenderer, times(8)).getRowController(any()) + verify(spyViewRenderer, times(1)).onDispatchComplete() + verifyNoMoreInteractions(spyViewRenderer) + } + } + + @Test + fun testDoesNotQueryControllerTwice() { + // GIVEN a renderer and multiple distinct listeners + setUpRenderer() + setUpListeners() + renderStageManager.addOnAfterRenderListListener(mock()) + renderStageManager.addOnAfterRenderGroupListener(mock()) + renderStageManager.addOnAfterRenderEntryListener(mock()) + + // WHEN a shade list is built + onRenderListListener.onRenderList(listWith2Groups8Entries()) + + // VERIFY that the renderer is queried once per group/entry + inOrder(spyViewRenderer).apply { + verify(spyViewRenderer, times(1)).onRenderList(any()) + verify(spyViewRenderer, times(1)).getStackController() + verify(spyViewRenderer, times(2)).getGroupController(any()) + verify(spyViewRenderer, times(8)).getRowController(any()) + verify(spyViewRenderer, times(1)).onDispatchComplete() + verifyNoMoreInteractions(spyViewRenderer) + } + } + + @Test + fun testDoesCallListenerWithEachGroupAndEntry() { + // GIVEN a renderer and multiple distinct listeners + setUpRenderer() + setUpListeners() + + // WHEN a shade list is built + onRenderListListener.onRenderList(listWith2Groups8Entries()) + + // VERIFY that the listeners are invoked once per group and once per entry + verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any()) + verify(onAfterRenderGroupListener, times(2)).onAfterRenderGroup(any(), any()) + verify(onAfterRenderEntryListener, times(8)).onAfterRenderEntry(any(), any()) + verifyNoMoreInteractions( + onAfterRenderListListener, + onAfterRenderGroupListener, + onAfterRenderEntryListener + ) + } + + @Test + fun testDoesNotCallGroupAndEntryListenersIfTheListIsEmpty() { + // GIVEN a renderer and multiple distinct listeners + setUpRenderer() + setUpListeners() + + // WHEN a shade list is built empty + onRenderListListener.onRenderList(listOf()) + + // VERIFY that the stack listener is invoked once but other listeners are not + verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any()) + verify(onAfterRenderGroupListener, never()).onAfterRenderGroup(any(), any()) + verify(onAfterRenderEntryListener, never()).onAfterRenderEntry(any(), any()) + verifyNoMoreInteractions( + onAfterRenderListListener, + onAfterRenderGroupListener, + onAfterRenderEntryListener + ) + } + + private fun listWith2Groups8Entries() = listOf( + group( + notif(1), + notif(2), + notif(3) + ), + notif(4), + group( + notif(5), + notif(6), + notif(7) + ), + notif(8) + ) + + private class FakeNotifViewRenderer : NotifViewRenderer { + override fun onRenderList(notifList: List<ListEntry>) {} + override fun getStackController(): NotifStackController = mock() + override fun getGroupController(group: GroupEntry): NotifGroupController = mock() + override fun getRowController(entry: NotificationEntry): NotifRowController = mock() + override fun onDispatchComplete() {} + } + + private fun notif(id: Int): NotificationEntry = NotificationEntryBuilder().setId(id).build() + + private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry = + GroupEntryBuilder().setSummary(summary).setChildren(children.toList()).build() +} 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 185d9cd8733e..9be283716058 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 @@ -30,7 +30,6 @@ import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -296,14 +295,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { setBarStateForTest(StatusBarState.SHADE); mStackScroller.setCurrentUserSetup(true); - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - when(row.canViewBeDismissed()).thenReturn(true); - when(mStackScroller.getChildCount()).thenReturn(1); - when(mStackScroller.getChildAt(anyInt())).thenReturn(row); mStackScroller.setIsRemoteInputActive(true); - when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) .thenReturn(true); - when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true); FooterView view = mock(FooterView.class); mStackScroller.setFooterView(view); @@ -312,14 +307,28 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testUpdateFooter_withoutNotifications() { + setBarStateForTest(StatusBarState.SHADE); + mStackScroller.setCurrentUserSetup(true); + + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) + .thenReturn(false); + + FooterView view = mock(FooterView.class); + mStackScroller.setFooterView(view); + mStackScroller.updateFooter(); + verify(mStackScroller).updateFooterView(false, false, true); + } + + @Test public void testUpdateFooter_oneClearableNotification() { setBarStateForTest(StatusBarState.SHADE); mStackScroller.setCurrentUserSetup(true); - when(mEmptyShadeView.getVisibility()).thenReturn(GONE); - when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) .thenReturn(true); - when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true); FooterView view = mock(FooterView.class); mStackScroller.setFooterView(view); @@ -332,10 +341,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { setBarStateForTest(StatusBarState.SHADE); mStackScroller.setCurrentUserSetup(false); - when(mEmptyShadeView.getVisibility()).thenReturn(GONE); - when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) .thenReturn(true); - when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true); FooterView view = mock(FooterView.class); mStackScroller.setFooterView(view); @@ -348,12 +356,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { setBarStateForTest(StatusBarState.SHADE); mStackScroller.setCurrentUserSetup(true); - ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); - when(row.canViewBeDismissed()).thenReturn(false); - when(mStackScroller.getChildCount()).thenReturn(1); - when(mStackScroller.getChildAt(anyInt())).thenReturn(row); - when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true); - when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL)) + when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); + when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) .thenReturn(false); when(mEmptyShadeView.getVisibility()).thenReturn(GONE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 25fd80133897..07debe68e224 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -101,6 +102,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private ScreenLifecycle mScreenLifecycle; + @Mock + private StatusBarStateController mStatusBarStateController; private BiometricUnlockController mBiometricUnlockController; @Before @@ -123,7 +126,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, - mAuthController); + mAuthController, mStatusBarStateController); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } @@ -378,6 +381,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test + public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() { + // GIVEN UDFPS is supported + when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true); + + // WHEN udfps fails twice - then don't show the bouncer + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean()); + + // WHEN udfps fails the third time + mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + + // THEN show the bouncer + verify(mStatusBarKeyguardViewManager).showBouncer(true); + } + + @Test public void onFinishedGoingToSleep_authenticatesWhenPending() { when(mUpdateMonitor.isGoingToSleep()).thenReturn(true); mBiometricUnlockController.onFinishedGoingToSleep(-1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index bafbccdb87d2..db5fd262a1d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -162,8 +162,11 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { } @Test - public void testHeaderReadFromOldController() { - mHeadsUpAppearanceController.setAppearFraction(1.0f, 1.0f); + public void constructor_animationValuesUpdated() { + float appearFraction = .75f; + float expandedHeight = 400f; + when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction); + when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight); HeadsUpAppearanceController newController = new HeadsUpAppearanceController( mock(NotificationIconAreaController.class), @@ -179,14 +182,9 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { new View(mContext), new View(mContext), new View(mContext)); - newController.readFrom(mHeadsUpAppearanceController); - - Assert.assertEquals(mHeadsUpAppearanceController.mExpandedHeight, - newController.mExpandedHeight, 0.0f); - Assert.assertEquals(mHeadsUpAppearanceController.mAppearFraction, - newController.mAppearFraction, 0.0f); - Assert.assertEquals(mHeadsUpAppearanceController.mIsExpanded, - newController.mIsExpanded); + + Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f); + Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f); } @Test @@ -195,7 +193,9 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { reset(mDarkIconDispatcher); reset(mPanelView); reset(mStackScrollerController); - mHeadsUpAppearanceController.destroy(); + + mHeadsUpAppearanceController.onViewDetached(); + verify(mHeadsUpManager).removeListener(any()); verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any()); verify(mPanelView).removeTrackingHeadsUpListener(any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt index 210744eb30cf..3257a84f14d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt @@ -47,8 +47,8 @@ class KeyguardBottomAreaTest : SysuiTestCase() { @Test fun initFrom_doesntCrash() { - val other = LayoutInflater.from(mContext).inflate( - R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView + val other = LayoutInflater.from(mContext).inflate(R.layout.keyguard_bottom_area, + null, false) as KeyguardBottomAreaView other.initFrom(mKeyguardBottomArea) other.launchVoiceAssist() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 270c64ddfa7a..3ec9629e187a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -68,6 +68,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import androidx.test.filters.SmallTest; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -350,6 +351,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock + private InteractionJankMonitor mInteractionJankMonitor; + @Mock private NotificationsQSContainerController mNotificationsQSContainerController; private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private SysuiStatusBarStateController mStatusBarStateController; @@ -363,7 +366,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager); + mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, + mInteractionJankMonitor); mKeyguardStatusView = new KeyguardStatusView(mContext); mKeyguardStatusView.setId(R.id.keyguard_status_view); @@ -426,7 +430,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { NotificationWakeUpCoordinator coordinator = new NotificationWakeUpCoordinator( mock(HeadsUpManagerPhone.class), - new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager), + new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, + mInteractionJankMonitor), mKeyguardBypassController, mDozeParameters, mUnlockedScreenOffAnimationController); @@ -465,8 +470,12 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .thenReturn(mUserSwitcherView); when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean())) .thenReturn(mKeyguardBottomArea); - when(mNotificationRemoteInputManager.isRemoteInputActive()).thenReturn(false); - + when(mNotificationRemoteInputManager.isRemoteInputActive()) + .thenReturn(false); + when(mInteractionJankMonitor.begin(any(), anyInt())) + .thenReturn(true); + when(mInteractionJankMonitor.end(anyInt())) + .thenReturn(true); reset(mView); mNotificationPanelViewController = new NotificationPanelViewController(mView, @@ -520,7 +529,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { new PanelExpansionStateManager(), mNotificationRemoteInputManager, mSysUIUnfoldComponent, - mControlsComponent); + mControlsComponent, + mInteractionJankMonitor); mNotificationPanelViewController.initDependencies( mStatusBar, () -> {}, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 6f174cbe0021..c5bdfed6082b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -374,6 +374,21 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void testHideAltAuth_onShowBouncer() { + // GIVEN alt auth is showing + mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); + when(mBouncer.isShowing()).thenReturn(false); + when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true); + reset(mAlternateAuthInterceptor); + + // WHEN showBouncer is called + mStatusBarKeyguardViewManager.showBouncer(true); + + // THEN alt bouncer should be hidden + verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer(); + } + + @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); 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 348c1819e622..07ec0e23aa01 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 @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Handler; @@ -116,6 +117,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private KeyguardStateController mKeyguardStateController; @Mock + private NotificationInterruptStateProvider mNotificationInterruptStateProvider; + @Mock private Handler mHandler; @Mock private BubblesManager mBubblesManager; @@ -137,7 +140,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private OnUserInteractionCallback mOnUserInteractionCallback; @Mock - private NotificationActivityStarter mNotificationActivityStarter; + private StatusBarNotificationActivityStarter mNotificationActivityStarter; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -219,7 +222,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mock(NotificationLockscreenUserManager.class), mShadeController, mKeyguardStateController, - mock(NotificationInterruptStateProvider.class), + mNotificationInterruptStateProvider, mock(LockPatternUtils.class), mock(StatusBarRemoteInputCallback.class), mActivityIntentHelper, @@ -375,4 +378,27 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Notification should not be cancelled. verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt()); } + + @Test + public void testOnFullScreenIntentWhenDozing_wakeUpDevice() { + // GIVEN entry that can has a full screen intent that can show + Notification.Builder nb = new Notification.Builder(mContext, "a") + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setFullScreenIntent(mock(PendingIntent.class), true); + StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, + "tag" + System.currentTimeMillis(), 0, 0, + nb.build(), new UserHandle(0), null, 0); + NotificationEntry entry = mock(NotificationEntry.class); + when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH); + when(entry.getSbn()).thenReturn(sbn); + when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry))) + .thenReturn(true); + + // WHEN + mNotificationActivityStarter.handleFullScreenIntent(entry); + + // THEN display should try wake up for the full screen intent + verify(mStatusBar).wakeUpForFullScreenIntent(); + } } 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 1df576e3bd58..a34d2f5220a5 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 @@ -77,6 +77,7 @@ import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.InitController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -219,6 +220,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotificationGutsManager mNotificationGutsManager; @Mock private NotificationMediaManager mNotificationMediaManager; @Mock private NavigationBarController mNavigationBarController; + @Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController; @Mock private BypassHeadsUpNotifier mBypassHeadsUpNotifier; @Mock private SysuiColorExtractor mColorExtractor; @Mock private ColorExtractor.GradientColors mGradientColors; @@ -279,7 +281,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StartingSurface mStartingSurface; @Mock private OperatorNameViewController mOperatorNameViewController; @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; - @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private NotifPipelineFlags mNotifPipelineFlags; private ShadeController mShadeController; @@ -417,6 +418,7 @@ public class StatusBarTest extends SysuiTestCase { mVisualStabilityManager, mDeviceProvisionedController, mNavigationBarController, + mAccessibilityFloatingMenuController, () -> mAssistManager, configurationController, mNotificationShadeWindowController, @@ -446,7 +448,6 @@ public class StatusBarTest extends SysuiTestCase { mExtensionController, mUserInfoControllerImpl, mOperatorNameViewControllerFactory, - mPhoneStatusBarViewControllerFactory, mPhoneStatusBarPolicy, mKeyguardIndicationController, mDemoModeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 609d69ce1204..b97f053b24b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import android.app.Fragment; @@ -48,9 +47,9 @@ import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; @@ -62,12 +61,11 @@ import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; - -import java.util.Optional; +import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @@ -82,16 +80,22 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // Set in instantiate() private StatusBarIconController mStatusBarIconController; private NetworkController mNetworkController; - private StatusBarStateController mStatusBarStateController; private KeyguardStateController mKeyguardStateController; - private final StatusBar mStatusBar = mock(StatusBar.class); private final CommandQueue mCommandQueue = mock(CommandQueue.class); private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; private OperatorNameViewController mOperatorNameViewController; + @Mock private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory; + @Mock private StatusBarFragmentComponent mStatusBarFragmentComponent; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private HeadsUpAppearanceController mHeadsUpAppearanceController; + @Mock + private NotificationPanelViewController mNotificationPanelViewController; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -99,49 +103,35 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Before public void setup() { - mStatusBarStateController = mDependency - .injectMockDependency(StatusBarStateController.class); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); - when(mStatusBar.getPanelController()).thenReturn( - mock(NotificationPanelViewController.class)); } @Test - public void testDisableNone() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableNone() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock) - .getVisibility()); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test - public void testDisableSystemInfo() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableSystemInfo() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); - assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); } @Test - public void testDisableNotifications() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableNotifications() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); @@ -153,25 +143,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void testDisableClock() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableClock() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); - assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test public void disable_noOngoingCall_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(false); @@ -183,9 +169,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -199,9 +183,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -214,9 +196,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_ongoingCallEnded_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -234,41 +214,95 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); } - @Ignore("b/192618546") @Test - public void testOnDozingChanged() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false); - fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test + public void disable_customClockButNotDozing_clockAndSystemInfoVisible() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test + public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); + + // Make sure they start out as visible + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); - reset(mStatusBarStateController); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); + + // Make sure they start out as visible + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + fragment.onDozingChanged(true); - Mockito.verify(mStatusBarStateController).isDozing(); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + // When this callback is triggered, we want to make sure the clock and system info + // visibilities are recalculated. Since dozing=true, they shouldn't be visible. + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test public void setUp_fragmentCreatesDaggerComponent() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); assertEquals(mStatusBarFragmentComponent, fragment.getStatusBarFragmentComponent()); } @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { - mStatusBarFragmentComponentFactory = - mock(StatusBarFragmentComponent.Factory.class); - mStatusBarFragmentComponent = mock(StatusBarFragmentComponent.class); - when(mStatusBarFragmentComponentFactory.create(any())) - .thenReturn(mStatusBarFragmentComponent); + MockitoAnnotations.initMocks(this); + setUpDaggerComponent(); mOngoingCallController = mock(OngoingCallController.class); mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); mLocationPublisher = mock(StatusBarLocationPublisher.class); @@ -294,10 +328,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new StatusBarHideIconsForBouncerManager( mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), mKeyguardStateController, - mock(NotificationPanelViewController.class), + mNotificationPanelViewController, mNetworkController, mStatusBarStateController, - () -> Optional.of(mStatusBar), mCommandQueue, new CollapsedStatusBarFragmentLogger( new LogBuffer("TEST", 1, 1, mock(LogcatEchoTracker.class)), @@ -306,6 +339,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mOperatorNameViewControllerFactory); } + private void setUpDaggerComponent() { + when(mStatusBarFragmentComponentFactory.create(any())) + .thenReturn(mStatusBarFragmentComponent); + when(mStatusBarFragmentComponent.getHeadsUpAppearanceController()) + .thenReturn(mHeadsUpAppearanceController); + } + private void setUpNotificationIconAreaController() { mMockNotificationAreaController = mock(NotificationIconAreaController.class); @@ -324,4 +364,18 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn( mNotificationAreaInner); } + + private CollapsedStatusBarFragment resumeAndGetFragment() { + mFragments.dispatchResume(); + processAllMessages(); + return (CollapsedStatusBarFragment) mFragment; + } + + private View getClockView() { + return mFragment.getView().findViewById(R.id.clock); + } + + private View getSystemIconAreaView() { + return mFragment.getView().findViewById(R.id.system_icon_area); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index de2012af8599..25fafa1f4dbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -124,7 +124,8 @@ class UserSwitcherControllerTest : SysuiTestCase() { mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager::class.java)) - `when`(userManager.canAddMoreUsers()).thenReturn(true) + `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY))) + .thenReturn(true) `when`(notificationShadeWindowView.context).thenReturn(context) userSwitcherController = UserSwitcherController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index ae7afcef57a6..1e15d2ae0bdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -79,6 +80,7 @@ public class WMShellTest extends SysuiTestCase { @Mock ShellCommandHandler mShellCommandHandler; @Mock SizeCompatUI mSizeCompatUI; @Mock ShellExecutor mSysUiMainExecutor; + @Mock DragAndDrop mDragAndDrop; @Before public void setUp() { @@ -87,6 +89,7 @@ public class WMShellTest extends SysuiTestCase { mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI), + Optional.of(mDragAndDrop), mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, mSysUiMainExecutor); diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 67bb7262b672..f1599e4d7ffa 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -39,6 +39,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -1101,8 +1102,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { MagnificationProcessor magnificationProcessor = mSystemSupport.getMagnificationProcessor(); - return magnificationProcessor - .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId); + final MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + return magnificationProcessor.setMagnificationConfig(displayId, config, animate, + mId); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 572cfdc1dc57..52a6dc143df7 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2382,6 +2382,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState); somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); somethingChanged |= readHighTextContrastEnabledSettingLocked(userState); + somethingChanged |= readAudioDescriptionEnabledSettingLocked(userState); somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); somethingChanged |= readAccessibilityShortcutKeySettingLocked(userState); @@ -2454,6 +2455,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + private boolean readAudioDescriptionEnabledSettingLocked(AccessibilityUserState userState) { + final boolean audioDescriptionByDefaultEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, 0, + userState.mUserId) == 1; + if (audioDescriptionByDefaultEnabled + != userState.isAudioDescriptionByDefaultEnabledLocked()) { + userState.setAudioDescriptionByDefaultEnabledLocked(audioDescriptionByDefaultEnabled); + return true; + } + return false; + } + private void updateTouchExplorationLocked(AccessibilityUserState userState) { boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); boolean serviceHandlesDoubleTapEnabled = false; @@ -3418,6 +3432,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + /** + * Gets the status of the audio description preference. + * @return {@code true} if the audio description is enabled, {@code false} otherwise. + */ + @Override + public boolean isAudioDescriptionByDefaultEnabled() { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".isAudioDescriptionByDefaultEnabled", + FLAGS_ACCESSIBILITY_MANAGER); + } + synchronized (mLock) { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + + return userState.isAudioDescriptionByDefaultEnabledLocked(); + } + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -3528,9 +3559,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mConnectionId = service.mId; - mClient = AccessibilityInteractionClient.getInstance(/* initializeCache= */false, - mContext); - mClient.addConnection(mConnectionId, service); + mClient = AccessibilityInteractionClient.getInstance(mContext); + mClient.addConnection(mConnectionId, service, /*initializeCache=*/false); //TODO: (multi-display) We need to support multiple displays. DisplayManager displayManager = (DisplayManager) @@ -3806,6 +3836,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mHighTextContrastUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED); + private final Uri mAudioDescriptionByDefaultUri = Settings.Secure.getUriFor( + Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT); + private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); @@ -3852,6 +3885,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub contentResolver.registerContentObserver( mHighTextContrastUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( + mAudioDescriptionByDefaultUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mShowImeWithHardKeyboardUri, false, this, UserHandle.USER_ALL); @@ -3905,6 +3940,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readHighTextContrastEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } + } else if (mAudioDescriptionByDefaultUri.equals(uri)) { + if (readAudioDescriptionEnabledSettingLocked(userState)) { + onUserStateChangedLocked(userState); + } } else if (mAccessibilitySoftKeyboardModeUri.equals(uri) || mShowImeWithHardKeyboardUri.equals(uri)) { userState.reconcileSoftKeyboardModeWithSettingsLocked(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index e9f58708eb2c..bcb34139056d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -406,7 +406,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { synchronized (mLock) { - if (mSecurityPolicy.canPerformGestures(this)) { + if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) { MotionEventInjector motionEventInjector = mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId); if (wmTracingEnabled()) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 8c3ca3430a3a..9324e3e0db47 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -105,6 +105,7 @@ class AccessibilityUserState { private String mTargetAssignedToAccessibilityButton; private boolean mBindInstantServiceAllowed; + private boolean mIsAudioDescriptionByDefaultRequested; private boolean mIsAutoclickEnabled; private boolean mIsDisplayMagnificationEnabled; private boolean mIsFilterKeyEventsEnabled; @@ -411,6 +412,10 @@ class AccessibilityUserState { if (mIsTextHighContrastEnabled) { clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; } + if (mIsAudioDescriptionByDefaultRequested) { + clientState |= + AccessibilityManager.STATE_FLAG_AUDIO_DESCRIPTION_BY_DEFAULT_ENABLED; + } clientState |= traceClientState; @@ -506,6 +511,8 @@ class AccessibilityUserState { pw.append(", magnificationModes=").append(String.valueOf(mMagnificationModes)); pw.append(", magnificationCapabilities=") .append(String.valueOf(mMagnificationCapabilities)); + pw.append(", audioDescriptionByDefaultEnabled=") + .append(String.valueOf(mIsAudioDescriptionByDefaultRequested)); pw.append("}"); pw.println(); pw.append(" shortcut key:{"); @@ -824,6 +831,14 @@ class AccessibilityUserState { mIsTextHighContrastEnabled = enabled; } + public boolean isAudioDescriptionByDefaultEnabledLocked() { + return mIsAudioDescriptionByDefaultRequested; + } + + public void setAudioDescriptionByDefaultEnabledLocked(boolean enabled) { + mIsAudioDescriptionByDefaultRequested = enabled; + } + public boolean isTouchExplorationEnabledLocked() { return mIsTouchExplorationEnabled; } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 946d22e46589..86777a2f62fd 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -537,6 +537,8 @@ public class TouchExplorer extends BaseEventStreamTransformation // touch, we figure out what to do. If were waiting // we resent the delayed callback and wait again. mSendHoverEnterAndMoveDelayed.cancel(); + // clear any hover events that might have been queued and never sent. + mSendHoverEnterAndMoveDelayed.clear(); mSendHoverExitDelayed.cancel(); // If a touch exploration gesture is in progress send events for its end. if (mState.isTouchExploring()) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 6473bf5ffc3e..42a81e16c0f4 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -20,7 +20,9 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; +import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; +import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -74,6 +76,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb private final PointF mTempPoint = new PointF(); private final Object mLock; private final Context mContext; + @GuardedBy("mLock") private final SparseArray<DisableMagnificationCallback> mMagnificationEndRunnableSparseArray = new SparseArray(); @@ -84,6 +87,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb @GuardedBy("mLock") private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; + @GuardedBy("mLock") + private int mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; // Track the active user to reset the magnification and get the associated user settings. private @UserIdInt int mUserId = UserHandle.USER_SYSTEM; @GuardedBy("mLock") @@ -206,7 +211,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb final float scale = mScaleProvider.getScale(displayId); final DisableMagnificationCallback animationEndCallback = new DisableMagnificationCallback(transitionCallBack, displayId, targetMode, - scale, magnificationCenter); + scale, magnificationCenter, true); if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { screenMagnificationController.reset(displayId, animationEndCallback); } else { @@ -216,6 +221,77 @@ public class MagnificationController implements WindowMagnificationManager.Callb setDisableMagnificationCallbackLocked(displayId, animationEndCallback); } + /** + * Transitions to the targeting magnification config mode with current center of the + * magnification mode if it is available. It disables the current magnifier immediately then + * transitions to the targeting magnifier. + * + * @param displayId The logical display id + * @param config The targeting magnification config + * @param animate {@code true} to animate the transition, {@code false} + * to transition immediately + */ + public void transitionMagnificationConfigMode(int displayId, MagnificationConfig config, + boolean animate) { + synchronized (mLock) { + final int targetMode = config.getMode(); + final PointF currentBoundsCenter = getCurrentMagnificationBoundsCenterLocked(displayId, + targetMode); + final PointF magnificationCenter = new PointF(config.getCenterX(), config.getCenterY()); + if (currentBoundsCenter != null) { + final float centerX = Float.isNaN(config.getCenterX()) + ? currentBoundsCenter.x + : config.getCenterX(); + final float centerY = Float.isNaN(config.getCenterY()) + ? currentBoundsCenter.y + : config.getCenterY(); + magnificationCenter.set(centerX, centerY); + } + + final DisableMagnificationCallback animationCallback = + getDisableMagnificationEndRunnableLocked(displayId); + if (animationCallback != null) { + Slog.w(TAG, "Discard previous animation request"); + animationCallback.setExpiredAndRemoveFromListLocked(); + } + + final FullScreenMagnificationController screenMagnificationController = + getFullScreenMagnificationController(); + final WindowMagnificationManager windowMagnificationMgr = getWindowMagnificationMgr(); + final float scale = mScaleProvider.getScale(displayId); + if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + screenMagnificationController.reset(displayId, false); + windowMagnificationMgr.enableWindowMagnification(displayId, + scale, magnificationCenter.x, magnificationCenter.y, + animate ? STUB_ANIMATION_CALLBACK : null); + } else if (targetMode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { + windowMagnificationMgr.disableWindowMagnification(displayId, false, null); + if (!screenMagnificationController.isRegistered(displayId)) { + screenMagnificationController.register(displayId); + } + screenMagnificationController.setScaleAndCenter(displayId, scale, + magnificationCenter.x, magnificationCenter.y, animate, + AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + } + } + } + + /** + * Return {@code true} if disable magnification animation callback of the display is running. + * + * @param displayId The logical display id + */ + public boolean hasDisableMagnificationCallback(int displayId) { + synchronized (mLock) { + final DisableMagnificationCallback animationCallback = + getDisableMagnificationEndRunnableLocked(displayId); + if (animationCallback != null) { + return true; + } + } + return false; + } + @Override public void onRequestMagnificationSpec(int displayId, int serviceId) { final WindowMagnificationManager windowMagnificationManager; @@ -239,6 +315,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + mLastActivatedMode = mActivatedMode; } logMagnificationModeWithImeOnIfNeeded(); disableFullScreenMagnificationIfNeeded(displayId); @@ -264,7 +341,8 @@ public class MagnificationController implements WindowMagnificationManager.Callb // Internal request may be for transition, so we just need to check external request. final boolean isMagnifyByExternalRequest = fullScreenMagnificationController.getIdOfLastServiceToMagnify(displayId) > 0; - if (isMagnifyByExternalRequest) { + if (isMagnifyByExternalRequest || isActivated(displayId, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) { fullScreenMagnificationController.reset(displayId, false); } } @@ -276,8 +354,10 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + mLastActivatedMode = mActivatedMode; } logMagnificationModeWithImeOnIfNeeded(); + disableWindowMagnificationIfNeeded(displayId); } else { logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, SystemClock.uptimeMillis() - mFullScreenModeEnabledTime); @@ -289,6 +369,14 @@ public class MagnificationController implements WindowMagnificationManager.Callb updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } + private void disableWindowMagnificationIfNeeded(int displayId) { + final WindowMagnificationManager windowMagnificationManager = + getWindowMagnificationMgr(); + if (isActivated(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { + windowMagnificationManager.disableWindowMagnification(displayId, false); + } + } + @Override public void onImeWindowVisibilityChanged(boolean shown) { synchronized (mLock) { @@ -298,6 +386,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb } /** + * Returns the last activated magnification mode. If there is no activated magnifier before, it + * returns fullscreen mode by default. + */ + public int getLastActivatedMode() { + synchronized (mLock) { + return mLastActivatedMode; + } + } + + /** * Wrapper method of logging the magnification activated mode and its duration of the usage * when the magnification is disabled. * @@ -336,6 +434,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { fullMagnificationController = mFullScreenMagnificationController; windowMagnificationManager = mWindowMagnificationMgr; + mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; } mScaleProvider.onUserChanged(userId); @@ -462,7 +561,15 @@ public class MagnificationController implements WindowMagnificationManager.Callb return mTempPoint; } - private boolean isActivated(int displayId, int mode) { + /** + * Return {@code true} if the specified magnification mode on the given display is activated + * or not. + * + * @param displayId The logical displayId. + * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or + * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW. + */ + public boolean isActivated(int displayId, int mode) { boolean isActivated = false; if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { synchronized (mLock) { @@ -495,15 +602,17 @@ public class MagnificationController implements WindowMagnificationManager.Callb private final int mCurrentMode; private final float mCurrentScale; private final PointF mCurrentCenter = new PointF(); + private final boolean mAnimate; - DisableMagnificationCallback(TransitionCallBack transitionCallBack, - int displayId, int targetMode, float scale, PointF currentCenter) { + DisableMagnificationCallback(@Nullable TransitionCallBack transitionCallBack, + int displayId, int targetMode, float scale, PointF currentCenter, boolean animate) { mTransitionCallBack = transitionCallBack; mDisplayId = displayId; mTargetMode = targetMode; mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL; mCurrentScale = scale; mCurrentCenter.set(currentCenter); + mAnimate = animate; } @Override @@ -521,7 +630,9 @@ public class MagnificationController implements WindowMagnificationManager.Callb applyMagnificationModeLocked(mTargetMode); } updateMagnificationButton(mDisplayId, mTargetMode); - mTransitionCallBack.onResult(mDisplayId, success); + if (mTransitionCallBack != null) { + mTransitionCallBack.onResult(mDisplayId, success); + } } } @@ -546,7 +657,9 @@ public class MagnificationController implements WindowMagnificationManager.Callb setExpiredAndRemoveFromListLocked(); applyMagnificationModeLocked(mCurrentMode); updateMagnificationButton(mDisplayId, mCurrentMode); - mTransitionCallBack.onResult(mDisplayId, true); + if (mTransitionCallBack != null) { + mTransitionCallBack.onResult(mDisplayId, true); + } } } @@ -557,9 +670,13 @@ public class MagnificationController implements WindowMagnificationManager.Callb private void applyMagnificationModeLocked(int mode) { if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) { - getFullScreenMagnificationController().setScaleAndCenter(mDisplayId, - mCurrentScale, mCurrentCenter.x, - mCurrentCenter.y, true, + final FullScreenMagnificationController fullScreenMagnificationController = + getFullScreenMagnificationController(); + if (!fullScreenMagnificationController.isRegistered(mDisplayId)) { + fullScreenMagnificationController.register(mDisplayId); + } + fullScreenMagnificationController.setScaleAndCenter(mDisplayId, mCurrentScale, + mCurrentCenter.x, mCurrentCenter.y, mAnimate, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); } else { getWindowMagnificationMgr().enableWindowMagnification(mDisplayId, diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java index efc6d515fdb8..dda1c4f728c8 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java @@ -16,6 +16,13 @@ package com.android.server.accessibility.magnification; +import static android.accessibilityservice.MagnificationConfig.DEFAULT_MODE; +import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE; +import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.graphics.Region; @@ -23,6 +30,22 @@ import android.graphics.Region; * Processor class for AccessibilityService connection to control magnification on the specified * display. This wraps the function of magnification controller. * + * <p> + * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current + * activated magnifier on the display. If there is no magnifier activated, it controls + * full-screen magnifier by default. + * </p> + * + * <p> + * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control + * full-screen magnifier on the display. + * </p> + * + * <p> + * If the magnification config uses {@link WINDOW_MODE}. This processor will control + * the activated window magnifier on the display. + * </p> + * * @see MagnificationController * @see FullScreenMagnificationController */ @@ -35,53 +58,185 @@ public class MagnificationProcessor { } /** - * {@link FullScreenMagnificationController#getScale(int)} + * Gets the magnification config of the display. + * + * @param displayId The logical display id + * @return the magnification config + */ + public @NonNull MagnificationConfig getMagnificationConfig(int displayId) { + final int mode = getControllingMode(displayId); + MagnificationConfig.Builder builder = new MagnificationConfig.Builder(); + if (mode == FULLSCREEN_MODE) { + final FullScreenMagnificationController fullScreenMagnificationController = + mController.getFullScreenMagnificationController(); + builder.setMode(mode) + .setScale(fullScreenMagnificationController.getScale(displayId)) + .setCenterX(fullScreenMagnificationController.getCenterX(displayId)) + .setCenterY(fullScreenMagnificationController.getCenterY(displayId)); + } else if (mode == WINDOW_MODE) { + final WindowMagnificationManager windowMagnificationManager = + mController.getWindowMagnificationMgr(); + builder.setMode(mode) + .setScale(windowMagnificationManager.getScale(displayId)) + .setCenterX(windowMagnificationManager.getCenterX(displayId)) + .setCenterY(windowMagnificationManager.getCenterY(displayId)); + } + return builder.build(); + } + + /** + * Sets the magnification config of the display. If animation is disabled, the transition + * is immediate. + * + * @param displayId The logical display id + * @param config The magnification config + * @param animate {@code true} to animate from the current config or + * {@code false} to set the config immediately + * @param id The ID of the service requesting the change + * @return {@code true} if the magnification spec changed, {@code false} if the spec did not + * change + */ + public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, + boolean animate, int id) { + if (transitionModeIfNeeded(displayId, config, animate)) { + return true; + } + + int configMode = config.getMode(); + if (configMode == DEFAULT_MODE) { + configMode = getControllingMode(displayId); + } + if (configMode == FULLSCREEN_MODE) { + return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(), + config.getCenterX(), config.getCenterY(), + animate, id); + } else if (configMode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId, + config.getScale(), config.getCenterX(), config.getCenterY()); + } + return false; + } + + /** + * Returns {@code true} if transition magnification mode needed. And it is no need to transition + * mode when the controlling mode is unchanged or the controlling magnifier is not activated. + */ + private boolean transitionModeIfNeeded(int displayId, MagnificationConfig config, + boolean animate) { + int currentMode = getControllingMode(displayId); + if (currentMode == config.getMode() + || !mController.hasDisableMagnificationCallback(displayId)) { + return false; + } + mController.transitionMagnificationConfigMode(displayId, config, animate); + return true; + } + + /** + * Returns the magnification scale. If an animation is in progress, + * this reflects the end state of the animation. + * + * @param displayId The logical display id. + * @return the scale */ public float getScale(int displayId) { - return mController.getFullScreenMagnificationController().getScale(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().getScale(displayId); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getScale(displayId); + } + return 0; } /** - * {@link FullScreenMagnificationController#getCenterX(int)} + * Returns the magnification center in X coordinate of the controlling magnification mode. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make the + * magnification center correct. + * + * @param displayId The logical display id + * @param canControlMagnification Whether the service can control magnification + * @return the X coordinate */ public float getCenterX(int displayId, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterX(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterX(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); + } } + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getCenterX(displayId); } + return 0; } /** - * {@link FullScreenMagnificationController#getCenterY(int)} + * Returns the magnification center in Y coordinate of the controlling magnification mode. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make the + * magnification center correct. + * + * @param displayId The logical display id + * @param canControlMagnification Whether the service can control magnification + * @return the Y coordinate */ public float getCenterY(int displayId, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, - canControlMagnification); - try { - return mController.getFullScreenMagnificationController().getCenterY(displayId); - } finally { - if (registeredJustForThisCall) { - unregister(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, + canControlMagnification); + try { + return mController.getFullScreenMagnificationController().getCenterY(displayId); + } finally { + if (registeredJustForThisCall) { + unregister(displayId); + } } + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().getCenterY(displayId); } + return 0; } /** - * {@link FullScreenMagnificationController#getMagnificationRegion(int, Region)} + * Return the magnification bounds of the current controlling magnification on the given + * display. If the magnifier is not enabled, it returns an empty region. + * If the service can control magnification but fullscreen magnifier is not registered, it will + * register the magnifier for this call then unregister the magnifier finally to make + * the magnification region correct. + * + * @param displayId The logical display id + * @param outRegion the region to populate + * @param canControlMagnification Whether the service can control magnification + * @return outRegion the magnification bounds of full-screen magnifier or the magnification + * source bounds of window magnifier */ public Region getMagnificationRegion(int displayId, @NonNull Region outRegion, boolean canControlMagnification) { - boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId, + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification); + } else if (mode == WINDOW_MODE) { + mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId, + outRegion); + } + return outRegion; + } + + private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion, + boolean canControlMagnification) { + boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId, canControlMagnification); try { mController.getFullScreenMagnificationController().getMagnificationRegion(displayId, outRegion); - return outRegion; } finally { if (registeredJustForThisCall) { unregister(displayId); @@ -89,67 +244,105 @@ public class MagnificationProcessor { } } - /** - * {@link FullScreenMagnificationController#setScaleAndCenter(int, float, float, float, boolean, - * int)} - */ - public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, + private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale, + float centerX, float centerY, boolean animate, int id) { if (!isRegistered(displayId)) { register(displayId); } - return mController.getFullScreenMagnificationController().setScaleAndCenter(displayId, + return mController.getFullScreenMagnificationController().setScaleAndCenter( + displayId, scale, centerX, centerY, animate, id); } /** - * {@link FullScreenMagnificationController#reset(int, boolean)} + * Resets the magnification on the given display. The reset mode could be full-screen or + * window if it is activated. + * + * @param displayId The logical display id. + * @param animate {@code true} to animate the transition, {@code false} + * to transition immediately + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change */ public boolean reset(int displayId, boolean animate) { - return mController.getFullScreenMagnificationController().reset(displayId, animate); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().reset(displayId, animate); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().reset(displayId); + } + return false; } /** * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)} */ + // TODO: support window magnification public void resetAllIfNeeded(int connectionId) { mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId); } /** - * {@link FullScreenMagnificationController#register(int)} - */ - public void register(int displayId) { - mController.getFullScreenMagnificationController().register(displayId); - } - - /** - * {@link FullScreenMagnificationController#unregister(int)} (int)} - */ - public void unregister(int displayId) { - mController.getFullScreenMagnificationController().unregister(displayId); - } - - /** * {@link FullScreenMagnificationController#isMagnifying(int)} + * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)} */ public boolean isMagnifying(int displayId) { - return mController.getFullScreenMagnificationController().isMagnifying(displayId); + int mode = getControllingMode(displayId); + if (mode == FULLSCREEN_MODE) { + return mController.getFullScreenMagnificationController().isMagnifying(displayId); + } else if (mode == WINDOW_MODE) { + return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId); + } + return false; } /** - * {@link FullScreenMagnificationController#isRegistered(int)} + * Returns the current controlling magnification mode on the given display. + * If there is no magnifier activated, it fallbacks to the last activated mode. + * And the last activated mode is {@link FULLSCREEN_MODE} by default. + * + * @param displayId The logical display id */ - public boolean isRegistered(int displayId) { - return mController.getFullScreenMagnificationController().isRegistered(displayId); + public int getControllingMode(int displayId) { + if (mController.isActivated(displayId, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) { + return WINDOW_MODE; + } else if (mController.isActivated(displayId, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) { + return FULLSCREEN_MODE; + } else { + return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) + ? WINDOW_MODE + : FULLSCREEN_MODE; + } } - private boolean registerMagnificationIfNeeded(int displayId, boolean canControlMagnification) { + private boolean registerDisplayMagnificationIfNeeded(int displayId, + boolean canControlMagnification) { if (!isRegistered(displayId) && canControlMagnification) { register(displayId); return true; } return false; } + + private boolean isRegistered(int displayId) { + return mController.getFullScreenMagnificationController().isRegistered(displayId); + } + + /** + * {@link FullScreenMagnificationController#register(int)} + */ + private void register(int displayId) { + mController.getFullScreenMagnificationController().register(displayId); + } + + /** + * {@link FullScreenMagnificationController#unregister(int)} (int)} + */ + private void unregister(int displayId) { + mController.getFullScreenMagnificationController().unregister(displayId); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java index 527742536480..25dcc2aea41b 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java @@ -59,15 +59,19 @@ class WindowMagnificationConnectionWrapper { } boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY, + float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable MagnificationAnimationCallback callback) { if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) { mTrace.logTrace(TAG + ".enableWindowMagnification", FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX - + ";centerY=" + centerY + ";callback=" + callback); + + ";centerY=" + centerY + ";magnificationFrameOffsetRatioX=" + + magnificationFrameOffsetRatioX + ";magnificationFrameOffsetRatioY=" + + magnificationFrameOffsetRatioY + ";callback=" + callback); } try { mConnection.enableWindowMagnification(displayId, scale, centerX, centerY, + magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, transformToRemoteCallback(callback, mTrace)); } catch (RemoteException e) { if (DBG) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 7d8f545b65c3..820be28387ae 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -18,6 +18,7 @@ package com.android.server.accessibility.magnification; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static java.util.Arrays.asList; @@ -78,6 +79,8 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl final DetectingState mDetectingState; @VisibleForTesting final PanningScalingGestureState mObservePanningScalingState; + @VisibleForTesting + final ViewportDraggingState mViewportDraggingState; @VisibleForTesting State mCurrentState; @@ -105,6 +108,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl policyFlags)); mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate); mDetectingState = new DetectingState(context, mDetectTripleTap); + mViewportDraggingState = new ViewportDraggingState(); mObservePanningScalingState = new PanningScalingGestureState( new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true, new PanningScalingHandler.MagnificationDelegate() { @@ -158,7 +162,8 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl public void handleShortcutTriggered() { final Point screenSize = mTempPoint; getScreenSize(mTempPoint); - toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f); + toggleMagnification(screenSize.x / 2.0f, screenSize.y / 2.0f, + WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); } private void getScreenSize(Point outSize) { @@ -171,14 +176,17 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; } - private void enableWindowMagnifier(float centerX, float centerY) { + private void enableWindowMagnifier(float centerX, float centerY, + @WindowMagnificationManager.WindowPosition int windowPosition) { if (DEBUG_ALL) { - Slog.i(mLogTag, "enableWindowMagnifier :" + centerX + ", " + centerY); + Slog.i(mLogTag, "enableWindowMagnifier :" + + centerX + ", " + centerY + ", " + windowPosition); } final float scale = MathUtils.constrain( mWindowMagnificationMgr.getPersistedScale(mDisplayId), MIN_SCALE, MAX_SCALE); - mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY); + mWindowMagnificationMgr.enableWindowMagnification(mDisplayId, scale, centerX, centerY, + windowPosition); } private void disableWindowMagnifier() { @@ -188,11 +196,12 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, false); } - private void toggleMagnification(float centerX, float centerY) { + private void toggleMagnification(float centerX, float centerY, + @WindowMagnificationManager.WindowPosition int windowPosition) { if (mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId)) { disableWindowMagnifier(); } else { - enableWindowMagnifier(centerX, centerY); + enableWindowMagnifier(centerX, centerY, windowPosition); } } @@ -200,7 +209,17 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl if (DEBUG_DETECTING) { Slog.i(mLogTag, "onTripleTap()"); } - toggleMagnification(up.getX(), up.getY()); + toggleMagnification(up.getX(), up.getY(), + WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + } + + private void onTripleTapAndHold(MotionEvent up) { + if (DEBUG_DETECTING) { + Slog.i(mLogTag, "onTripleTapAndHold()"); + } + enableWindowMagnifier(up.getX(), up.getY(), + WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT); + transitionTo(mViewportDraggingState); } void resetToDetectState() { @@ -319,6 +338,65 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl } } + + /** + * This class handles motion events when the event dispatcher has + * determined that the user is performing a single-finger drag of the + * magnification viewport. + * + * Leaving this state until receiving {@link MotionEvent#ACTION_UP} + * or {@link MotionEvent#ACTION_CANCEL}. + */ + final class ViewportDraggingState implements State { + + private float mLastX = Float.NaN; + private float mLastY = Float.NaN; + + @Override + public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + final int action = event.getActionMasked(); + switch (action) { + case ACTION_MOVE: { + if (!Float.isNaN(mLastX) && !Float.isNaN(mLastY)) { + float offsetX = event.getX() - mLastX; + float offsetY = event.getY() - mLastY; + mWindowMagnificationMgr.moveWindowMagnification(mDisplayId, offsetX, + offsetY); + } + mLastX = event.getX(); + mLastY = event.getY(); + } + break; + + case ACTION_UP: + case ACTION_CANCEL: { + mWindowMagnificationMgr.disableWindowMagnification(mDisplayId, true); + transitionTo(mDetectingState); + } + break; + } + } + + @Override + public void clear() { + mLastX = Float.NaN; + mLastY = Float.NaN; + } + + @Override + public void onExit() { + clear(); + } + + @Override + public String toString() { + return "ViewportDraggingState{" + + "mLastX=" + mLastX + + ",mLastY=" + mLastY + + '}'; + } + } + /** * This class handles motion events in a duration to determine if the user is going to * manipulate the window magnifier or want to interact with current UI. The rule of leaving @@ -405,6 +483,8 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl transitionTo(mObservePanningScalingState); } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP) { onTripleTap(motionEvent); + } else if (gestureId == MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD) { + onTripleTapAndHold(motionEvent); } else { mMotionEventDispatcherDelegate.sendDelayedMotionEvents(delayedEventQueue, lastDownEventTime); @@ -439,6 +519,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl return "WindowMagnificationGestureHandler{" + "mDetectingState=" + mDetectingState + ", mDelegatingState=" + mDelegatingState + + ", mViewportDraggingState=" + mViewportDraggingState + ", mMagnifiedInteractionState=" + mObservePanningScalingState + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index e1ec273b444a..9162064780ca 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -20,13 +20,16 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNI import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK; import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.Region; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -43,6 +46,9 @@ import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.statusbar.StatusBarManagerInternal; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class to manipulate window magnification through {@link WindowMagnificationConnectionWrapper} * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with @@ -57,6 +63,25 @@ public class WindowMagnificationManager implements private static final String TAG = "WindowMagnificationMgr"; + /** + * Indicate that the magnification window is at the magnification center. + */ + public static final int WINDOW_POSITION_AT_CENTER = 0; + + /** + * Indicate that the magnification window is at the top-left side of the magnification + * center. The offset is equal to a half of MirrorSurfaceView. So, the bottom-right corner + * of the window is at the magnification center. + */ + public static final int WINDOW_POSITION_AT_TOP_LEFT = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "WINDOW_POSITION_AT_" }, value = { + WINDOW_POSITION_AT_CENTER, + WINDOW_POSITION_AT_TOP_LEFT + }) + public @interface WindowPosition {} + private final Object mLock = new Object(); private final Context mContext; @VisibleForTesting @@ -271,41 +296,88 @@ public class WindowMagnificationManager implements * or {@link Float#NaN} to leave unchanged. * @param centerY The screen-relative Y coordinate around which to center, * or {@link Float#NaN} to leave unchanged. + * @return {@code true} if the magnification is enabled successfully. */ - void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) { - enableWindowMagnification(displayId, scale, centerX, centerY, STUB_ANIMATION_CALLBACK); + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY) { + return enableWindowMagnification(displayId, scale, centerX, centerY, + STUB_ANIMATION_CALLBACK); } /** - * Enables window magnification with specified center and scale on the specified display and + * Enables window magnification with specified center and scale on the given display and * animating the transition. * * @param displayId The logical display id. * @param scale The target scale, must be >= 1. - * @param centerX The screen-relative X coordinate around which to center, + * @param centerX The screen-relative X coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. - * @param centerY The screen-relative Y coordinate around which to center, + * @param centerY The screen-relative Y coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param animationCallback Called when the animation result is valid. + * @return {@code true} if the magnification is enabled successfully. + */ + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY, @Nullable MagnificationAnimationCallback animationCallback) { + return enableWindowMagnification(displayId, scale, centerX, centerY, animationCallback, + WINDOW_POSITION_AT_CENTER); + } + + /** + * Enables window magnification with specified center and scale on the given display and + * animating the transition. + * + * @param displayId The logical display id. + * @param scale The target scale, must be >= 1. + * @param centerX The screen-relative X coordinate around which to center for magnification, * or {@link Float#NaN} to leave unchanged. + * @param centerY The screen-relative Y coordinate around which to center for magnification, + * or {@link Float#NaN} to leave unchanged. + * @param windowPosition Indicate the offset between window position and (centerX, centerY). + * @return {@code true} if the magnification is enabled successfully. + */ + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY, @WindowPosition int windowPosition) { + return enableWindowMagnification(displayId, scale, centerX, centerY, + STUB_ANIMATION_CALLBACK, windowPosition); + } + + /** + * Enables window magnification with specified center and scale on the given display and + * animating the transition. + * + * @param displayId The logical display id. + * @param scale The target scale, must be >= 1. + * @param centerX The screen-relative X coordinate around which to center for + * magnification, or {@link Float#NaN} to leave unchanged. + * @param centerY The screen-relative Y coordinate around which to center for + * magnification, or {@link Float#NaN} to leave unchanged. * @param animationCallback Called when the animation result is valid. + * @param windowPosition Indicate the offset between window position and (centerX, centerY). + * @return {@code true} if the magnification is enabled successfully. */ - void enableWindowMagnification(int displayId, float scale, float centerX, float centerY, - @Nullable MagnificationAnimationCallback animationCallback) { + public boolean enableWindowMagnification(int displayId, float scale, float centerX, + float centerY, @Nullable MagnificationAnimationCallback animationCallback, + @WindowPosition int windowPosition) { final boolean enabled; + boolean previousEnabled; synchronized (mLock) { if (mConnectionWrapper == null) { - return; + return false; } WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { magnifier = createWindowMagnifier(displayId); } + previousEnabled = magnifier.mEnabled; enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, - animationCallback); + animationCallback, windowPosition); } - if (enabled) { + if (enabled && !previousEnabled) { mCallback.onWindowMagnificationActivationState(displayId, true); } + return enabled; } /** @@ -464,7 +536,7 @@ public class WindowMagnificationManager implements * @param displayId The logical display id * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled. */ - float getCenterX(int displayId) { + public float getCenterX(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -480,7 +552,7 @@ public class WindowMagnificationManager implements * @param displayId The logical display id * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled. */ - float getCenterY(int displayId) { + public float getCenterY(int displayId) { synchronized (mLock) { WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); if (magnifier == null) { @@ -491,6 +563,42 @@ public class WindowMagnificationManager implements } /** + * Populates magnified bounds on the screen. And the populated magnified bounds would be + * empty If window magnifier is not activated. + * + * @param displayId The logical display id. + * @param outRegion the region to populate + */ + public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + outRegion.setEmpty(); + } else { + outRegion.set(magnifier.mSourceBounds); + } + } + } + + /** + * Resets the magnification scale and center. + * + * @param displayId The logical display id. + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change + */ + public boolean reset(int displayId) { + synchronized (mLock) { + WindowMagnifier magnifier = mWindowMagnifiers.get(displayId); + if (magnifier == null) { + return false; + } + magnifier.reset(); + return true; + } + } + + /** * Creates the windowMagnifier based on the specified display and stores it. * * @param displayId logical display id. @@ -618,6 +726,8 @@ public class WindowMagnificationManager implements // The magnified bounds on the screen. private final Rect mSourceBounds = new Rect(); + private PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f); + WindowMagnifier(int displayId, WindowMagnificationManager windowMagnificationManager) { mDisplayId = displayId; mWindowMagnificationManager = windowMagnificationManager; @@ -625,13 +735,17 @@ public class WindowMagnificationManager implements @GuardedBy("mLock") boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY, - @Nullable MagnificationAnimationCallback animationCallback) { - if (mEnabled) { - return false; + @Nullable MagnificationAnimationCallback animationCallback, + @WindowPosition int windowPosition) { + // Handle defaults. The scale may be NAN when just updating magnification center. + if (Float.isNaN(scale)) { + scale = getScale(); } final float normScale = MagnificationScaleProvider.constrainScale(scale); + setMagnificationFrameOffsetRatioByWindowPosition(windowPosition); if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale, - centerX, centerY, animationCallback)) { + centerX, centerY, mMagnificationFrameOffsetRatio.x, + mMagnificationFrameOffsetRatio.y, animationCallback)) { mScale = normScale; mEnabled = true; @@ -640,6 +754,19 @@ public class WindowMagnificationManager implements return false; } + void setMagnificationFrameOffsetRatioByWindowPosition(@WindowPosition int windowPosition) { + switch (windowPosition) { + case WINDOW_POSITION_AT_CENTER: { + mMagnificationFrameOffsetRatio.set(0f, 0f); + } + break; + case WINDOW_POSITION_AT_TOP_LEFT: { + mMagnificationFrameOffsetRatio.set(-1f, -1f); + } + break; + } + } + @GuardedBy("mLock") boolean disableWindowMagnificationInternal( @Nullable MagnificationAnimationCallback animationResultCallback) { @@ -723,9 +850,15 @@ public class WindowMagnificationManager implements } private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX, - float centerY, MagnificationAnimationCallback animationCallback) { - return mConnectionWrapper != null && mConnectionWrapper.enableWindowMagnification( - displayId, scale, centerX, centerY, animationCallback); + float centerY, float magnificationFrameOffsetRatioX, + float magnificationFrameOffsetRatioY, + MagnificationAnimationCallback animationCallback) { + synchronized (mLock) { + return mConnectionWrapper != null && mConnectionWrapper.enableWindowMagnification( + displayId, scale, centerX, centerY, + magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, + animationCallback); + } } private boolean setScaleInternal(int displayId, float scale) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 5e2449da04c7..422749e9a75a 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -210,7 +210,7 @@ public final class AutofillManagerService final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler(), - Context.RECEIVER_NOT_EXPORTED); + Context.RECEIVER_EXPORTED); mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(), com.android.internal.R.string.config_defaultAugmentedAutofillService); diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java index fd573d5e0665..594140efe5f2 100644 --- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java +++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java @@ -40,8 +40,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.internal.util.Preconditions; import com.android.server.backup.transport.OnTransportRegisteredListener; -import com.android.server.backup.transport.TransportClient; -import com.android.server.backup.transport.TransportClientManager; +import com.android.server.backup.transport.TransportConnection; +import com.android.server.backup.transport.TransportConnectionManager; import com.android.server.backup.transport.TransportConnectionListener; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; @@ -65,7 +65,7 @@ public class TransportManager { private final @UserIdInt int mUserId; private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; - private final TransportClientManager mTransportClientManager; + private final TransportConnectionManager mTransportConnectionManager; private final TransportStats mTransportStats; private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; @@ -73,8 +73,8 @@ public class TransportManager { * Lock for registered transports and currently selected transport. * * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport - * code being executed such as {@link TransportClient#connect(String)}} and its variants should - * be made with this lock held, risk of deadlock. + * code being executed such as {@link TransportConnection#connect(String)}} and its variants + * should be made with this lock held, risk of deadlock. */ private final Object mTransportLock = new Object(); @@ -94,7 +94,8 @@ public class TransportManager { mTransportWhitelist = Preconditions.checkNotNull(whitelist); mCurrentTransportName = selectedTransport; mTransportStats = new TransportStats(); - mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats); + mTransportConnectionManager = new TransportConnectionManager(mUserId, context, + mTransportStats); } @VisibleForTesting @@ -103,13 +104,13 @@ public class TransportManager { Context context, Set<ComponentName> whitelist, String selectedTransport, - TransportClientManager transportClientManager) { + TransportConnectionManager transportConnectionManager) { mUserId = userId; mPackageManager = context.getPackageManager(); mTransportWhitelist = Preconditions.checkNotNull(whitelist); mCurrentTransportName = selectedTransport; mTransportStats = new TransportStats(); - mTransportClientManager = transportClientManager; + mTransportConnectionManager = transportConnectionManager; } /* Sets a listener to be called whenever a transport is registered. */ @@ -307,7 +308,7 @@ public class TransportManager { * transportConsumer}. * * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of - * {@link TransportClient#connect(String)} here, otherwise you risk deadlock. + * {@link TransportConnection#connect(String)} here, otherwise you risk deadlock. */ public void forEachRegisteredTransport(Consumer<String> transportConsumer) { synchronized (mTransportLock) { @@ -407,17 +408,17 @@ public class TransportManager { } /** - * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not + * Returns a {@link TransportConnection} for {@code transportName} or {@code null} if not * registered. * * @param transportName The name of the transport. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not registered. + * @return A {@link TransportConnection} or null if not registered. */ @Nullable - public TransportClient getTransportClient(String transportName, String caller) { + public TransportConnection getTransportClient(String transportName, String caller) { try { return getTransportClientOrThrow(transportName, caller); } catch (TransportNotRegisteredException e) { @@ -427,38 +428,38 @@ public class TransportManager { } /** - * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. + * Returns a {@link TransportConnection} for {@code transportName} or throws if not registered. * * @param transportName The name of the transport. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient}. + * @return A {@link TransportConnection}. * @throws TransportNotRegisteredException if the transport is not registered. */ - public TransportClient getTransportClientOrThrow(String transportName, String caller) + public TransportConnection getTransportClientOrThrow(String transportName, String caller) throws TransportNotRegisteredException { synchronized (mTransportLock) { ComponentName component = getRegisteredTransportComponentLocked(transportName); if (component == null) { throw new TransportNotRegisteredException(transportName); } - return mTransportClientManager.getTransportClient(component, caller); + return mTransportConnectionManager.getTransportClient(component, caller); } } /** - * Returns a {@link TransportClient} for the current transport or {@code null} if not + * Returns a {@link TransportConnection} for the current transport or {@code null} if not * registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not registered. + * @return A {@link TransportConnection} or null if not registered. * @throws IllegalStateException if no transport is selected. */ @Nullable - public TransportClient getCurrentTransportClient(String caller) { + public TransportConnection getCurrentTransportClient(String caller) { if (mCurrentTransportName == null) { throw new IllegalStateException("No transport selected"); } @@ -468,16 +469,16 @@ public class TransportManager { } /** - * Returns a {@link TransportClient} for the current transport or throws if not registered. + * Returns a {@link TransportConnection} for the current transport or throws if not registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient}. + * @return A {@link TransportConnection}. * @throws TransportNotRegisteredException if the transport is not registered. * @throws IllegalStateException if no transport is selected. */ - public TransportClient getCurrentTransportClientOrThrow(String caller) + public TransportConnection getCurrentTransportClientOrThrow(String caller) throws TransportNotRegisteredException { if (mCurrentTransportName == null) { throw new IllegalStateException("No transport selected"); @@ -488,15 +489,15 @@ public class TransportManager { } /** - * Disposes of the {@link TransportClient}. + * Disposes of the {@link TransportConnection}. * - * @param transportClient The {@link TransportClient} to be disposed of. + * @param transportConnection The {@link TransportConnection} to be disposed of. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. */ - public void disposeOfTransportClient(TransportClient transportClient, String caller) { - mTransportClientManager.disposeOfTransportClient(transportClient, caller); + public void disposeOfTransportClient(TransportConnection transportConnection, String caller) { + mTransportConnectionManager.disposeOfTransportClient(transportConnection, caller); } /** @@ -637,15 +638,16 @@ public class TransportManager { Bundle extras = new Bundle(); extras.putBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION, true); - TransportClient transportClient = - mTransportClientManager.getTransportClient( + TransportConnection transportConnection = + mTransportConnectionManager.getTransportClient( transportComponent, extras, callerLogString); final IBackupTransport transport; try { - transport = transportClient.connectOrThrow(callerLogString); + transport = transportConnection.connectOrThrow(callerLogString); } catch (TransportNotAvailableException e) { Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration"); - mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); + mTransportConnectionManager.disposeOfTransportClient(transportConnection, + callerLogString); return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } @@ -667,7 +669,7 @@ public class TransportManager { result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } - mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); + mTransportConnectionManager.disposeOfTransportClient(transportConnection, callerLogString); return result; } @@ -695,7 +697,7 @@ public class TransportManager { } public void dumpTransportClients(PrintWriter pw) { - mTransportClientManager.dump(pw); + mTransportConnectionManager.dump(pw); } public void dumpTransportStats(PrintWriter pw) { diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java new file mode 100644 index 000000000000..a3f6eb6f9842 --- /dev/null +++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.backup.transport; + +import android.annotation.Nullable; +import android.app.backup.RestoreDescription; +import android.app.backup.RestoreSet; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; + +import com.android.internal.backup.IBackupTransport; + +/** + * Client to {@link com.android.internal.backup.IBackupTransport}. Manages the call to the remote + * transport service and delivers the results. + */ +public class BackupTransportClient { + private final IBackupTransport mTransportBinder; + + BackupTransportClient(IBackupTransport transportBinder) { + mTransportBinder = transportBinder; + } + + /** + * See {@link IBackupTransport#name()}. + */ + public String name() throws RemoteException { + return mTransportBinder.name(); + } + + /** + * See {@link IBackupTransport#configurationIntent()} + */ + public Intent configurationIntent() throws RemoteException { + return mTransportBinder.configurationIntent(); + } + + /** + * See {@link IBackupTransport#currentDestinationString()} + */ + public String currentDestinationString() throws RemoteException { + return mTransportBinder.currentDestinationString(); + } + + /** + * See {@link IBackupTransport#dataManagementIntent()} + */ + public Intent dataManagementIntent() throws RemoteException { + return mTransportBinder.dataManagementIntent(); + } + + /** + * See {@link IBackupTransport#dataManagementIntentLabel()} + */ + @Nullable + public CharSequence dataManagementIntentLabel() throws RemoteException { + return mTransportBinder.dataManagementIntentLabel(); + } + + /** + * See {@link IBackupTransport#transportDirName()} + */ + public String transportDirName() throws RemoteException { + return mTransportBinder.transportDirName(); + } + + /** + * See {@link IBackupTransport#initializeDevice()} + */ + public int initializeDevice() throws RemoteException { + return mTransportBinder.initializeDevice(); + } + + /** + * See {@link IBackupTransport#clearBackupData(PackageInfo)} + */ + public int clearBackupData(PackageInfo packageInfo) throws RemoteException { + return mTransportBinder.clearBackupData(packageInfo); + } + + /** + * See {@link IBackupTransport#finishBackup()} + */ + public int finishBackup() throws RemoteException { + return mTransportBinder.finishBackup(); + } + + /** + * See {@link IBackupTransport#requestBackupTime()} + */ + public long requestBackupTime() throws RemoteException { + return mTransportBinder.requestBackupTime(); + } + + /** + * See {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)} + */ + public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) + throws RemoteException { + return mTransportBinder.performBackup(packageInfo, inFd, flags); + } + + /** + * See {@link IBackupTransport#getAvailableRestoreSets()} + */ + public RestoreSet[] getAvailableRestoreSets() throws RemoteException { + return mTransportBinder.getAvailableRestoreSets(); + } + + /** + * See {@link IBackupTransport#getCurrentRestoreSet()} + */ + public long getCurrentRestoreSet() throws RemoteException { + return mTransportBinder.getCurrentRestoreSet(); + } + + /** + * See {@link IBackupTransport#startRestore(long, PackageInfo[])} + */ + public int startRestore(long token, PackageInfo[] packages) throws RemoteException { + return mTransportBinder.startRestore(token, packages); + } + + /** + * See {@link IBackupTransport#nextRestorePackage()} + */ + public RestoreDescription nextRestorePackage() throws RemoteException { + return mTransportBinder.nextRestorePackage(); + } + + /** + * See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)} + */ + public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { + return mTransportBinder.getRestoreData(outFd); + } + + /** + * See {@link IBackupTransport#finishRestore()} + */ + public void finishRestore() throws RemoteException { + mTransportBinder.finishRestore(); + } + + /** + * See {@link IBackupTransport#requestFullBackupTime()} + */ + public long requestFullBackupTime() throws RemoteException { + return mTransportBinder.requestFullBackupTime(); + } + + /** + * See {@link IBackupTransport#performFullBackup(PackageInfo, ParcelFileDescriptor, int)} + */ + public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, + int flags) throws RemoteException { + return mTransportBinder.performFullBackup(targetPackage, socket, flags); + } + + /** + * See {@link IBackupTransport#checkFullBackupSize(long)} + */ + public int checkFullBackupSize(long size) throws RemoteException { + return mTransportBinder.checkFullBackupSize(size); + } + + /** + * See {@link IBackupTransport#sendBackupData(int)} + */ + public int sendBackupData(int numBytes) throws RemoteException { + return mTransportBinder.sendBackupData(numBytes); + } + + /** + * See {@link IBackupTransport#cancelFullBackup()} + */ + public void cancelFullBackup() throws RemoteException { + mTransportBinder.cancelFullBackup(); + } + + /** + * See {@link IBackupTransport#isAppEligibleForBackup(PackageInfo, boolean)} + */ + public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) + throws RemoteException { + return mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup); + } + + /** + * See {@link IBackupTransport#getBackupQuota(String, boolean)} + */ + public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException { + return mTransportBinder.getBackupQuota(packageName, isFullBackup); + } + + /** + * See {@link IBackupTransport#getNextFullRestoreDataChunk(ParcelFileDescriptor)} + */ + public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException { + return mTransportBinder.getNextFullRestoreDataChunk(socket); + } + + /** + * See {@link IBackupTransport#abortFullRestore()} + */ + public int abortFullRestore() throws RemoteException { + return mTransportBinder.abortFullRestore(); + } + + /** + * See {@link IBackupTransport#getTransportFlags()} + */ + public int getTransportFlags() throws RemoteException { + return mTransportBinder.getTransportFlags(); + } +} diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java b/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java deleted file mode 100644 index ab870803e60d..000000000000 --- a/services/backup/backuplib/java/com/android/server/backup/transport/DelegatingTransport.java +++ /dev/null @@ -1,414 +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.server.backup.transport; - -import android.app.backup.BackupAgent; -import android.app.backup.BackupTransport; -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; - -import com.android.internal.backup.IBackupTransport; - -/** - * Delegates all transport methods to the delegate() implemented in the derived class. - */ -public abstract class DelegatingTransport extends IBackupTransport.Stub { - protected abstract IBackupTransport getDelegate() throws RemoteException; - - /** - * Ask the transport for the name under which it should be registered. This will - * typically be its host service's component name, but need not be. - */ - @Override - public String name() throws RemoteException { - return getDelegate().name(); - } - - /** - * Ask the transport for an Intent that can be used to launch any internal - * configuration Activity that it wishes to present. For example, the transport - * may offer a UI for allowing the user to supply login credentials for the - * transport's off-device backend. - * - * If the transport does not supply any user-facing configuration UI, it should - * return null from this method. - * - * @return An Intent that can be passed to Context.startActivity() in order to - * launch the transport's configuration UI. This method will return null - * if the transport does not offer any user-facing configuration UI. - */ - @Override - public Intent configurationIntent() throws RemoteException { - return getDelegate().configurationIntent(); - } - - /** - * On demand, supply a one-line string that can be shown to the user that - * describes the current backend destination. For example, a transport that - * can potentially associate backup data with arbitrary user accounts should - * include the name of the currently-active account here. - * - * @return A string describing the destination to which the transport is currently - * sending data. This method should not return null. - */ - @Override - public String currentDestinationString() throws RemoteException { - return getDelegate().currentDestinationString(); - } - - /** - * Ask the transport for an Intent that can be used to launch a more detailed - * secondary data management activity. For example, the configuration intent might - * be one for allowing the user to select which account they wish to associate - * their backups with, and the management intent might be one which presents a - * UI for managing the data on the backend. - * - * <p>In the Settings UI, the configuration intent will typically be invoked - * when the user taps on the preferences item labeled with the current - * destination string, and the management intent will be placed in an overflow - * menu labelled with the management label string. - * - * <p>If the transport does not supply any user-facing data management - * UI, then it should return {@code null} from this method. - * - * @return An intent that can be passed to Context.startActivity() in order to - * launch the transport's data-management UI. This method will return - * {@code null} if the transport does not offer any user-facing data - * management UI. - */ - @Override - public Intent dataManagementIntent() throws RemoteException { - return getDelegate().dataManagementIntent(); - } - - /** - * On demand, supply a short {@link CharSequence} that can be shown to the user as the - * label on - * an overflow menu item used to invoke the data management UI. - * - * @return A {@link CharSequence} to be used as the label for the transport's data management - * affordance. If the transport supplies a data management intent, this - * method must not return {@code null}. - */ - @Override - public CharSequence dataManagementIntentLabel() throws RemoteException { - return getDelegate().dataManagementIntentLabel(); - } - - /** - * Ask the transport where, on local device storage, to keep backup state blobs. - * This is per-transport so that mock transports used for testing can coexist with - * "live" backup services without interfering with the live bookkeeping. The - * returned string should be a name that is expected to be unambiguous among all - * available backup transports; the name of the class implementing the transport - * is a good choice. This MUST be constant. - * - * @return A unique name, suitable for use as a file or directory name, that the - * Backup Manager could use to disambiguate state files associated with - * different backup transports. - */ - @Override - public String transportDirName() throws RemoteException { - return getDelegate().transportDirName(); - } - - /** - * Verify that this is a suitable time for a backup pass. This should return zero - * if a backup is reasonable right now, some positive value otherwise. This method - * will be called outside of the {@link #startSession}/{@link #endSession} pair. - * - * <p>If this is not a suitable time for a backup, the transport should return a - * backoff delay, in milliseconds, after which the Backup Manager should try again. - * - * @return Zero if this is a suitable time for a backup pass, or a positive time delay - * in milliseconds to suggest deferring the backup pass for a while. - */ - @Override - public long requestBackupTime() throws RemoteException { - return getDelegate().requestBackupTime(); - } - - /** - * Initialize the server side storage for this device, erasing all stored data. - * The transport may send the request immediately, or may buffer it. After - * this is called, {@link #finishBackup} must be called to ensure the request - * is sent and received successfully. - * - * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or - * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure). - */ - @Override - public int initializeDevice() throws RemoteException { - return getDelegate().initializeDevice(); - } - - /** - * Send one application's data to the backup destination. The transport may send - * the data immediately, or may buffer it. After this is called, {@link #finishBackup} - * must be called to ensure the data is sent and recorded successfully. - * - * @param packageInfo The identity of the application whose data is being backed up. - * This specifically includes the signature list for the package. - * @param inFd Descriptor of file with data that resulted from invoking the application's - * BackupService.doBackup() method. This may be a pipe rather than a file on - * persistent media, so it may not be seekable. - * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}. - * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far), - * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or - * {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has - * become lost due to inactive expiry or some other reason and needs re-initializing) - */ - @Override - public int performBackup(PackageInfo packageInfo, - ParcelFileDescriptor inFd, int flags) throws RemoteException { - return getDelegate().performBackup(packageInfo, inFd, flags); - } - - /** - * Erase the give application's data from the backup destination. This clears - * out the given package's data from the current backup set, making it as though - * the app had never yet been backed up. After this is called, {@link finishBackup} - * must be called to ensure that the operation is recorded successfully. - * - * @return the same error codes as {@link #performBackup}. - * @param packageInfo - */ - @Override - public int clearBackupData(PackageInfo packageInfo) throws RemoteException { - return getDelegate().clearBackupData(packageInfo); - } - - /** - * Finish sending application data to the backup destination. This must be - * called after {@link #performBackup} or {@link clearBackupData} to ensure that - * all data is sent. Only when this method returns true can a backup be assumed - * to have succeeded. - * - * @return the same error codes as {@link #performBackup}. - */ - @Override - public int finishBackup() throws RemoteException { - return getDelegate().finishBackup(); - } - - /** - * Get the set of all backups currently available over this transport. - * - * @return Descriptions of the set of restore images available for this device, - * or null if an error occurred (the attempt should be rescheduled). - **/ - @Override - public RestoreSet[] getAvailableRestoreSets() throws RemoteException { - return getDelegate().getAvailableRestoreSets(); - } - - /** - * Get the identifying token of the backup set currently being stored from - * this device. This is used in the case of applications wishing to restore - * their last-known-good data. - * - * @return A token that can be passed to {@link #startRestore}, or 0 if there - * is no backup set available corresponding to the current device state. - */ - @Override - public long getCurrentRestoreSet() throws RemoteException { - return getDelegate().getCurrentRestoreSet(); - } - - /** - * Start restoring application data from backup. After calling this function, - * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData} - * to walk through the actual application data. - * - * @param token A backup token as returned by {@link #getAvailableRestoreSets} - * or {@link #getCurrentRestoreSet}. - * @param packages List of applications to restore (if data is available). - * Application data will be restored in the order given. - * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call - * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR} - * (an error occurred, the restore should be aborted and rescheduled). - */ - @Override - public int startRestore(long token, PackageInfo[] packages) throws RemoteException { - return getDelegate().startRestore(token, packages); - } - - /** - * Get the package name of the next application with data in the backup store, plus - * a description of the structure of the restored archive: either TYPE_KEY_VALUE for - * an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream. - * - * <p>If the package name in the returned RestoreDescription object is the singleton - * {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available - * in the current restore session: all packages described in startRestore() have been - * processed. - * - * <p>If this method returns {@code null}, it means that a transport-level error has - * occurred and the entire restore operation should be abandoned. - * - * @return A RestoreDescription object containing the name of one of the packages - * supplied to {@link #startRestore} plus an indicator of the data type of that - * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that - * no more packages can be restored in this session; or {@code null} to indicate - * a transport-level error. - */ - @Override - public RestoreDescription nextRestorePackage() throws RemoteException { - return getDelegate().nextRestorePackage(); - } - - /** - * Get the data for the application returned by {@link #nextRestorePackage}. - * - * @param outFd An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #startRestore}. - */ - @Override - public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { - return getDelegate().getRestoreData(outFd); - } - - /** - * End a restore session (aborting any in-process data transfer as necessary), - * freeing any resources and connections used during the restore process. - */ - @Override - public void finishRestore() throws RemoteException { - getDelegate().finishRestore(); - } - - @Override - public long requestFullBackupTime() throws RemoteException { - return getDelegate().requestFullBackupTime(); - } - - @Override - public int performFullBackup(PackageInfo targetPackage, - ParcelFileDescriptor socket, int flags) throws RemoteException { - return getDelegate().performFullBackup(targetPackage, socket, flags); - } - - @Override - public int checkFullBackupSize(long size) throws RemoteException { - return getDelegate().checkFullBackupSize(size); - } - - @Override - public int sendBackupData(int numBytes) throws RemoteException { - return getDelegate().sendBackupData(numBytes); - } - - @Override - public void cancelFullBackup() throws RemoteException { - getDelegate().cancelFullBackup(); - } - - /** - * Ask the transport whether this app is eligible for backup. - * - * @param targetPackage The identity of the application. - * @param isFullBackup If set, transport should check if app is eligible for full data backup, - * otherwise to check if eligible for key-value backup. - * @return Whether this app is eligible for backup. - */ - @Override - public boolean isAppEligibleForBackup(PackageInfo targetPackage, - boolean isFullBackup) throws RemoteException { - return getDelegate().isAppEligibleForBackup(targetPackage, isFullBackup); - } - - /** - * Ask the transport about current quota for backup size of the package. - * - * @param packageName ID of package to provide the quota. - * @param isFullBackup If set, transport should return limit for full data backup, otherwise - * for key-value backup. - * @return Current limit on full data backup size in bytes. - */ - @Override - public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException { - return getDelegate().getBackupQuota(packageName, isFullBackup); - } - - /** - * Ask the transport to provide data for the "current" package being restored. This - * is the package that was just reported by {@link #nextRestorePackage()} as having - * {@link RestoreDescription#TYPE_FULL_STREAM} data. - * - * The transport writes some data to the socket supplied to this call, and returns - * the number of bytes written. The system will then read that many bytes and - * stream them to the application's agent for restore, then will call this method again - * to receive the next chunk of the archive. This sequence will be repeated until the - * transport returns zero indicating that all of the package's data has been delivered - * (or returns a negative value indicating some sort of hard error condition at the - * transport level). - * - * <p>After this method returns zero, the system will then call - * {@link #getNextFullRestorePackage()} to begin the restore process for the next - * application, and the sequence begins again. - * - * <p>The transport should always close this socket when returning from this method. - * Do not cache this socket across multiple calls or you may leak file descriptors. - * - * @param socket The file descriptor that the transport will use for delivering the - * streamed archive. The transport must close this socket in all cases when returning - * from this method. - * @return 0 when no more data for the current package is available. A positive value - * indicates the presence of that many bytes to be delivered to the app. Any negative - * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, - * indicating a fatal error condition that precludes further restore operations - * on the current dataset. - */ - @Override - public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException { - return getDelegate().getNextFullRestoreDataChunk(socket); - } - - /** - * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} - * data for restore, it will invoke this method to tell the transport that it should - * abandon the data download for the current package. The OS will then either call - * {@link #nextRestorePackage()} again to move on to restoring the next package in the - * set being iterated over, or will call {@link #finishRestore()} to shut down the restore - * operation. - * - * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the - * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious - * transport-level failure. If the transport reports an error here, the entire restore - * operation will immediately be finished with no further attempts to restore app data. - */ - @Override - public int abortFullRestore() throws RemoteException { - return getDelegate().abortFullRestore(); - } - - /** - * Returns flags with additional information about the transport, which is accessible to the - * {@link BackupAgent}. This allows the agent to decide what to backup or - * restore based on properties of the transport. - * - * <p>For supported flags see {@link BackupAgent}. - */ - @Override - public int getTransportFlags() throws RemoteException { - return getDelegate().getTransportFlags(); - } -} diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java index 0eb3ea3e1bc8..da77eba083c3 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java @@ -59,16 +59,17 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; /** - * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained - * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is - * responsible for only one connection to the transport service, not more. + * A {@link TransportConnection} manages the connection to an {@link IBackupTransport} service, + * obtained via the {@param bindIntent} parameter provided in the constructor. A + * {@link TransportConnection} is responsible for only one connection to the transport service, + * not more. * * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can * call either {@link #connect(String)}, if you can block your thread, or {@link * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport. * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly - * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}. + * via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}. * * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around. * @@ -76,8 +77,8 @@ import java.util.concurrent.ExecutionException; * * @see TransportManager */ -public class TransportClient { - @VisibleForTesting static final String TAG = "TransportClient"; +public class TransportConnection { + @VisibleForTesting static final String TAG = "TransportConnection"; private static final int LOG_BUFFER_SIZE = 5; private final @UserIdInt int mUserId; @@ -107,7 +108,7 @@ public class TransportClient { @GuardedBy("mStateLock") private volatile IBackupTransport mTransport; - TransportClient( + TransportConnection( @UserIdInt int userId, Context context, TransportStats transportStats, @@ -127,7 +128,7 @@ public class TransportClient { } @VisibleForTesting - TransportClient( + TransportConnection( @UserIdInt int userId, Context context, TransportStats transportStats, @@ -144,7 +145,7 @@ public class TransportClient { mIdentifier = identifier; mCreatorLogString = caller; mListenerHandler = listenerHandler; - mConnection = new TransportConnection(context, this); + mConnection = new TransportConnectionMonitor(context, this); // For logging String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", ""); @@ -192,7 +193,7 @@ public class TransportClient { * For unusable transport binders check {@link DeadObjectException}. * * @param listener The listener that will be called with the (possibly null or unusable) {@link - * IBackupTransport} instance and this {@link TransportClient} object. + * IBackupTransport} instance and this {@link TransportConnection} object. * @param caller A {@link String} identifying the caller for logging/debugging purposes. This * should be a human-readable short string that is easily identifiable in the logs. Ideally * TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very @@ -373,8 +374,8 @@ public class TransportClient { } /** - * If the {@link TransportClient} is already connected to the transport, returns the transport, - * otherwise throws {@link TransportNotAvailableException}. + * If the {@link TransportConnection} is already connected to the transport, returns the + * transport, otherwise throws {@link TransportNotAvailableException}. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check * {@link #connectAsync(TransportConnectionListener, String)} for more details. @@ -647,19 +648,20 @@ public class TransportClient { * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message. */ - private static class TransportConnection implements ServiceConnection { + private static class TransportConnectionMonitor implements ServiceConnection { private final Context mContext; - private final WeakReference<TransportClient> mTransportClientRef; + private final WeakReference<TransportConnection> mTransportClientRef; - private TransportConnection(Context context, TransportClient transportClient) { + private TransportConnectionMonitor(Context context, + TransportConnection transportConnection) { mContext = context; - mTransportClientRef = new WeakReference<>(transportClient); + mTransportClientRef = new WeakReference<>(transportConnection); } @Override public void onServiceConnected(ComponentName transportComponent, IBinder binder) { - TransportClient transportClient = mTransportClientRef.get(); - if (transportClient == null) { + TransportConnection transportConnection = mTransportClientRef.get(); + if (transportConnection == null) { referenceLost("TransportConnection.onServiceConnected()"); return; } @@ -667,30 +669,30 @@ public class TransportClient { // In short-term, blocking calls are OK as the transports come from the allowlist at // {@link SystemConfig#getBackupTransportWhitelist()} Binder.allowBlocking(binder); - transportClient.onServiceConnected(binder); + transportConnection.onServiceConnected(binder); } @Override public void onServiceDisconnected(ComponentName transportComponent) { - TransportClient transportClient = mTransportClientRef.get(); - if (transportClient == null) { + TransportConnection transportConnection = mTransportClientRef.get(); + if (transportConnection == null) { referenceLost("TransportConnection.onServiceDisconnected()"); return; } - transportClient.onServiceDisconnected(); + transportConnection.onServiceDisconnected(); } @Override public void onBindingDied(ComponentName transportComponent) { - TransportClient transportClient = mTransportClientRef.get(); - if (transportClient == null) { + TransportConnection transportConnection = mTransportClientRef.get(); + if (transportConnection == null) { referenceLost("TransportConnection.onBindingDied()"); return; } - transportClient.onBindingDied(); + transportConnection.onBindingDied(); } - /** @see TransportClient#finalize() */ + /** @see TransportConnection#finalize() */ private void referenceLost(String caller) { mContext.unbindService(this); TransportUtils.log( diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java index 1ccffd01d12c..03d35e46952c 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java @@ -21,17 +21,18 @@ import android.annotation.Nullable; import com.android.internal.backup.IBackupTransport; /** - * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener, + * Listener to be called by {@link TransportConnection#connectAsync(TransportConnectionListener, * String)}. */ public interface TransportConnectionListener { /** - * Called when {@link TransportClient} has a transport binder available or that it decided it - * couldn't obtain one, in which case {@param transport} is null. + * Called when {@link TransportConnection} has a transport binder available or that it decided + * it couldn't obtain one, in which case {@param transport} is null. * * @param transport A {@link IBackupTransport} transport binder or null. - * @param transportClient The {@link TransportClient} used to retrieve this transport binder. + * @param transportConnection The {@link TransportConnection} used to retrieve this transport + * binder. */ void onTransportConnectionResult( - @Nullable IBackupTransport transport, TransportClient transportClient); + @Nullable IBackupTransport transport, TransportConnection transportConnection); } diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionManager.java index 72b1ee741d95..16acb18d5025 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionManager.java @@ -36,38 +36,21 @@ import java.util.WeakHashMap; import java.util.function.Function; /** - * Manages the creation and disposal of {@link TransportClient}s. The only class that should use + * Manages the creation and disposal of {@link TransportConnection}s. The only class that should use * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}. */ -public class TransportClientManager { - private static final String TAG = "TransportClientManager"; - private static final String SERVICE_ACTION_ENCRYPTING_TRANSPORT = - "android.encryption.BACKUP_ENCRYPTION"; - private static final ComponentName ENCRYPTING_TRANSPORT = new ComponentName( - "com.android.server.backup.encryption", - "com.android.server.backup.encryption.BackupEncryptionService"); - private static final String ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY = "transport"; +public class TransportConnectionManager { + private static final String TAG = "TransportConnectionManager"; private final @UserIdInt int mUserId; private final Context mContext; private final TransportStats mTransportStats; private final Object mTransportClientsLock = new Object(); private int mTransportClientsCreated = 0; - private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>(); + private Map<TransportConnection, String> mTransportClientsCallerMap = new WeakHashMap<>(); private final Function<ComponentName, Intent> mIntentFunction; /** - * Return an {@link Intent} which resolves to an intermediate {@link IBackupTransport} that - * encrypts (or decrypts) the data when sending it (or receiving it) from the {@link - * IBackupTransport} for the given {@link ComponentName}. - */ - public static Intent getEncryptingTransportIntent(ComponentName tranportComponent) { - return new Intent(SERVICE_ACTION_ENCRYPTING_TRANSPORT) - .setComponent(ENCRYPTING_TRANSPORT) - .putExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY, tranportComponent); - } - - /** * Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link * ComponentName}. */ @@ -75,38 +58,12 @@ public class TransportClientManager { return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent); } - /** - * Given a {@link Intent} originally created by {@link - * #getEncryptingTransportIntent(ComponentName)}, returns the {@link Intent} which resolves to - * the {@link IBackupTransport} for that {@link ComponentName}. - */ - public static Intent getRealTransportIntent(Intent encryptingTransportIntent) { - ComponentName transportComponent = encryptingTransportIntent.getParcelableExtra( - ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY); - Intent intent = getRealTransportIntent(transportComponent) - .putExtras(encryptingTransportIntent.getExtras()); - intent.removeExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY); - return intent; - } - - /** - * Create a {@link TransportClientManager} such that {@link #getTransportClient(ComponentName, - * Bundle, String)} returns a {@link TransportClient} which connects to an intermediate {@link - * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from - * the {@link IBackupTransport} for the given {@link ComponentName}. - */ - public static TransportClientManager createEncryptingClientManager(@UserIdInt int userId, - Context context, TransportStats transportStats) { - return new TransportClientManager(userId, context, transportStats, - TransportClientManager::getEncryptingTransportIntent); - } - - public TransportClientManager(@UserIdInt int userId, Context context, + public TransportConnectionManager(@UserIdInt int userId, Context context, TransportStats transportStats) { - this(userId, context, transportStats, TransportClientManager::getRealTransportIntent); + this(userId, context, transportStats, TransportConnectionManager::getRealTransportIntent); } - private TransportClientManager(@UserIdInt int userId, Context context, + private TransportConnectionManager(@UserIdInt int userId, Context context, TransportStats transportStats, Function<ComponentName, Intent> intentFunction) { mUserId = userId; mContext = context; @@ -115,31 +72,31 @@ public class TransportClientManager { } /** - * Retrieves a {@link TransportClient} for the transport identified by {@param + * Retrieves a {@link TransportConnection} for the transport identified by {@param * transportComponent}. * * @param transportComponent The {@link ComponentName} of the transport. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient}. + * @return A {@link TransportConnection}. */ - public TransportClient getTransportClient(ComponentName transportComponent, String caller) { + public TransportConnection getTransportClient(ComponentName transportComponent, String caller) { return getTransportClient(transportComponent, null, caller); } /** - * Retrieves a {@link TransportClient} for the transport identified by {@param + * Retrieves a {@link TransportConnection} for the transport identified by {@param * transportComponent} whose binding intent will have the {@param extras} extras. * * @param transportComponent The {@link ComponentName} of the transport. * @param extras A {@link Bundle} of extras to pass to the binding intent. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient}. + * @return A {@link TransportConnection}. */ - public TransportClient getTransportClient( + public TransportConnection getTransportClient( ComponentName transportComponent, @Nullable Bundle extras, String caller) { Intent bindIntent = mIntentFunction.apply(transportComponent); if (extras != null) { @@ -148,11 +105,11 @@ public class TransportClientManager { return getTransportClient(transportComponent, caller, bindIntent); } - private TransportClient getTransportClient( + private TransportConnection getTransportClient( ComponentName transportComponent, String caller, Intent bindIntent) { synchronized (mTransportClientsLock) { - TransportClient transportClient = - new TransportClient( + TransportConnection transportConnection = + new TransportConnection( mUserId, mContext, mTransportStats, @@ -160,33 +117,33 @@ public class TransportClientManager { transportComponent, Integer.toString(mTransportClientsCreated), caller); - mTransportClientsCallerMap.put(transportClient, caller); + mTransportClientsCallerMap.put(transportConnection, caller); mTransportClientsCreated++; TransportUtils.log( Priority.DEBUG, TAG, - formatMessage(null, caller, "Retrieving " + transportClient)); - return transportClient; + formatMessage(null, caller, "Retrieving " + transportConnection)); + return transportConnection; } } /** - * Disposes of the {@link TransportClient}. + * Disposes of the {@link TransportConnection}. * - * @param transportClient The {@link TransportClient} to be disposed of. + * @param transportConnection The {@link TransportConnection} to be disposed of. * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. */ - public void disposeOfTransportClient(TransportClient transportClient, String caller) { - transportClient.unbind(caller); - transportClient.markAsDisposed(); + public void disposeOfTransportClient(TransportConnection transportConnection, String caller) { + transportConnection.unbind(caller); + transportConnection.markAsDisposed(); synchronized (mTransportClientsLock) { TransportUtils.log( Priority.DEBUG, TAG, - formatMessage(null, caller, "Disposing of " + transportClient)); - mTransportClientsCallerMap.remove(transportClient); + formatMessage(null, caller, "Disposing of " + transportConnection)); + mTransportClientsCallerMap.remove(transportConnection); } } @@ -194,10 +151,10 @@ public class TransportClientManager { pw.println("Transport clients created: " + mTransportClientsCreated); synchronized (mTransportClientsLock) { pw.println("Current transport clients: " + mTransportClientsCallerMap.size()); - for (TransportClient transportClient : mTransportClientsCallerMap.keySet()) { - String caller = mTransportClientsCallerMap.get(transportClient); - pw.println(" " + transportClient + " [" + caller + "]"); - for (String logEntry : transportClient.getLogBuffer()) { + for (TransportConnection transportConnection : mTransportClientsCallerMap.keySet()) { + String caller = mTransportClientsCallerMap.get(transportConnection); + pw.println(" " + transportConnection + " [" + caller + "]"); + for (String logEntry : transportConnection.getLogBuffer()) { pw.println(" " + logEntry); } } diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java index c08eb7f4a54e..83b2782d3cf2 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java @@ -22,10 +22,10 @@ import com.android.internal.backup.IBackupTransport; /** * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link - * TransportClient} connection attempt fails. Check {@link - * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens. + * TransportConnection} connection attempt fails. Check {@link + * TransportConnection#connectAsync(TransportConnectionListener, String)} for when that happens. * - * @see TransportClient#connectAsync(TransportConnectionListener, String) + * @see TransportConnection#connectAsync(TransportConnectionListener, String) */ public class TransportNotAvailableException extends AndroidException { TransportNotAvailableException() { diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java index bd84782122ad..c67a5b65380e 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java @@ -25,7 +25,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -/** Responsible for aggregating {@link TransportClient} relevant times. */ +/** Responsible for aggregating {@link TransportConnection} relevant times. */ public class TransportStats { private final Object mStatsLock = new Object(); private final Map<ComponentName, Stats> mTransportStats = new HashMap<>(); diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 1a5d91c8cca5..452adb294540 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -127,7 +127,7 @@ import com.android.server.backup.params.ClearRetryParams; import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.ActiveRestoreSession; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.backup.utils.BackupEligibilityRules; @@ -1894,16 +1894,16 @@ public class UserBackupManagerService { return BackupManager.ERROR_BACKUP_NOT_ALLOWED; } - final TransportClient transportClient; + final TransportConnection transportConnection; final String transportDirName; int operationType; try { transportDirName = mTransportManager.getTransportDirName( mTransportManager.getCurrentTransportName()); - transportClient = + transportConnection = mTransportManager.getCurrentTransportClientOrThrow("BMS.requestBackup()"); - operationType = getOperationTypeFromTransport(transportClient); + operationType = getOperationTypeFromTransport(transportConnection); } catch (TransportNotRegisteredException | TransportNotAvailableException | RemoteException e) { BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED); @@ -1914,13 +1914,13 @@ public class UserBackupManagerService { } OnTaskFinishedListener listener = - caller -> mTransportManager.disposeOfTransportClient(transportClient, caller); + caller -> mTransportManager.disposeOfTransportClient(transportConnection, caller); BackupEligibilityRules backupEligibilityRules = getEligibilityRulesForOperation( operationType); Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP); msg.obj = getRequestBackupParams(packages, observer, monitor, flags, backupEligibilityRules, - transportClient, transportDirName, listener); + transportConnection, transportDirName, listener); mBackupHandler.sendMessage(msg); return BackupManager.SUCCESS; } @@ -1928,7 +1928,7 @@ public class UserBackupManagerService { @VisibleForTesting BackupParams getRequestBackupParams(String[] packages, IBackupObserver observer, IBackupManagerMonitor monitor, int flags, BackupEligibilityRules backupEligibilityRules, - TransportClient transportClient, String transportDirName, + TransportConnection transportConnection, String transportDirName, OnTaskFinishedListener listener) { ArrayList<String> fullBackupList = new ArrayList<>(); ArrayList<String> kvBackupList = new ArrayList<>(); @@ -1974,7 +1974,7 @@ public class UserBackupManagerService { boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0; - return new BackupParams(transportClient, transportDirName, kvBackupList, fullBackupList, + return new BackupParams(transportConnection, transportDirName, kvBackupList, fullBackupList, observer, monitor, listener, /* userInitiated */ true, nonIncrementalBackup, backupEligibilityRules); } @@ -2875,10 +2875,10 @@ public class UserBackupManagerService { } mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager .getTransportClient(transportName, "BMS.clearBackupData()"); - if (transportClient == null) { + if (transportConnection == null) { // transport is currently unregistered -- make sure to retry Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, new ClearRetryParams(transportName, packageName)); @@ -2888,11 +2888,11 @@ public class UserBackupManagerService { final long oldId = Binder.clearCallingIdentity(); try { OnTaskFinishedListener listener = caller -> mTransportManager - .disposeOfTransportClient(transportClient, caller); + .disposeOfTransportClient(transportConnection, caller); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage( MSG_RUN_CLEAR, - new ClearParams(transportClient, info, listener)); + new ClearParams(transportConnection, info, listener)); mBackupHandler.sendMessage(msg); } finally { Binder.restoreCallingIdentity(oldId); @@ -3715,11 +3715,11 @@ public class UserBackupManagerService { // And update our current-dataset bookkeeping String callerLogString = "BMS.updateStateForTransport()"; - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getTransportClient(newTransportName, callerLogString); - if (transportClient != null) { + if (transportConnection != null) { try { - IBackupTransport transport = transportClient.connectOrThrow(callerLogString); + IBackupTransport transport = transportConnection.connectOrThrow(callerLogString); mCurrentToken = transport.getCurrentRestoreSet(); } catch (Exception e) { // Oops. We can't know the current dataset token, so reset and figure it out @@ -3733,7 +3733,7 @@ public class UserBackupManagerService { + newTransportName + " not available: current token = 0")); } - mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } else { Slog.w( TAG, @@ -3946,9 +3946,9 @@ public class UserBackupManagerService { skip = true; } - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getCurrentTransportClient("BMS.restoreAtInstall()"); - if (transportClient == null) { + if (transportConnection == null) { if (DEBUG) Slog.w(TAG, addUserIdToLogMessage(mUserId, "No transport client")); skip = true; } @@ -3972,7 +3972,7 @@ public class UserBackupManagerService { mWakelock.acquire(); OnTaskFinishedListener listener = caller -> { - mTransportManager.disposeOfTransportClient(transportClient, caller); + mTransportManager.disposeOfTransportClient(transportConnection, caller); mWakelock.release(); }; @@ -3984,7 +3984,7 @@ public class UserBackupManagerService { Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); msg.obj = RestoreParams.createForRestoreAtInstall( - transportClient, + transportConnection, /* observer */ null, /* monitor */ null, restoreSet, @@ -4006,9 +4006,9 @@ public class UserBackupManagerService { if (skip) { // Auto-restore disabled or no way to attempt a restore - if (transportClient != null) { + if (transportConnection != null) { mTransportManager.disposeOfTransportClient( - transportClient, "BMS.restoreAtInstall()"); + transportConnection, "BMS.restoreAtInstall()"); } // Tell the PackageManager to proceed with the post-install handling for this package. @@ -4177,13 +4177,13 @@ public class UserBackupManagerService { final long oldToken = Binder.clearCallingIdentity(); try { String callerLogString = "BMS.isAppEligibleForBackup"; - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getCurrentTransportClient(callerLogString); boolean eligible = mScheduledBackupEligibility.appIsRunningAndEligibleForBackupWithTransport( - transportClient, packageName); - if (transportClient != null) { - mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + transportConnection, packageName); + if (transportConnection != null) { + mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } return eligible; } finally { @@ -4199,17 +4199,17 @@ public class UserBackupManagerService { final long oldToken = Binder.clearCallingIdentity(); try { String callerLogString = "BMS.filterAppsEligibleForBackup"; - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getCurrentTransportClient(callerLogString); List<String> eligibleApps = new LinkedList<>(); for (String packageName : packages) { if (mScheduledBackupEligibility.appIsRunningAndEligibleForBackupWithTransport( - transportClient, packageName)) { + transportConnection, packageName)) { eligibleApps.add(packageName); } } - if (transportClient != null) { - mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + if (transportConnection != null) { + mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } return eligibleApps.toArray(new String[eligibleApps.size()]); } finally { @@ -4362,7 +4362,7 @@ public class UserBackupManagerService { } @VisibleForTesting - @OperationType int getOperationTypeFromTransport(TransportClient transportClient) + @OperationType int getOperationTypeFromTransport(TransportConnection transportConnection) throws TransportNotAvailableException, RemoteException { if (!shouldUseNewBackupEligibilityRules()) { // Return the default to stick to the legacy behaviour. @@ -4371,7 +4371,7 @@ public class UserBackupManagerService { final long oldCallingId = Binder.clearCallingIdentity(); try { - IBackupTransport transport = transportClient.connectOrThrow( + IBackupTransport transport = transportConnection.connectOrThrow( /* caller */ "BMS.getOperationTypeFromTransport"); if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) { return OperationType.MIGRATION; diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index a4d47d492451..1c860917c4ef 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -51,7 +51,7 @@ import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; import com.android.server.backup.remote.RemoteCall; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.utils.BackupEligibilityRules; import com.android.server.backup.utils.BackupManagerMonitorUtils; @@ -110,13 +110,15 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba String caller, BackupEligibilityRules backupEligibilityRules) { TransportManager transportManager = backupManagerService.getTransportManager(); - TransportClient transportClient = transportManager.getCurrentTransportClient(caller); + TransportConnection transportConnection = transportManager.getCurrentTransportClient( + caller); OnTaskFinishedListener listener = listenerCaller -> - transportManager.disposeOfTransportClient(transportClient, listenerCaller); + transportManager.disposeOfTransportClient(transportConnection, + listenerCaller); return new PerformFullTransportBackupTask( backupManagerService, - transportClient, + transportConnection, observer, whichPackages, updateSchedule, @@ -145,7 +147,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba SinglePackageBackupRunner mBackupRunner; private final int mBackupRunnerOpToken; private final OnTaskFinishedListener mListener; - private final TransportClient mTransportClient; + private final TransportConnection mTransportConnection; private final int mUserId; // This is true when a backup operation for some package is in progress. @@ -156,7 +158,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private final BackupEligibilityRules mBackupEligibilityRules; public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, IFullBackupRestoreObserver observer, String[] whichPackages, boolean updateSchedule, FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver, @@ -164,7 +166,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba boolean userInitiated, BackupEligibilityRules backupEligibilityRules) { super(observer); this.mUserBackupManagerService = backupManagerService; - mTransportClient = transportClient; + mTransportConnection = transportConnection; mUpdateSchedule = updateSchedule; mLatch = latch; mJob = runningJob; @@ -299,7 +301,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba try { // If we're running a backup we should be connected to a transport IBackupTransport transport = - mTransportClient.getConnectedTransport("PFTBT.handleCancel()"); + mTransportConnection.getConnectedTransport("PFTBT.handleCancel()"); transport.cancelFullBackup(); } catch (RemoteException | TransportNotAvailableException e) { Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e); @@ -351,7 +353,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba return; } - IBackupTransport transport = mTransportClient.connect("PFTBT.run()"); + IBackupTransport transport = mTransportConnection.connect("PFTBT.run()"); if (transport == null) { Slog.w(TAG, "Transport not present; full data backup not performed"); backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED; @@ -395,7 +397,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba enginePipes = ParcelFileDescriptor.createPipe(); mBackupRunner = new SinglePackageBackupRunner(enginePipes[1], currentPackage, - mTransportClient, quota, mBackupRunnerOpToken, + mTransportConnection, quota, mBackupRunnerOpToken, transport.getTransportFlags()); // The runner dup'd the pipe half, so we close it here enginePipes[1].close(); @@ -697,17 +699,17 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight { final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR); final CountDownLatch mLatch = new CountDownLatch(1); - final TransportClient mTransportClient; + final TransportConnection mTransportConnection; final long mQuota; private final int mCurrentOpToken; private final int mTransportFlags; SinglePackageBackupPreflight( - TransportClient transportClient, + TransportConnection transportConnection, long quota, int currentOpToken, int transportFlags) { - mTransportClient = transportClient; + mTransportConnection = transportConnection; mQuota = quota; mCurrentOpToken = currentOpToken; mTransportFlags = transportFlags; @@ -744,7 +746,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } IBackupTransport transport = - mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()"); + mTransportConnection.connectOrThrow("PFTBT$SPBP.preflightFullBackup()"); result = transport.checkFullBackupSize(totalSize); if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) { if (MORE_DEBUG) { @@ -817,14 +819,14 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba private final int mTransportFlags; SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target, - TransportClient transportClient, long quota, int currentOpToken, int transportFlags) - throws IOException { + TransportConnection transportConnection, long quota, int currentOpToken, + int transportFlags) throws IOException { mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor()); mTarget = target; mCurrentOpToken = currentOpToken; mEphemeralToken = mUserBackupManagerService.generateRandomIntegerToken(); mPreflight = new SinglePackageBackupPreflight( - transportClient, quota, mEphemeralToken, transportFlags); + transportConnection, quota, mEphemeralToken, transportFlags); mPreflightLatch = new CountDownLatch(1); mBackupLatch = new CountDownLatch(1); mPreflightResult = BackupTransport.AGENT_ERROR; diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 1cb7c11e9499..3b3bf8c694af 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -20,7 +20,6 @@ import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; import static com.android.server.backup.BackupManagerService.TAG; -import android.app.backup.BackupManager; import android.app.backup.BackupManager.OperationType; import android.app.backup.RestoreSet; import android.os.Handler; @@ -52,7 +51,7 @@ import com.android.server.backup.params.RestoreGetSetsParams; import com.android.server.backup.params.RestoreParams; import com.android.server.backup.restore.PerformAdbRestoreTask; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import java.util.ArrayList; import java.util.Collections; @@ -148,16 +147,16 @@ public class BackupHandler extends Handler { backupManagerService.setLastBackupPass(System.currentTimeMillis()); String callerLogString = "BH/MSG_RUN_BACKUP"; - TransportClient transportClient = + TransportConnection transportConnection = transportManager.getCurrentTransportClient(callerLogString); IBackupTransport transport = - transportClient != null - ? transportClient.connect(callerLogString) + transportConnection != null + ? transportConnection.connect(callerLogString) : null; if (transport == null) { - if (transportClient != null) { + if (transportConnection != null) { transportManager - .disposeOfTransportClient(transportClient, callerLogString); + .disposeOfTransportClient(transportConnection, callerLogString); } Slog.v(TAG, "Backup requested but no transport available"); break; @@ -212,10 +211,11 @@ public class BackupHandler extends Handler { OnTaskFinishedListener listener = caller -> transportManager - .disposeOfTransportClient(transportClient, caller); + .disposeOfTransportClient(transportConnection, + caller); KeyValueBackupTask.start( backupManagerService, - transportClient, + transportConnection, transport.transportDirName(), queue, oldJournal, @@ -240,7 +240,7 @@ public class BackupHandler extends Handler { } if (!staged) { - transportManager.disposeOfTransportClient(transportClient, callerLogString); + transportManager.disposeOfTransportClient(transportConnection, callerLogString); // if we didn't actually hand off the wakelock, rewind until next time synchronized (backupManagerService.getQueueLock()) { backupManagerService.setBackupRunning(false); @@ -296,7 +296,7 @@ public class BackupHandler extends Handler { PerformUnifiedRestoreTask task = new PerformUnifiedRestoreTask( backupManagerService, - params.transportClient, + params.mTransportConnection, params.observer, params.monitor, params.token, @@ -344,7 +344,7 @@ public class BackupHandler extends Handler { Runnable task = new PerformClearTask( backupManagerService, - params.transportClient, + params.mTransportConnection, params.packageInfo, params.listener); task.run(); @@ -365,7 +365,7 @@ public class BackupHandler extends Handler { String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS"; try { IBackupTransport transport = - params.transportClient.connectOrThrow(callerLogString); + params.mTransportConnection.connectOrThrow(callerLogString); sets = transport.getAvailableRestoreSets(); // cache the result in the active session synchronized (params.session) { @@ -459,7 +459,7 @@ public class BackupHandler extends Handler { KeyValueBackupTask.start( backupManagerService, - params.transportClient, + params.mTransportConnection, params.dirName, params.kvPackages, /* dataChangedJournal */ null, diff --git a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java index e417f06c8a05..30de50996f0c 100644 --- a/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java +++ b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java @@ -16,7 +16,7 @@ package com.android.server.backup.internal; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportConnectionListener; /** Listener to be called when a task finishes, successfully or not. */ @@ -27,7 +27,7 @@ public interface OnTaskFinishedListener { * Called when a task finishes, successfully or not. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check - * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more * details. */ void onFinished(String caller); diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java index 5ffa795d87f0..80bd60451dfd 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java @@ -24,23 +24,23 @@ import android.util.Slog; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import java.io.File; public class PerformClearTask implements Runnable { private final UserBackupManagerService mBackupManagerService; private final TransportManager mTransportManager; - private final TransportClient mTransportClient; + private final TransportConnection mTransportConnection; private final PackageInfo mPackage; private final OnTaskFinishedListener mListener; PerformClearTask(UserBackupManagerService backupManagerService, - TransportClient transportClient, PackageInfo packageInfo, + TransportConnection transportConnection, PackageInfo packageInfo, OnTaskFinishedListener listener) { mBackupManagerService = backupManagerService; mTransportManager = backupManagerService.getTransportManager(); - mTransportClient = transportClient; + mTransportConnection = transportConnection; mPackage = packageInfo; mListener = listener; } @@ -51,12 +51,13 @@ public class PerformClearTask implements Runnable { try { // Clear the on-device backup state to ensure a full backup next time String transportDirName = - mTransportManager.getTransportDirName(mTransportClient.getTransportComponent()); + mTransportManager.getTransportDirName( + mTransportConnection.getTransportComponent()); File stateDir = new File(mBackupManagerService.getBaseStateDir(), transportDirName); File stateFile = new File(stateDir, mPackage.packageName); stateFile.delete(); - transport = mTransportClient.connectOrThrow(callerLogString); + transport = mTransportConnection.connectOrThrow(callerLogString); // Tell the transport to remove all the persistent storage for the app // TODO - need to handle failures transport.clearBackupData(mPackage); diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java index 6b78fbf60899..7636ef65211f 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java @@ -32,7 +32,7 @@ import com.android.internal.backup.IBackupTransport; import com.android.server.EventLogTags; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import java.io.File; import java.util.ArrayList; @@ -109,26 +109,26 @@ public class PerformInitializeTask implements Runnable { public void run() { // mWakelock is *acquired* when execution begins here String callerLogString = "PerformInitializeTask.run()"; - List<TransportClient> transportClientsToDisposeOf = new ArrayList<>(mQueue.length); + List<TransportConnection> transportClientsToDisposeOf = new ArrayList<>(mQueue.length); int result = BackupTransport.TRANSPORT_OK; try { for (String transportName : mQueue) { - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getTransportClient(transportName, callerLogString); - if (transportClient == null) { + if (transportConnection == null) { Slog.e(TAG, "Requested init for " + transportName + " but not found"); continue; } - transportClientsToDisposeOf.add(transportClient); + transportClientsToDisposeOf.add(transportConnection); Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName); String transportDirName = mTransportManager.getTransportDirName( - transportClient.getTransportComponent()); + transportConnection.getTransportComponent()); EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName); long startRealtime = SystemClock.elapsedRealtime(); - IBackupTransport transport = transportClient.connectOrThrow(callerLogString); + IBackupTransport transport = transportConnection.connectOrThrow(callerLogString); int status = transport.initializeDevice(); if (status != BackupTransport.TRANSPORT_OK) { Slog.e(TAG, "Transport error in initializeDevice()"); @@ -170,8 +170,8 @@ public class PerformInitializeTask implements Runnable { Slog.e(TAG, "Unexpected error performing init", e); result = BackupTransport.TRANSPORT_ERROR; } finally { - for (TransportClient transportClient : transportClientsToDisposeOf) { - mTransportManager.disposeOfTransportClient(transportClient, callerLogString); + for (TransportConnection transportConnection : transportClientsToDisposeOf) { + mTransportManager.disposeOfTransportClient(transportConnection, callerLogString); } notifyFinished(result); mListener.onFinished(callerLogString); diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index 7267cdf8539c..bdb2e6fc127f 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -65,7 +65,7 @@ import com.android.server.backup.internal.Operation; import com.android.server.backup.remote.RemoteCall; import com.android.server.backup.remote.RemoteCallable; import com.android.server.backup.remote.RemoteResult; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.utils.BackupEligibilityRules; @@ -192,10 +192,10 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { * dedicated thread and kicks off the operation in it. * * @param backupManagerService The {@link UserBackupManagerService} instance. - * @param transportClient The {@link TransportClient} that contains the transport used for the - * operation. + * @param transportConnection The {@link TransportConnection} that contains the transport used + * for the operation. * @param transportDirName The value of {@link IBackupTransport#transportDirName()} for the - * transport whose {@link TransportClient} was provided above. + * transport whose {@link TransportConnection} was provided above. * @param queue The list of package names that will be backed-up. * @param dataChangedJournal The old data-changed journal file that will be deleted when the * operation finishes (successfully or not) or {@code null}. @@ -211,7 +211,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { */ public static KeyValueBackupTask start( UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, String transportDirName, List<String> queue, @Nullable DataChangedJournal dataChangedJournal, @@ -227,7 +227,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { KeyValueBackupTask task = new KeyValueBackupTask( backupManagerService, - transportClient, + transportConnection, transportDirName, queue, dataChangedJournal, @@ -245,7 +245,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private final UserBackupManagerService mBackupManagerService; private final PackageManager mPackageManager; - private final TransportClient mTransportClient; + private final TransportConnection mTransportConnection; private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final KeyValueBackupReporter mReporter; private final OnTaskFinishedListener mTaskFinishedListener; @@ -302,7 +302,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { @VisibleForTesting public KeyValueBackupTask( UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, String transportDirName, List<String> queue, @Nullable DataChangedJournal journal, @@ -314,7 +314,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { BackupEligibilityRules backupEligibilityRules) { mBackupManagerService = backupManagerService; mPackageManager = backupManagerService.getPackageManager(); - mTransportClient = transportClient; + mTransportConnection = transportConnection; mOriginalQueue = queue; // We need to retain the original queue contents in case of transport failure mQueue = new ArrayList<>(queue); @@ -418,7 +418,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { boolean noDataPackageEncountered = false; try { IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.informTransportOfEmptyBackups()"); + mTransportConnection.connectOrThrow("KVBT.informTransportOfEmptyBackups()"); for (String packageName : succeedingPackages) { if (appsBackedUp.contains(packageName)) { @@ -463,7 +463,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private boolean isEligibleForNoDataCall(PackageInfo packageInfo) { return mBackupEligibilityRules.appIsKeyValueOnly(packageInfo) && mBackupEligibilityRules.appIsRunningAndEligibleForBackupWithTransport( - mTransportClient, packageInfo.packageName); + mTransportConnection, packageInfo.packageName); } /** Send the "no data changed" message to a transport for a specific package */ @@ -608,7 +608,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { mReporter.onQueueReady(mQueue); File pmState = new File(mStateDirectory, PM_PACKAGE); try { - IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()"); + IBackupTransport transport = mTransportConnection.connectOrThrow("KVBT.startTask()"); String transportName = transport.name(); if (transportName.contains("EncryptedLocalTransport")) { // Temporary code for EiTF POC. Only supports non-incremental backups. @@ -638,7 +638,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) { return new PerformFullTransportBackupTask( mBackupManagerService, - mTransportClient, + mTransportConnection, /* fullBackupRestoreObserver */ null, packages.toArray(new String[packages.size()]), /* updateSchedule */ false, @@ -764,7 +764,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { long currentToken = mBackupManagerService.getCurrentToken(); if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) { try { - IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString); + IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString); transportName = transport.name(); mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet()); mBackupManagerService.writeRestoreTokens(); @@ -836,7 +836,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { @GuardedBy("mQueueLock") private void triggerTransportInitializationLocked() throws Exception { IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.triggerTransportInitializationLocked"); + mTransportConnection.connectOrThrow("KVBT.triggerTransportInitializationLocked"); mBackupManagerService.getPendingInits().add(transport.name()); deletePmStateFile(); mBackupManagerService.backupNow(); @@ -919,7 +919,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } } - IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.extractAgentData()"); + IBackupTransport transport = mTransportConnection.connectOrThrow( + "KVBT.extractAgentData()"); long quota = transport.getBackupQuota(packageName, /* isFullBackup */ false); int transportFlags = transport.getTransportFlags(); @@ -1078,7 +1079,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { try (ParcelFileDescriptor backupData = ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) { IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.transportPerformBackup()"); + mTransportConnection.connectOrThrow("KVBT.transportPerformBackup()"); mReporter.onTransportPerformBackup(packageName); int flags = getPerformBackupFlags(mUserInitiated, nonIncremental); @@ -1131,7 +1132,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { if (agent != null) { try { IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.agentDoQuotaExceeded()"); + mTransportConnection.connectOrThrow("KVBT.agentDoQuotaExceeded()"); long quota = transport.getBackupQuota(packageName, false); remoteCall( callback -> agent.doQuotaExceeded(size, quota, callback), @@ -1227,7 +1228,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { long delay; try { IBackupTransport transport = - mTransportClient.connectOrThrow("KVBT.revertTask()"); + mTransportConnection.connectOrThrow("KVBT.revertTask()"); delay = transport.requestBackupTime(); } catch (Exception e) { mReporter.onTransportRequestBackupTimeError(e); diff --git a/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java index 800257002f01..c8ed00b003b1 100644 --- a/services/backup/java/com/android/server/backup/params/BackupParams.java +++ b/services/backup/java/com/android/server/backup/params/BackupParams.java @@ -20,14 +20,14 @@ import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import java.util.ArrayList; public class BackupParams { - public TransportClient transportClient; + public TransportConnection mTransportConnection; public String dirName; public ArrayList<String> kvPackages; public ArrayList<String> fullPackages; @@ -38,11 +38,11 @@ public class BackupParams { public boolean nonIncrementalBackup; public BackupEligibilityRules mBackupEligibilityRules; - public BackupParams(TransportClient transportClient, String dirName, + public BackupParams(TransportConnection transportConnection, String dirName, ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer, IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated, boolean nonIncrementalBackup, BackupEligibilityRules backupEligibilityRules) { - this.transportClient = transportClient; + this.mTransportConnection = transportConnection; this.dirName = dirName; this.kvPackages = kvPackages; this.fullPackages = fullPackages; diff --git a/services/backup/java/com/android/server/backup/params/ClearParams.java b/services/backup/java/com/android/server/backup/params/ClearParams.java index dc3bba007443..bc3b79b8f2e6 100644 --- a/services/backup/java/com/android/server/backup/params/ClearParams.java +++ b/services/backup/java/com/android/server/backup/params/ClearParams.java @@ -19,18 +19,18 @@ package com.android.server.backup.params; import android.content.pm.PackageInfo; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; public class ClearParams { - public TransportClient transportClient; + public TransportConnection mTransportConnection; public PackageInfo packageInfo; public OnTaskFinishedListener listener; public ClearParams( - TransportClient transportClient, + TransportConnection transportConnection, PackageInfo packageInfo, OnTaskFinishedListener listener) { - this.transportClient = transportClient; + this.mTransportConnection = transportConnection; this.packageInfo = packageInfo; this.listener = listener; } diff --git a/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java b/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java index 914e9ea7f57c..dbd06ee3c6b2 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreGetSetsParams.java @@ -19,22 +19,21 @@ package com.android.server.backup.params; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; -import com.android.internal.backup.IBackupTransport; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.restore.ActiveRestoreSession; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; public class RestoreGetSetsParams { - public final TransportClient transportClient; + public final TransportConnection mTransportConnection; public final ActiveRestoreSession session; public final IRestoreObserver observer; public final IBackupManagerMonitor monitor; public final OnTaskFinishedListener listener; - public RestoreGetSetsParams(TransportClient _transportClient, ActiveRestoreSession _session, - IRestoreObserver _observer, IBackupManagerMonitor _monitor, - OnTaskFinishedListener _listener) { - transportClient = _transportClient; + public RestoreGetSetsParams(TransportConnection _transportConnection, + ActiveRestoreSession _session, IRestoreObserver _observer, + IBackupManagerMonitor _monitor, OnTaskFinishedListener _listener) { + mTransportConnection = _transportConnection; session = _session; observer = _observer; monitor = _monitor; diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java index a08a1f8d5387..1795a3cb1740 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java @@ -22,14 +22,11 @@ import android.app.backup.IRestoreObserver; import android.content.pm.PackageInfo; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; -import java.util.Map; -import java.util.Set; - public class RestoreParams { - public final TransportClient transportClient; + public final TransportConnection mTransportConnection; public final IRestoreObserver observer; public final IBackupManagerMonitor monitor; public final long token; @@ -44,7 +41,7 @@ public class RestoreParams { * No kill after restore. */ public static RestoreParams createForSinglePackage( - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long token, @@ -52,7 +49,7 @@ public class RestoreParams { OnTaskFinishedListener listener, BackupEligibilityRules eligibilityRules) { return new RestoreParams( - transportClient, + transportConnection, observer, monitor, token, @@ -68,7 +65,7 @@ public class RestoreParams { * Kill after restore. */ public static RestoreParams createForRestoreAtInstall( - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long token, @@ -78,7 +75,7 @@ public class RestoreParams { BackupEligibilityRules backupEligibilityRules) { String[] filterSet = {packageName}; return new RestoreParams( - transportClient, + transportConnection, observer, monitor, token, @@ -94,14 +91,14 @@ public class RestoreParams { * This is the form that Setup Wizard or similar restore UXes use. */ public static RestoreParams createForRestoreAll( - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long token, OnTaskFinishedListener listener, BackupEligibilityRules backupEligibilityRules) { return new RestoreParams( - transportClient, + transportConnection, observer, monitor, token, @@ -117,7 +114,7 @@ public class RestoreParams { * Caller specifies whether is considered a system-level restore. */ public static RestoreParams createForRestorePackages( - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long token, @@ -126,7 +123,7 @@ public class RestoreParams { OnTaskFinishedListener listener, BackupEligibilityRules backupEligibilityRules) { return new RestoreParams( - transportClient, + transportConnection, observer, monitor, token, @@ -139,7 +136,7 @@ public class RestoreParams { } private RestoreParams( - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long token, @@ -149,7 +146,7 @@ public class RestoreParams { @Nullable String[] filterSet, OnTaskFinishedListener listener, BackupEligibilityRules backupEligibilityRules) { - this.transportClient = transportClient; + this.mTransportConnection = transportConnection; this.observer = observer; this.monitor = monitor; this.token = token; diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index d0a88813fa5e..8b1d5619a70d 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -26,7 +26,6 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; @@ -44,7 +43,7 @@ import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.params.RestoreGetSetsParams; import com.android.server.backup.params.RestoreParams; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import java.util.function.BiFunction; @@ -104,10 +103,10 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { final long oldId = Binder.clearCallingIdentity(); try { - TransportClient transportClient = + TransportConnection transportConnection = mTransportManager.getTransportClient( mTransportName, "RestoreSession.getAvailableRestoreSets()"); - if (transportClient == null) { + if (transportConnection == null) { Slog.w(TAG, "Null transport client getting restore sets"); return -1; } @@ -123,12 +122,13 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { // Prevent lambda from leaking 'this' TransportManager transportManager = mTransportManager; OnTaskFinishedListener listener = caller -> { - transportManager.disposeOfTransportClient(transportClient, caller); + transportManager.disposeOfTransportClient(transportConnection, caller); wakelock.release(); }; Message msg = mBackupManagerService.getBackupHandler().obtainMessage( MSG_RUN_GET_RESTORE_SETS, - new RestoreGetSetsParams(transportClient, this, observer, monitor, listener)); + new RestoreGetSetsParams(transportConnection, this, observer, monitor, + listener)); mBackupManagerService.getBackupHandler().sendMessage(msg); return 0; } catch (Exception e) { @@ -399,11 +399,11 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { * Returns 0 if operation sent or -1 otherwise. */ private int sendRestoreToHandlerLocked( - BiFunction<TransportClient, OnTaskFinishedListener, RestoreParams> restoreParamsBuilder, - String callerLogString) { - TransportClient transportClient = + BiFunction<TransportConnection, OnTaskFinishedListener, + RestoreParams> restoreParamsBuilder, String callerLogString) { + TransportConnection transportConnection = mTransportManager.getTransportClient(mTransportName, callerLogString); - if (transportClient == null) { + if (transportConnection == null) { Slog.e(TAG, "Transport " + mTransportName + " got unregistered"); return -1; } @@ -421,11 +421,11 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { // Prevent lambda from leaking 'this' TransportManager transportManager = mTransportManager; OnTaskFinishedListener listener = caller -> { - transportManager.disposeOfTransportClient(transportClient, caller); + transportManager.disposeOfTransportClient(transportConnection, caller); wakelock.release(); }; Message msg = backupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = restoreParamsBuilder.apply(transportClient, listener); + msg.obj = restoreParamsBuilder.apply(transportConnection, listener); backupHandler.sendMessage(msg); return 0; } diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index f07bac8cd762..8c786d556518 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -66,7 +66,7 @@ import com.android.server.backup.PackageManagerBackupAgent.Metadata; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import com.android.server.backup.utils.BackupManagerMonitorUtils; @@ -87,7 +87,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final int mUserId; private final TransportManager mTransportManager; // Transport client we're working with to do the restore - private final TransportClient mTransportClient; + private final TransportConnection mTransportConnection; // Where per-transport saved state goes private File mStateDir; @@ -169,7 +169,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) { mListener = null; mAgentTimeoutParameters = null; - mTransportClient = null; + mTransportConnection = null; mTransportManager = null; mEphemeralOpToken = 0; mUserId = 0; @@ -181,7 +181,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // about releasing it. public PerformUnifiedRestoreTask( UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long restoreSetToken, @@ -198,7 +198,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { mState = UnifiedRestoreState.INITIAL; mStartRealtime = SystemClock.elapsedRealtime(); - mTransportClient = transportClient; + mTransportConnection = transportConnection; mObserver = observer; mMonitor = monitor; mToken = restoreSetToken; @@ -386,7 +386,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { try { String transportDirName = - mTransportManager.getTransportDirName(mTransportClient.getTransportComponent()); + mTransportManager.getTransportDirName( + mTransportConnection.getTransportComponent()); mStateDir = new File(backupManagerService.getBaseStateDir(), transportDirName); // Fetch the current metadata from the dataset first @@ -397,7 +398,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]); IBackupTransport transport = - mTransportClient.connectOrThrow("PerformUnifiedRestoreTask.startRestore()"); + mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()"); mStatus = transport.startRestore(mToken, packages); if (mStatus != BackupTransport.TRANSPORT_OK) { @@ -495,7 +496,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { UnifiedRestoreState nextState = UnifiedRestoreState.FINAL; try { IBackupTransport transport = - mTransportClient.connectOrThrow( + mTransportConnection.connectOrThrow( "PerformUnifiedRestoreTask.dispatchNextRestore()"); mRestoreDescription = transport.nextRestorePackage(); final String pkgName = (mRestoreDescription != null) @@ -709,7 +710,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { try { IBackupTransport transport = - mTransportClient.connectOrThrow( + mTransportConnection.connectOrThrow( "PerformUnifiedRestoreTask.initiateOneRestore()"); // Run the transport's restore pass @@ -939,7 +940,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()"; try { - IBackupTransport transport = mTransportClient.connectOrThrow(callerLogString); + IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString); while (status == BackupTransport.TRANSPORT_OK) { // have the transport write some of the restoring data to us int result = transport.getNextFullRestoreDataChunk(tWriteEnd); @@ -1032,7 +1033,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // level is immaterial; we need to tell the transport to bail try { IBackupTransport transport = - mTransportClient.connectOrThrow(callerLogString); + mTransportConnection.connectOrThrow(callerLogString); transport.abortFullRestore(); } catch (Exception e) { // transport itself is dead; make sure we handle this as a @@ -1095,7 +1096,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()"; try { IBackupTransport transport = - mTransportClient.connectOrThrow(callerLogString); + mTransportConnection.connectOrThrow(callerLogString); transport.finishRestore(); } catch (Exception e) { Slog.e(TAG, "Error finishing restore", e); diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java index bfb6f65374de..652386f13bea 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java +++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java @@ -42,11 +42,10 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; import com.android.internal.util.ArrayUtils; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.google.android.collect.Sets; -import java.util.Objects; import java.util.Set; /** @@ -225,7 +224,7 @@ public class BackupEligibilityRules { * </ol> */ public boolean appIsRunningAndEligibleForBackupWithTransport( - @Nullable TransportClient transportClient, + @Nullable TransportConnection transportConnection, String packageName) { try { PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName, @@ -236,10 +235,10 @@ public class BackupEligibilityRules { || appIsDisabled(applicationInfo)) { return false; } - if (transportClient != null) { + if (transportConnection != null) { try { IBackupTransport transport = - transportClient.connectOrThrow( + transportConnection.connectOrThrow( "AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport"); return transport.isAppEligibleForBackup( packageInfo, appGetsFullBackup(packageInfo)); diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 2645f3f9d9f5..a0a00f7629b1 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -16,29 +16,23 @@ package com.android.server.companion; -import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; -import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; -import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - import static com.android.internal.util.CollectionUtils.filter; import static com.android.internal.util.FunctionalUtils.uncheckExceptions; -import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; -import static com.android.server.companion.CompanionDeviceManagerService.getCallingUserId; +import static com.android.server.companion.PermissionsUtils.enforceCallerPermissionsToRequest; +import static com.android.server.companion.RolesUtils.isRoleHolder; -import static java.util.Collections.unmodifiableMap; +import static java.util.Objects.requireNonNull; -import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.role.RoleManager; +import android.annotation.UserIdInt; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; +import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceDiscoveryService; -import android.companion.IFindDeviceCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -46,8 +40,6 @@ import android.content.pm.Signature; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; -import android.util.ArrayMap; import android.util.PackageUtils; import android.util.Slog; @@ -60,26 +52,12 @@ import com.android.server.FgThread; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; class AssociationRequestsProcessor { private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor"; - private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION; - static { - final Map<String, String> map = new ArrayMap<>(); - map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); - map.put(DEVICE_PROFILE_APP_STREAMING, - Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING); - map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, - Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION); - - DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); - } - private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative( CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceDiscoveryService"); @@ -89,57 +67,91 @@ class AssociationRequestsProcessor { private final Context mContext; private final CompanionDeviceManagerService mService; + private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors; private AssociationRequest mRequest; - private IFindDeviceCallback mFindDeviceCallback; - private String mCallingPackage; + private IAssociationRequestCallback mAppCallback; private AndroidFuture<?> mOngoingDeviceDiscovery; - private RoleManager mRoleManager; - - private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors; - AssociationRequestsProcessor(CompanionDeviceManagerService service, RoleManager roleManager) { + AssociationRequestsProcessor(CompanionDeviceManagerService service) { mContext = service.getContext(); mService = service; - mRoleManager = roleManager; final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO); - mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() { + mServiceConnectors = new PerUser<>() { @Override protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) { return new ServiceConnector.Impl<>( - mContext, - serviceIntent, 0/* bindingFlags */, userId, - ICompanionDeviceDiscoveryService.Stub::asInterface); + mContext, + serviceIntent, 0/* bindingFlags */, userId, + ICompanionDeviceDiscoveryService.Stub::asInterface); } }; } - void process(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) - throws RemoteException { + /** + * Handle incoming {@link AssociationRequest}s, sent via + * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)} + */ + void process(@NonNull AssociationRequest request, @NonNull String packageName, + @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) { + requireNonNull(request, "Request MUST NOT be null"); + if (request.isSelfManaged()) { + requireNonNull(request.getDisplayName(), "AssociationRequest.displayName " + + "MUST NOT be null."); + } + requireNonNull(packageName, "Package name MUST NOT be null"); + requireNonNull(callback, "Callback MUST NOT be null"); + if (DEBUG) { - Slog.d(TAG, "process(request=" + request + ", from=" + callingPackage + ")"); + Slog.d(TAG, "process() " + + "request=" + request + ", " + + "package=u" + userId + "/" + packageName); } - checkNotNull(request, "Request cannot be null"); - checkNotNull(callback, "Callback cannot be null"); - mService.checkCallerIsSystemOr(callingPackage); - int userId = getCallingUserId(); - mService.checkUsesFeature(callingPackage, userId); - final String deviceProfile = request.getDeviceProfile(); - validateDeviceProfileAndCheckPermission(deviceProfile); + // 1. Enforce permissions and other requirements. + enforceCallerPermissionsToRequest(mContext, request, packageName, userId); + mService.checkUsesFeature(packageName, userId); + + // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER + // to perform discovery NOR to collect user consent). + if (request.isSelfManaged() && !request.isForceConfirmation() + && !willAddRoleHolder(request, packageName, userId)) { + // 2a. Create association right away. + final AssociationInfo association = mService.createAssociation(userId, packageName, + /* macAddress */ null, request.getDisplayName(), request.getDeviceProfile(), + /* selfManaged */true); + withCatchingRemoteException(() -> callback.onAssociationCreated(association)); + return; + } + + // 2b. Launch the UI. + synchronized (mService.mLock) { + if (mRequest != null) { + Slog.w(TAG, "CDM is already processing another AssociationRequest."); - mFindDeviceCallback = callback; - mRequest = request; - mCallingPackage = callingPackage; - request.setCallingPackage(callingPackage); + withCatchingRemoteException(() -> callback.onFailure("Busy.")); + } + + final boolean linked = withCatchingRemoteException( + () -> callback.asBinder().linkToDeath(mBinderDeathRecipient, 0)); + if (!linked) { + // The process has died by now: do not proceed. + return; + } + + mRequest = request; + } + + mAppCallback = callback; + request.setCallingPackage(packageName); - if (mayAssociateWithoutPrompt(callingPackage, userId)) { + if (mayAssociateWithoutPrompt(packageName, userId)) { Slog.i(TAG, "setSkipPrompt(true)"); request.setSkipPrompt(true); } - callback.asBinder().linkToDeath(mBinderDeathRecipient /* recipient */, 0); + final String deviceProfile = request.getDeviceProfile(); mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile) .thenComposeAsync(description -> { if (DEBUG) { @@ -155,17 +167,16 @@ class AssociationRequestsProcessor { } AndroidFuture<String> future = new AndroidFuture<>(); - service.startDiscovery(request, callingPackage, callback, future); + service.startDiscovery(request, packageName, callback, future); return future; }).cancelTimeout(); }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> { if (err == null) { - mService.createAssociationInternal( - userId, deviceAddress, callingPackage, deviceProfile); - mServiceConnectors.forUser(userId).post(service -> { - service.onAssociationCreated(); - }); + mService.legacyCreateAssociation( + userId, deviceAddress, packageName, deviceProfile); + mServiceConnectors.forUser(userId).post( + ICompanionDeviceDiscoveryService::onAssociationCreated); } else { Slog.e(TAG, "Failed to discover device(s)", err); callback.onFailure("No devices found: " + err.getMessage()); @@ -174,52 +185,16 @@ class AssociationRequestsProcessor { })); } - private boolean isRoleHolder(int userId, String packageName, String role) { - final long identity = Binder.clearCallingIdentity(); - try { - List<String> holders = mRoleManager.getRoleHoldersAsUser(role, UserHandle.of(userId)); - return holders.contains(packageName); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) { - if (DEBUG) { - Slog.d(TAG, "stopScan(request = " + request + ")"); - } - if (Objects.equals(request, mRequest) - && Objects.equals(callback, mFindDeviceCallback) - && Objects.equals(callingPackage, mCallingPackage)) { - cleanup(); - } - } - - private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) { - // Device profile can be null. - if (deviceProfile == null) return; - - if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) { - // TODO: remove, when properly supporting this profile. - throw new UnsupportedOperationException( - "DEVICE_PROFILE_APP_STREAMING is not fully supported yet."); - } - - if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { - // TODO: remove, when properly supporting this profile. - throw new UnsupportedOperationException( - "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet."); - } + private boolean willAddRoleHolder(@NonNull AssociationRequest request, + @NonNull String packageName, @UserIdInt int userId) { + final String deviceProfile = request.getDeviceProfile(); + if (deviceProfile == null) return false; - if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) { - throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile); - } + final boolean isRoleHolder = Binder.withCleanCallingIdentity( + () -> isRoleHolder(mContext, userId, packageName, deviceProfile)); - final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile); - if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { - throw new SecurityException("Application must hold " + permission + " to associate " - + "with a device with " + deviceProfile + " profile."); - } + // Don't need to "grant" the role, if the package already holds the role. + return !isRoleHolder; } private void cleanup() { @@ -232,20 +207,23 @@ class AssociationRequestsProcessor { if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) { ongoingDeviceDiscovery.cancel(true); } - if (mFindDeviceCallback != null) { - mFindDeviceCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0); - mFindDeviceCallback = null; + if (mAppCallback != null) { + mAppCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0); + mAppCallback = null; } mRequest = null; - mCallingPackage = null; } } private boolean mayAssociateWithoutPrompt(String packageName, int userId) { - if (mRequest.getDeviceProfile() != null - && isRoleHolder(userId, packageName, mRequest.getDeviceProfile())) { - // Don't need to collect user's consent since app already holds the role. - return true; + final String deviceProfile = mRequest.getDeviceProfile(); + if (deviceProfile != null) { + final boolean isRoleHolder = Binder.withCleanCallingIdentity( + () -> isRoleHolder(mContext, userId, packageName, deviceProfile)); + if (isRoleHolder) { + // Don't need to collect user's consent since app already holds the role. + return true; + } } String[] sameOemPackages = mContext.getResources() @@ -261,7 +239,7 @@ class AssociationRequestsProcessor { // Throttle frequent associations long now = System.currentTimeMillis(); Set<AssociationInfo> recentAssociations = filter( - mService.getAllAssociations(userId, packageName), + mService.getAssociations(userId, packageName), a -> now - a.getTimeApprovedMs() < ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS); if (recentAssociations.size() >= ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { @@ -351,4 +329,17 @@ class AssociationRequestsProcessor { return sameOemPackageCerts; } + + private static boolean withCatchingRemoteException(ThrowingRunnable runnable) { + try { + runnable.run(); + } catch (RemoteException e) { + return false; + } + return true; + } + + private interface ThrowingRunnable { + void run() throws RemoteException; + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index d5357dc1dc5e..049018cfe1db 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -17,11 +17,15 @@ package com.android.server.companion; +import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED; -import static android.companion.DeviceId.TYPE_MAC_ADDRESS; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; +import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Binder.getCallingUid; +import static android.os.Process.SYSTEM_UID; +import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.add; import static com.android.internal.util.CollectionUtils.any; @@ -29,11 +33,16 @@ import static com.android.internal.util.CollectionUtils.filter; import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.CollectionUtils.forEach; import static com.android.internal.util.CollectionUtils.map; -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; +import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage; +import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; +import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId; +import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice; +import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; +import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; +import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableSet; @@ -48,7 +57,6 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.role.RoleManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.le.BluetoothLeScanner; @@ -58,10 +66,10 @@ import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.companion.AssociationInfo; import android.companion.AssociationRequest; -import android.companion.DeviceId; import android.companion.DeviceNotAssociatedException; +import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; -import android.companion.IFindDeviceCallback; +import android.companion.IOnAssociationsChangedListener; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -74,13 +82,13 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; +import android.net.MacAddress; import android.net.NetworkPolicyManager; import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Parcel; import android.os.PowerWhitelistManager; -import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -117,7 +125,6 @@ import java.io.PrintWriter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -159,7 +166,6 @@ public class CompanionDeviceManagerService extends SystemService { private final AssociationRequestsProcessor mAssociationRequestsProcessor; private PowerWhitelistManager mPowerWhitelistManager; private IAppOpsService mAppOpsManager; - private RoleManager mRoleManager; private BluetoothAdapter mBluetoothAdapter; private UserManager mUserManager; @@ -202,7 +208,6 @@ public class CompanionDeviceManagerService extends SystemService { mPersistentDataStore = new PersistentDataStore(); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); - mRoleManager = context.getSystemService(RoleManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); @@ -212,7 +217,7 @@ public class CompanionDeviceManagerService extends SystemService { context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); mCompanionDevicePresenceController = new CompanionDevicePresenceController(); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mRoleManager); + mAssociationRequestsProcessor = new AssociationRequestsProcessor(this); registerPackageMonitor(); } @@ -221,26 +226,28 @@ public class CompanionDeviceManagerService extends SystemService { new PackageMonitor() { @Override public void onPackageRemoved(String packageName, int uid) { - Slog.d(LOG_TAG, "onPackageRemoved(packageName = " + packageName - + ", uid = " + uid + ")"); - int userId = getChangingUserId(); - updateAssociations( - set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)), - userId); - - mCompanionDevicePresenceController.unbindDevicePresenceListener( - packageName, userId); + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); + + clearAssociationForPackage(userId, packageName); } @Override - public void onPackageModified(String packageName) { - Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")"); - int userId = getChangingUserId(); - forEach(getAllAssociations(userId, packageName), association -> { - updateSpecialAccessPermissionForAssociatedPackage(association); - }); + public void onPackageDataCleared(String packageName, int uid) { + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); + + clearAssociationForPackage(userId, packageName); } + @Override + public void onPackageModified(String packageName) { + final int userId = getChangingUserId(); + Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); + + forEach(getAssociations(userId, packageName), association -> + updateSpecialAccessPermissionForAssociatedPackage(association)); + } }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); } @@ -269,18 +276,82 @@ public class CompanionDeviceManagerService extends SystemService { @Override public void onUserUnlocking(@NonNull TargetUser user) { - int userHandle = user.getUserIdentifier(); - Set<AssociationInfo> associations = getAllAssociations(userHandle); - if (associations == null || associations.isEmpty()) { - return; - } - updateAtm(userHandle, associations); + final int userId = user.getUserIdentifier(); + final Set<AssociationInfo> associations = getAllAssociationsForUser(userId); + + if (associations.isEmpty()) return; + + updateAtm(userId, associations); BackgroundThread.getHandler().sendMessageDelayed( obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this), MINUTES.toMillis(10)); } + @NonNull + Set<AssociationInfo> getAllAssociationsForUser(@UserIdInt int userId) { + synchronized (mLock) { + readPersistedStateForUserIfNeededLocked(userId); + // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method + // we just called adds an empty set, if there was no previously saved data. + return mCachedAssociations.get(userId); + } + } + + @NonNull + Set<AssociationInfo> getAssociations(@UserIdInt int userId, @NonNull String packageName) { + return filter(getAllAssociationsForUser(userId), + a -> a.belongsToPackage(userId, packageName)); + } + + @Nullable + private AssociationInfo getAssociation(int associationId) { + return find(getAllAssociations(), association -> association.getId() == associationId); + } + + @Nullable + AssociationInfo getAssociation( + @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { + return find(getAssociations(userId, packageName), a -> a.isLinkedTo(macAddress)); + } + + @Nullable + AssociationInfo getAssociationWithCallerChecks( + @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { + return sanitizeWithCallerChecks(getAssociation(userId, packageName, macAddress)); + } + + @Nullable + AssociationInfo getAssociationWithCallerChecks(int associationId) { + return sanitizeWithCallerChecks(getAssociation(associationId)); + } + + @Nullable + private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) { + if (association == null) return null; + + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) { + return null; + } + + return association; + } + + private Set<AssociationInfo> getAllAssociations() { + final long identity = Binder.clearCallingIdentity(); + try { + final Set<AssociationInfo> result = new ArraySet<>(); + for (UserInfo user : mUserManager.getAliveUsers()) { + result.addAll(getAllAssociationsForUser(user.id)); + } + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + void maybeGrantAutoRevokeExemptions() { Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); PackageManager pm = getContext().getPackageManager(); @@ -293,7 +364,7 @@ public class CompanionDeviceManagerService extends SystemService { } try { - Set<AssociationInfo> associations = getAllAssociations(userId); + Set<AssociationInfo> associations = getAllAssociationsForUser(userId); if (associations == null) { continue; } @@ -325,67 +396,92 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void associate( - AssociationRequest request, - IFindDeviceCallback callback, - String callingPackage) throws RemoteException { - Slog.i(LOG_TAG, "associate(request = " + request + ", callback = " + callback - + ", callingPackage = " + callingPackage + ")"); - mAssociationRequestsProcessor.process(request, callback, callingPackage); + public void associate(AssociationRequest request, IAssociationRequestCallback callback, + String packageName, int userId) throws RemoteException { + Slog.i(LOG_TAG, "associate() " + + "request=" + request + ", " + + "package=u" + userId + "/" + packageName); + mAssociationRequestsProcessor.process(request, packageName, userId, callback); + } + + @Override + public List<AssociationInfo> getAssociations(String packageName, int userId) { + final int callingUid = getCallingUserId(); + if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) { + throw new SecurityException("Caller (uid=" + callingUid + ") does not have " + + "permissions to get associations for u" + userId + "/" + packageName); + } + + if (!checkCallerCanManageCompanionDevice(getContext())) { + // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to + // request the feature (also: the caller is the app itself). + checkUsesFeature(packageName, getCallingUserId()); + } + + return new ArrayList<>( + CompanionDeviceManagerService.this.getAssociations(userId, packageName)); + } + + @Override + public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException { + enforceCallerCanInteractWithUserId(getContext(), userId); + enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser"); + + return new ArrayList<>( + CompanionDeviceManagerService.this.getAllAssociationsForUser(userId)); } @Override - public void stopScan(AssociationRequest request, - IFindDeviceCallback callback, - String callingPackage) { - Slog.i(LOG_TAG, "stopScan(request = " + request + ")"); - mAssociationRequestsProcessor.stopScan(request, callback, callingPackage); + public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, + int userId) { + enforceCallerCanInteractWithUserId(getContext(), userId); + enforceCallerCanManagerCompanionDevice(getContext(), + "addOnAssociationsChangedListener"); + + //TODO: Implement. } @Override - public List<String> getAssociations(String callingPackage, int userId) - throws RemoteException { - if (!callerCanManageCompanionDevices()) { - checkCallerIsSystemOr(callingPackage, userId); - checkUsesFeature(callingPackage, getCallingUserId()); - } - return new ArrayList<>(map( - getAllAssociations(userId, callingPackage), - a -> a.getDeviceMacAddress())); + public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, + int userId) { + //TODO: Implement. } @Override - public List<AssociationInfo> getAssociationsForUser(int userId) { - if (!callerCanManageCompanionDevices()) { - throw new SecurityException("Caller must hold " - + android.Manifest.permission.MANAGE_COMPANION_DEVICES); + public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { + requireNonNull(deviceMacAddress); + requireNonNull(packageName); + + final AssociationInfo association = + getAssociationWithCallerChecks(userId, packageName, deviceMacAddress); + if (association == null) { + throw new IllegalArgumentException("Association does not exist " + + "or the caller does not have permissions to manage it " + + "(ie. it belongs to a different package or a different user)."); } - return new ArrayList<>(getAllAssociations(userId, null /* packageFilter */)); + disassociateInternal(userId, association.getId()); } - //TODO also revoke notification access @Override - public void disassociate(String deviceMacAddress, String callingPackage) - throws RemoteException { - checkNotNull(deviceMacAddress); - checkCallerIsSystemOr(callingPackage); - checkUsesFeature(callingPackage, getCallingUserId()); - removeAssociation(getCallingUserId(), callingPackage, deviceMacAddress); - } + public void disassociate(int associationId) { + final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (association == null) { + throw new IllegalArgumentException("Association with ID " + associationId + " " + + "does not exist " + + "or belongs to a different package " + + "or belongs to a different user"); + } - private boolean callerCanManageCompanionDevices() { - return getContext().checkCallingOrSelfPermission( - android.Manifest.permission.MANAGE_COMPANION_DEVICES) - == PERMISSION_GRANTED; + disassociateInternal(association.getUserId(), associationId); } @Override - public PendingIntent requestNotificationAccess(ComponentName component) + public PendingIntent requestNotificationAccess(ComponentName component, int userId) throws RemoteException { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); - int userId = getCallingUserId(); + //TODO: check userId. String packageTitle = BidiFormatter.getInstance().unicodeWrap( getPackageInfo(callingPackage, userId) .applicationInfo @@ -425,7 +521,7 @@ public class CompanionDeviceManagerService extends SystemService { public boolean isDeviceAssociatedForWifiConnection(String packageName, String macAddress, int userId) { getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_COMPANION_DEVICES, "isDeviceAssociated"); + MANAGE_COMPANION_DEVICES, "isDeviceAssociated"); boolean bypassMacPermission = getContext().getPackageManager().checkPermission( android.Manifest.permission.COMPANION_APPROVE_WIFI_CONNECTIONS, packageName) @@ -434,23 +530,22 @@ public class CompanionDeviceManagerService extends SystemService { return true; } - return any( - getAllAssociations(userId, packageName), - a -> Objects.equals(a.getDeviceMacAddress(), macAddress)); + return any(CompanionDeviceManagerService.this.getAssociations(userId, packageName), + a -> a.isLinkedTo(macAddress)); } @Override - public void registerDevicePresenceListenerService( - String packageName, String deviceAddress) - throws RemoteException { - registerDevicePresenceListenerActive(packageName, deviceAddress, true); + public void registerDevicePresenceListenerService(String deviceAddress, + String callingPackage, int userId) throws RemoteException { + //TODO: take the userId into account. + registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); } @Override - public void unregisterDevicePresenceListenerService( - String packageName, String deviceAddress) - throws RemoteException { - registerDevicePresenceListenerActive(packageName, deviceAddress, false); + public void unregisterDevicePresenceListenerService(String deviceAddress, + String callingPackage, int userId) throws RemoteException { + //TODO: take the userId into account. + registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); } @Override @@ -464,12 +559,12 @@ public class CompanionDeviceManagerService extends SystemService { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE, "[un]registerDevicePresenceListenerService"); - checkCallerIsSystemOr(packageName); + final int userId = getCallingUserId(); + enforceCallerIsSystemOr(userId, packageName); - int userId = getCallingUserId(); Set<AssociationInfo> deviceAssociations = filter( - getAllAssociations(userId, packageName), - association -> deviceAddress.equals(association.getDeviceMacAddress())); + CompanionDeviceManagerService.this.getAssociations(userId, packageName), + a -> a.isLinkedTo(deviceAddress)); if (deviceAssociations.isEmpty()) { throw new RemoteException(new DeviceNotAssociatedException("App " + packageName @@ -478,8 +573,8 @@ public class CompanionDeviceManagerService extends SystemService { } updateAssociations(associations -> map(associations, association -> { - if (Objects.equals(association.getPackageName(), packageName) - && Objects.equals(association.getDeviceMacAddress(), deviceAddress)) { + if (association.belongsToPackage(userId, packageName) + && association.isLinkedTo(deviceAddress)) { association.setNotifyOnDeviceNearby(active); } return association; @@ -500,32 +595,34 @@ public class CompanionDeviceManagerService extends SystemService { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES, "createAssociation"); - createAssociationInternal(userId, macAddress, packageName, null); + legacyCreateAssociation(userId, macAddress, packageName, null); } - private void checkCanCallNotificationApi(String callingPackage) throws RemoteException { - checkCallerIsSystemOr(callingPackage); - int userId = getCallingUserId(); - checkState(!ArrayUtils.isEmpty(getAllAssociations(userId, callingPackage)), + private void checkCanCallNotificationApi(String callingPackage) { + final int userId = getCallingUserId(); + enforceCallerIsSystemOr(userId, callingPackage); + + checkState(!ArrayUtils.isEmpty( + CompanionDeviceManagerService.this.getAssociations(userId, callingPackage)), "App must have an association before calling this API"); checkUsesFeature(callingPackage, userId); } @Override - public boolean canPairWithoutPrompt( - String packageName, String deviceMacAddress, int userId) { - return any( - getAllAssociations(userId, packageName, deviceMacAddress), - a -> System.currentTimeMillis() - a.getTimeApprovedMs() - < PAIR_WITHOUT_PROMPT_WINDOW_MS); + public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) { + final AssociationInfo association = getAssociation(userId, packageName, macAddress); + if (association == null) { + return false; + } + return System.currentTimeMillis() - association.getTimeApprovedMs() + < PAIR_WITHOUT_PROMPT_WINDOW_MS; } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { - getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_COMPANION_DEVICES, null); + enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand"); new CompanionDeviceShellCommand(CompanionDeviceManagerService.this) .exec(this, in, out, err, args, callback, resultReceiver); } @@ -575,62 +672,29 @@ public class CompanionDeviceManagerService extends SystemService { } } - void checkCallerIsSystemOr(String pkg) throws RemoteException { - checkCallerIsSystemOr(pkg, getCallingUserId()); - } - - private void checkCallerIsSystemOr(String pkg, int userId) throws RemoteException { - if (isCallerSystem()) { - return; - } - - checkArgument(getCallingUserId() == userId, - "Must be called by either same user or system"); - int callingUid = Binder.getCallingUid(); - if (mAppOpsManager.checkPackage(callingUid, pkg) != AppOpsManager.MODE_ALLOWED) { - throw new SecurityException(pkg + " doesn't belong to uid " + callingUid); - } - } - - static int getCallingUserId() { - return UserHandle.getUserId(Binder.getCallingUid()); - } - - private static boolean isCallerSystem() { - return Binder.getCallingUid() == Process.SYSTEM_UID; - } - - void checkUsesFeature(String pkg, int userId) { - if (isCallerSystem()) { - // Drop the requirement for calls from system process - return; - } - - FeatureInfo[] reqFeatures = getPackageInfo(pkg, userId).reqFeatures; - String requiredFeature = PackageManager.FEATURE_COMPANION_DEVICE_SETUP; - int numFeatures = ArrayUtils.size(reqFeatures); - for (int i = 0; i < numFeatures; i++) { - if (requiredFeature.equals(reqFeatures[i].name)) return; - } - throw new IllegalStateException("Must declare uses-feature " - + requiredFeature - + " in manifest to use this API"); + /** + * @deprecated use + * {@link #createAssociation(int, String, MacAddress, CharSequence, String, boolean)} + */ + @Deprecated + void legacyCreateAssociation(@UserIdInt int userId, @NonNull String deviceMacAddress, + @NonNull String packageName, @Nullable String deviceProfile) { + final MacAddress macAddress = MacAddress.fromString(deviceMacAddress); + createAssociation(userId, packageName, macAddress, null, deviceProfile, false); } - void createAssociationInternal( - int userId, String deviceMacAddress, String packageName, String deviceProfile) { - final AssociationInfo association = new AssociationInfo( - getNewAssociationIdForPackage(userId, packageName), - userId, - packageName, - Arrays.asList(new DeviceId(TYPE_MAC_ADDRESS, deviceMacAddress)), - deviceProfile, - /* managedByCompanionApp */false, - /* notifyOnDeviceNearby */ false , - System.currentTimeMillis()); + AssociationInfo createAssociation(@UserIdInt int userId, @NonNull String packageName, + @Nullable MacAddress macAddress, @Nullable CharSequence displayName, + @Nullable String deviceProfile, boolean selfManaged) { + final int id = getNewAssociationIdForPackage(userId, packageName); + final long timestamp = System.currentTimeMillis(); + final AssociationInfo association = new AssociationInfo(id, userId, packageName, + macAddress, displayName, deviceProfile, selfManaged, false, timestamp); updateSpecialAccessPermissionForAssociatedPackage(association); recordAssociation(association, userId); + + return association; } @GuardedBy("mLock") @@ -648,8 +712,8 @@ public class CompanionDeviceManagerService extends SystemService { // First: collect all IDs currently in use for this user's Associations. final SparseBooleanArray usedIds = new SparseBooleanArray(); - for (AssociationInfo it : getAllAssociations(userId)) { - usedIds.put(it.getAssociationId(), true); + for (AssociationInfo it : getAllAssociationsForUser(userId)) { + usedIds.put(it.getId(), true); } // Second: collect all IDs that have been previously used for this package (and user). @@ -674,19 +738,29 @@ public class CompanionDeviceManagerService extends SystemService { } } - void removeAssociation(int userId, String packageName, String deviceMacAddress) { - updateAssociations(associations -> filterOut(associations, it -> { - final boolean match = it.belongsToPackage(userId, packageName) - && Objects.equals(it.getDeviceMacAddress(), deviceMacAddress); - if (match) { - onAssociationPreRemove(it); - markIdAsPreviouslyUsedForPackage(it.getAssociationId(), userId, packageName); - } - return match; - }), userId); + //TODO also revoke notification access + void disassociateInternal(@UserIdInt int userId, int associationId) { + updateAssociations(associations -> + filterOut(associations, it -> { + if (it.getId() != associationId) return false; + + onAssociationPreRemove(it); + markIdAsPreviouslyUsedForPackage( + it.getId(), it.getUserId(), it.getPackageName()); + return true; + }), userId); + restartBleScan(); } + void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); + + mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); + updateAssociations(set -> filterOut(set, it -> it.belongsToPackage(userId, packageName)), + userId); + } + private void markIdAsPreviouslyUsedForPackage( int associationId, @UserIdInt int userId, @NonNull String packageName) { synchronized (mLock) { @@ -707,32 +781,15 @@ public class CompanionDeviceManagerService extends SystemService { String deviceProfile = association.getDeviceProfile(); if (deviceProfile != null) { AssociationInfo otherAssociationWithDeviceProfile = find( - getAllAssociations(association.getUserId()), + getAllAssociationsForUser(association.getUserId()), a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); if (otherAssociationWithDeviceProfile != null) { Slog.i(LOG_TAG, "Not revoking " + deviceProfile + " for " + association + " - profile still present in " + otherAssociationWithDeviceProfile); } else { - final long identity = Binder.clearCallingIdentity(); - try { - mRoleManager.removeRoleHolderAsUser( - association.getDeviceProfile(), - association.getPackageName(), - RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, - UserHandle.of(association.getUserId()), - getContext().getMainExecutor(), - success -> { - if (!success) { - Slog.e(LOG_TAG, "Failed to revoke device profile role " - + association.getDeviceProfile() - + " to " + association.getPackageName() - + " for user " + association.getUserId()); - } - }); - } finally { - Binder.restoreCallingIdentity(identity); - } + Binder.withCleanCallingIdentity( + () -> removeRoleHolderForAssociation(getContext(), association)); } } } @@ -780,9 +837,9 @@ public class CompanionDeviceManagerService extends SystemService { exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - if (!association.isManagedByCompanionApp()) { - if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddress())) { - grantDeviceProfile(association); + if (!association.isSelfManaged()) { + if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) { + addRoleHolderForAssociation(getContext(), association); } if (association.isNotifyOnDeviceNearby()) { @@ -810,17 +867,10 @@ public class CompanionDeviceManagerService extends SystemService { @Nullable private PackageInfo getPackageInfo(String packageName, int userId) { - return Binder.withCleanCallingIdentity(PooledLambda.obtainSupplier((context, pkg, id) -> { - try { - return context.getPackageManager().getPackageInfoAsUser( - pkg, - PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS, - id); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + pkg, e); - return null; - } - }, getContext(), packageName, userId).recycleOnUse()); + final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS; + return Binder.withCleanCallingIdentity( + () -> getContext().getPackageManager() + .getPackageInfoAsUser(packageName, flags , userId)); } private void recordAssociation(AssociationInfo association, int userId) { @@ -833,7 +883,7 @@ public class CompanionDeviceManagerService extends SystemService { synchronized (mLock) { if (DEBUG) Slog.d(LOG_TAG, "Updating Associations set..."); - final Set<AssociationInfo> prevAssociations = getAllAssociations(userId); + final Set<AssociationInfo> prevAssociations = getAllAssociationsForUser(userId); if (DEBUG) Slog.d(LOG_TAG, " > Before : " + prevAssociations + "..."); final Set<AssociationInfo> updatedAssociations = update.apply( @@ -871,15 +921,6 @@ public class CompanionDeviceManagerService extends SystemService { } } - @NonNull Set<AssociationInfo> getAllAssociations(int userId) { - synchronized (mLock) { - readPersistedStateForUserIfNeededLocked(userId); - // This returns non-null, because the readAssociationsInfoForUserIfNeededLocked() method - // we just called adds an empty set, if there was no previously saved data. - return mCachedAssociations.get(userId); - } - } - @GuardedBy("mLock") private void readPersistedStateForUserIfNeededLocked(@UserIdInt int userId) { if (mCachedAssociations.get(userId) != null) return; @@ -908,49 +949,20 @@ public class CompanionDeviceManagerService extends SystemService { } } - Set<AssociationInfo> getAllAssociations(int userId, @Nullable String packageFilter) { - return filter( - getAllAssociations(userId), - // Null filter == get all associations - a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName())); - } - - private Set<AssociationInfo> getAllAssociations() { - final long identity = Binder.clearCallingIdentity(); - try { - ArraySet<AssociationInfo> result = new ArraySet<>(); - for (UserInfo user : mUserManager.getAliveUsers()) { - result.addAll(getAllAssociations(user.id)); - } - return result; - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private Set<AssociationInfo> getAllAssociations( - int userId, @Nullable String packageFilter, @Nullable String addressFilter) { - return filter( - getAllAssociations(userId), - // Null filter == get all associations - a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName())) - && (addressFilter == null - || Objects.equals(addressFilter, a.getDeviceMacAddress()))); - } - void onDeviceConnected(String address) { Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); mCurrentlyConnectedDevices.add(address); for (UserInfo user : getAllUsers()) { - for (AssociationInfo association : getAllAssociations(user.id)) { - if (Objects.equals(address, association.getDeviceMacAddress())) { + for (AssociationInfo association : getAllAssociationsForUser(user.id)) { + if (association.isLinkedTo(address)) { if (association.getDeviceProfile() != null) { Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() + " to " + association.getPackageName() + " due to device connected: " + association.getDeviceMacAddress()); - grantDeviceProfile(association); + + addRoleHolderForAssociation(getContext(), association); } } } @@ -959,27 +971,6 @@ public class CompanionDeviceManagerService extends SystemService { onDeviceNearby(address); } - private void grantDeviceProfile(AssociationInfo association) { - Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")"); - - if (association.getDeviceProfile() != null) { - mRoleManager.addRoleHolderAsUser( - association.getDeviceProfile(), - association.getPackageName(), - RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP, - UserHandle.of(association.getUserId()), - getContext().getMainExecutor(), - success -> { - if (!success) { - Slog.e(LOG_TAG, "Failed to grant device profile role " - + association.getDeviceProfile() - + " to " + association.getPackageName() - + " for user " + association.getUserId()); - } - }); - } - } - void onDeviceDisconnected(String address) { Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")"); @@ -1113,8 +1104,8 @@ public class CompanionDeviceManagerService extends SystemService { Set<AssociationInfo> result = new ArraySet<>(); for (int i = 0, size = aliveUsers.size(); i < size; i++) { UserInfo user = aliveUsers.get(i); - for (AssociationInfo association : getAllAssociations(user.id)) { - if (Objects.equals(association.getDeviceMacAddress(), deviceAddress)) { + for (AssociationInfo association : getAllAssociationsForUser(user.id)) { + if (association.isLinkedTo(deviceAddress)) { result.add(association); } } @@ -1224,10 +1215,10 @@ public class CompanionDeviceManagerService extends SystemService { ArrayList<ScanFilter> result = new ArrayList<>(); ArraySet<String> addressesSeen = new ArraySet<>(); for (AssociationInfo association : getAllAssociations()) { - if (association.isManagedByCompanionApp()) { + if (association.isSelfManaged()) { continue; } - String address = association.getDeviceMacAddress(); + String address = association.getDeviceMacAddressAsString(); if (addressesSeen.contains(address)) { continue; } @@ -1273,4 +1264,19 @@ public class CompanionDeviceManagerService extends SystemService { forEach(orig, (key, value) -> copy.put(key, new ArraySet<>(value))); return copy; } + + void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) { + if (getCallingUid() == SYSTEM_UID) return; + + final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures; + if (requestedFeatures != null) { + for (int i = 0; i < requestedFeatures.length; i++) { + if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return; + } + } + + throw new IllegalStateException("Must declare uses-feature " + + FEATURE_COMPANION_DEVICE_SETUP + + " in manifest to use this API"); + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java index a79db2c03849..3e008467c9eb 100644 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java @@ -67,7 +67,7 @@ public class CompanionDevicePresenceController { Slog.i(LOG_TAG, "Sending onDeviceAppeared to " + association.getPackageName() + ")"); primaryConnector.run( - service -> service.onDeviceAppeared(association.getDeviceMacAddress())); + s -> s.onDeviceAppeared(association.getDeviceMacAddressAsString())); } } @@ -78,7 +78,7 @@ public class CompanionDevicePresenceController { Slog.i(LOG_TAG, "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); primaryConnector.run( - service -> service.onDeviceDisappeared(association.getDeviceMacAddress())); + s -> s.onDeviceDisappeared(association.getDeviceMacAddressAsString())); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index e143f5e68b4c..5cb30797c0f4 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static com.android.internal.util.CollectionUtils.forEach; import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import android.companion.AssociationInfo; import android.util.Log; import android.util.Slog; @@ -37,7 +38,7 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { switch (cmd) { case "list": { forEach( - mService.getAllAssociations(getNextArgInt()), + mService.getAllAssociationsForUser(getNextArgInt()), a -> getOutPrintWriter() .println(a.getPackageName() + " " + a.getDeviceMacAddress())); @@ -48,13 +49,19 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { int userId = getNextArgInt(); String packageName = getNextArgRequired(); String address = getNextArgRequired(); - mService.createAssociationInternal(userId, address, packageName, null); + mService.legacyCreateAssociation(userId, address, packageName, null); } break; case "disassociate": { - mService.removeAssociation(getNextArgInt(), getNextArgRequired(), - getNextArgRequired()); + final int userId = getNextArgInt(); + final String packageName = getNextArgRequired(); + final String address = getNextArgRequired(); + final AssociationInfo association = + mService.getAssociationWithCallerChecks(userId, packageName, address); + if (association != null) { + mService.disassociateInternal(userId, association.getId()); + } } break; diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java new file mode 100644 index 000000000000..0d38fa365641 --- /dev/null +++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; +import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; +import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Binder.getCallingUid; +import static android.os.Process.SYSTEM_UID; +import static android.os.UserHandle.getCallingUserId; + +import static java.util.Collections.unmodifiableMap; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.companion.AssociationRequest; +import android.companion.CompanionDeviceManager; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.ArrayMap; + +import com.android.internal.app.IAppOpsService; + +import java.util.Map; + +/** + * Utility methods for checking permissions required for accessing {@link CompanionDeviceManager} + * APIs (such as {@link Manifest.permission#REQUEST_COMPANION_PROFILE_WATCH}, + * {@link Manifest.permission#REQUEST_COMPANION_PROFILE_APP_STREAMING}, + * {@link Manifest.permission#REQUEST_COMPANION_SELF_MANAGED} etc.) + */ +final class PermissionsUtils { + + private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION; + static { + final Map<String, String> map = new ArrayMap<>(); + map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); + map.put(DEVICE_PROFILE_APP_STREAMING, + Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING); + map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, + Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION); + + DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); + } + + static void enforceCallerPermissionsToRequest(@NonNull Context context, + @NonNull AssociationRequest request, @NonNull String packageName, + @UserIdInt int userId) { + enforceCallerCanInteractWithUserId(context, userId); + enforceCallerIsSystemOr(userId, packageName); + + enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile()); + + if (request.isSelfManaged()) { + enforceRequestSelfManagedPermission(context); + } + } + + static void enforceRequestDeviceProfilePermissions( + @NonNull Context context, @Nullable String deviceProfile) { + // Device profile can be null. + if (deviceProfile == null) return; + + if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) { + throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile); + } + + if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) { + // TODO: remove, when properly supporting this profile. + throw new UnsupportedOperationException( + "DEVICE_PROFILE_APP_STREAMING is not fully supported yet."); + } + + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + // TODO: remove, when properly supporting this profile. + throw new UnsupportedOperationException( + "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet."); + } + + final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile); + if (context.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { + throw new SecurityException("Application must hold " + permission + " to associate " + + "with a device with " + deviceProfile + " profile."); + } + } + + static void enforceRequestSelfManagedPermission(@NonNull Context context) { + if (context.checkCallingOrSelfPermission(REQUEST_COMPANION_SELF_MANAGED) + != PERMISSION_GRANTED) { + throw new SecurityException("Application does not hold " + + REQUEST_COMPANION_SELF_MANAGED); + } + } + + static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) { + if (getCallingUserId() == userId) return true; + + return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED; + } + + static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) { + if (getCallingUserId() == userId) return; + + context.enforceCallingPermission(INTERACT_ACROSS_USERS, null); + } + + static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) { + final int callingUid = getCallingUid(); + if (callingUid == SYSTEM_UID) return true; + + if (getCallingUserId() != userId) return false; + + if (!checkPackage(callingUid, packageName)) return false; + + return true; + } + + static void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) { + final int callingUid = getCallingUid(); + if (callingUid == SYSTEM_UID) return; + + final int callingUserId = getCallingUserId(); + if (getCallingUserId() != userId) { + throw new SecurityException("Calling UserId (" + callingUserId + ") does not match " + + "the expected UserId (" + userId + ")"); + } + + if (!checkPackage(callingUid, packageName)) { + throw new SecurityException(packageName + " doesn't belong to calling uid (" + + callingUid + ")"); + } + } + + static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) { + if (getCallingUserId() == SYSTEM_UID) return true; + + return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED; + } + + static void enforceCallerCanManagerCompanionDevice(@NonNull Context context, + @Nullable String message) { + if (getCallingUserId() == SYSTEM_UID) return; + + context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message); + } + + static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context, + @UserIdInt int userId, @NonNull String packageName) { + if (checkCallerIsSystemOr(userId, packageName)) return true; + + if (!checkCallerCanInteractWithUserId(context, userId)) return false; + + return checkCallerCanManageCompanionDevice(context); + } + + private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) { + try { + return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED; + } catch (RemoteException e) { + // Can't happen: AppOpsManager is running in the same process. + return true; + } + } + + private static IAppOpsService getAppOpsService() { + if (sAppOpsService == null) { + synchronized (PermissionsUtils.class) { + if (sAppOpsService == null) { + sAppOpsService = IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); + } + } + } + return sAppOpsService; + } + + // DO NOT USE DIRECTLY! Access via getAppOpsService(). + private static IAppOpsService sAppOpsService = null; + + private PermissionsUtils() {} +} diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 5b8d7e507fc0..87558dfd4ffd 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -16,8 +16,6 @@ package com.android.server.companion; -import static android.companion.DeviceId.TYPE_MAC_ADDRESS; - import static com.android.internal.util.CollectionUtils.forEach; import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; @@ -35,7 +33,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.companion.AssociationInfo; -import android.companion.DeviceId; +import android.net.MacAddress; import android.os.Environment; import android.util.AtomicFile; import android.util.ExceptionUtils; @@ -53,10 +51,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -101,8 +96,8 @@ import java.util.concurrent.ConcurrentMap; * Since Android T the data is stored using the v1 schema. * In the v1 schema, a list of the previously used IDs is storead along with the association * records. - * In the v1 schema, we no longer store MAC addresses, instead each assocition record may have a - * number of DeviceIds. + * V1 schema adds a new optional `display_name` attribute, and makes the `mac_address` attribute + * optional. * * @see #CURRENT_PERSISTENCE_VERSION * @see #readAssociationsV1(TypedXmlPullParser, int, Set) @@ -116,21 +111,19 @@ import java.util.concurrent.ConcurrentMap; * <association * id="1" * package="com.sample.companion.app" - * managed_by_app="false" + * mac_address="AA:BB:CC:DD:EE:00" + * self_managed="false" * notify_device_nearby="false" - * time_approved="1634389553216"> - * <device-id type="mac_address" value="AA:BB:CC:DD:EE:00" /> - * </association> + * time_approved="1634389553216"/> * * <association * id="3" * profile="android.app.role.COMPANION_DEVICE_WATCH" * package="com.sample.companion.another.app" - * managed_by_app="false" + * display_name="Jhon's Chromebook" + * self_managed="true" * notify_device_nearby="false" - * time_approved="1634641160229"> - * <device-id type="mac_address" value="AA:BB:CC:DD:EE:FF" /> - * </association> + * time_approved="1634641160229"/> * </associations> * * <previously-used-ids> @@ -153,7 +146,6 @@ final class PersistentDataStore { private static final String XML_TAG_STATE = "state"; private static final String XML_TAG_ASSOCIATIONS = "associations"; private static final String XML_TAG_ASSOCIATION = "association"; - private static final String XML_TAG_DEVICE_ID = "device-id"; private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids"; private static final String XML_TAG_PACKAGE = "package"; private static final String XML_TAG_ID = "id"; @@ -164,13 +156,14 @@ final class PersistentDataStore { private static final String XML_ATTR_PACKAGE_NAME = "package_name"; // Used in <association> elements, nested within <associations> elements. private static final String XML_ATTR_PACKAGE = "package"; - private static final String XML_ATTR_DEVICE = "device"; + private static final String XML_ATTR_MAC_ADDRESS = "mac_address"; + private static final String XML_ATTR_DISPLAY_NAME = "display_name"; private static final String XML_ATTR_PROFILE = "profile"; - private static final String XML_ATTR_MANAGED_BY_APP = "managed_by_app"; + private static final String XML_ATTR_SELF_MANAGED = "self_managed"; private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby"; private static final String XML_ATTR_TIME_APPROVED = "time_approved"; - private static final String XML_ATTR_TYPE = "type"; - private static final String XML_ATTR_VALUE = "value"; + + private static final String LEGACY_XML_ATTR_DEVICE = "device"; private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile = new ConcurrentHashMap<>(); @@ -353,9 +346,7 @@ final class PersistentDataStore { requireStartOfTag(parser, XML_TAG_ASSOCIATION); final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE); - // In v0, CDM did not have a notion of a DeviceId yet, instead each Association had a MAC - // address. - final String deviceAddress = readStringAttribute(parser, XML_ATTR_DEVICE); + final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE); if (appPackage == null || deviceAddress == null) return; @@ -363,10 +354,8 @@ final class PersistentDataStore { final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY); final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); - // "Convert" MAC address into a DeviceId. - final List<DeviceId> deviceIds = Arrays.asList( - new DeviceId(TYPE_MAC_ADDRESS, deviceAddress)); - out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile, + out.add(new AssociationInfo(associationId, userId, appPackage, + MacAddress.fromString(deviceAddress), null, profile, /* managedByCompanionApp */false, notify, timeApproved)); } @@ -391,23 +380,18 @@ final class PersistentDataStore { final int associationId = readIntAttribute(parser, XML_ATTR_ID); final String profile = readStringAttribute(parser, XML_ATTR_PROFILE); final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE); - final boolean managedByApp = readBooleanAttribute(parser, XML_ATTR_MANAGED_BY_APP); + final MacAddress macAddress = stringToMacAddress( + readStringAttribute(parser, XML_ATTR_MAC_ADDRESS)); + final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME); + final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED); final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY); final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); - final List<DeviceId> deviceIds = new ArrayList<>(); - while (true) { - parser.nextTag(); - if (isEndOfTag(parser, XML_TAG_ASSOCIATION)) break; - if (!isStartOfTag(parser, XML_TAG_DEVICE_ID)) continue; - - final String type = readStringAttribute(parser, XML_ATTR_TYPE); - final String value = readStringAttribute(parser, XML_ATTR_VALUE); - deviceIds.add(new DeviceId(type, value)); + final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId, + appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved); + if (associationInfo != null) { + out.add(associationInfo); } - - out.add(new AssociationInfo(associationId, userId, appPackage, deviceIds, profile, - managedByApp, notify, timeApproved)); } private static void readPreviouslyUsedIdsV1(@NonNull TypedXmlPullParser parser, @@ -447,32 +431,19 @@ final class PersistentDataStore { throws IOException { final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATION); - writeIntAttribute(serializer, XML_ATTR_ID, a.getAssociationId()); + writeIntAttribute(serializer, XML_ATTR_ID, a.getId()); writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile()); writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName()); - writeBooleanAttribute(serializer, XML_ATTR_MANAGED_BY_APP, a.isManagedByCompanionApp()); + writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString()); + writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName()); + writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged()); writeBooleanAttribute( serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby()); writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs()); - final List<DeviceId> deviceIds = a.getDeviceIds(); - for (int i = 0, size = deviceIds.size(); i < size; i++) { - writeDeviceId(serializer, deviceIds.get(i)); - } - serializer.endTag(null, XML_TAG_ASSOCIATION); } - private static void writeDeviceId(@NonNull XmlSerializer parent, @NonNull DeviceId deviceId) - throws IOException { - final XmlSerializer serializer = parent.startTag(null, XML_TAG_DEVICE_ID); - - writeStringAttribute(serializer, XML_ATTR_TYPE, deviceId.getType()); - writeStringAttribute(serializer, XML_ATTR_VALUE, deviceId.getValue()); - - serializer.endTag(null, XML_TAG_DEVICE_ID); - } - private static void writePreviouslyUsedIds(@NonNull XmlSerializer parent, @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) throws IOException { final XmlSerializer serializer = parent.startTag(null, XML_TAG_PREVIOUSLY_USED_IDS); @@ -509,4 +480,22 @@ final class PersistentDataStore { throw new XmlPullParserException( "Should be at the start of \"" + XML_TAG_ASSOCIATIONS + "\" tag"); } + + private static @Nullable MacAddress stringToMacAddress(@Nullable String address) { + return address != null ? MacAddress.fromString(address) : null; + } + + private static AssociationInfo createAssociationInfoNoThrow(int associationId, + @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress, + @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged, + boolean notify, long timeApproved) { + AssociationInfo associationInfo = null; + try { + associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, + displayName, profile, selfManaged, notify, timeApproved); + } catch (Exception e) { + if (DEBUG) Slog.w(LOG_TAG, "Could not create AssociationInfo", e); + } + return associationInfo; + } } diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java new file mode 100644 index 000000000000..76340fc1d6a8 --- /dev/null +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion; + +import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; + +import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; +import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.role.RoleManager; +import android.companion.AssociationInfo; +import android.content.Context; +import android.os.UserHandle; +import android.util.Slog; + +import java.util.List; + +/** Utility methods for accessing {@link RoleManager} APIs. */ +final class RolesUtils { + + static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, + @NonNull String packageName, @NonNull String role) { + final RoleManager roleManager = context.getSystemService(RoleManager.class); + final List<String> roleHolders = roleManager.getRoleHoldersAsUser( + role, UserHandle.of(userId)); + return roleHolders.contains(packageName); + } + + static void addRoleHolderForAssociation( + @NonNull Context context, @NonNull AssociationInfo associationInfo) { + if (DEBUG) { + Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo); + } + + final String deviceProfile = associationInfo.getDeviceProfile(); + if (deviceProfile == null) return; + + final RoleManager roleManager = context.getSystemService(RoleManager.class); + + final String packageName = associationInfo.getPackageName(); + final int userId = associationInfo.getUserId(); + final UserHandle userHandle = UserHandle.of(userId); + + roleManager.addRoleHolderAsUser(deviceProfile, packageName, + MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), + success -> { + if (!success) { + Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName + + " to the list of " + deviceProfile + " holders."); + } + }); + } + + static void removeRoleHolderForAssociation( + @NonNull Context context, @NonNull AssociationInfo associationInfo) { + if (DEBUG) { + Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo); + } + + final String deviceProfile = associationInfo.getDeviceProfile(); + if (deviceProfile == null) return; + + final RoleManager roleManager = context.getSystemService(RoleManager.class); + + final String packageName = associationInfo.getPackageName(); + final int userId = associationInfo.getUserId(); + final UserHandle userHandle = UserHandle.of(userId); + + roleManager.removeRoleHolderAsUser(deviceProfile, packageName, + MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), + success -> { + if (!success) { + Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName + + " from the list of " + deviceProfile + " holders."); + } + }); + } + + private RolesUtils() {}; +} diff --git a/services/core/Android.bp b/services/core/Android.bp index 9f22489f9305..9351415c1fb0 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -137,6 +137,7 @@ java_library_static { ], required: [ + "default_television.xml", "gps_debug.conf", "protolog.conf.json.gz", ], diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 6ac015b52553..f3fad84b576b 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.AppIdInt; import android.annotation.IntDef; +import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -108,7 +109,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP // Please note the numbers should be continuous. public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS; - @IntDef(flag = true, prefix = "RESOLVE_", value = { + @LongDef(flag = true, prefix = "RESOLVE_", value = { RESOLVE_NON_BROWSER_ONLY, RESOLVE_NON_RESOLVER_ONLY }) @@ -197,7 +198,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @see PackageManager#getPackageInfo(String, int) */ public abstract PackageInfo getPackageInfo(String packageName, - @PackageInfoFlags int flags, int filterCallingUid, int userId); + @PackageInfoFlags long flags, int filterCallingUid, int userId); /** * Retrieve CE data directory inode number of an application. @@ -226,7 +227,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * deleted with {@code DELETE_KEEP_DATA} flag set). */ public abstract List<ApplicationInfo> getInstalledApplications( - @ApplicationInfoFlags int flags, @UserIdInt int userId, int callingUid); + @ApplicationInfoFlags long flags, @UserIdInt int userId, int callingUid); /** * Retrieve launcher extras for a suspended package provided to the system in @@ -323,7 +324,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @see PackageManager#getPackageUidAsUser(String, int, int) * @return The app's uid, or < 0 if the package was not found in that user */ - public abstract int getPackageUid(String packageName, @PackageInfoFlags int flags, int userId); + public abstract int getPackageUid(String packageName, @PackageInfoFlags long flags, int userId); /** * Retrieve all of the information we know about a particular package/application. @@ -332,7 +333,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @see PackageManager#getApplicationInfo(String, int) */ public abstract ApplicationInfo getApplicationInfo(String packageName, - @ApplicationInfoFlags int flags, int filterCallingUid, int userId); + @ApplicationInfoFlags long flags, int filterCallingUid, int userId); /** * Retrieve all of the information we know about a particular activity class. @@ -341,7 +342,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @see PackageManager#getActivityInfo(ComponentName, int) */ public abstract ActivityInfo getActivityInfo(ComponentName component, - @ComponentInfoFlags int flags, int filterCallingUid, int userId); + @ComponentInfoFlags long flags, int filterCallingUid, int userId); /** * Retrieve all activities that can be performed for the given intent. @@ -352,7 +353,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @see PackageManager#queryIntentActivities(Intent, int) */ public abstract List<ResolveInfo> queryIntentActivities( - Intent intent, @Nullable String resolvedType, @ResolveInfoFlags int flags, + Intent intent, @Nullable String resolvedType, @ResolveInfoFlags long flags, int filterCallingUid, int userId); @@ -360,14 +361,14 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * Retrieve all receivers that can handle a broadcast of the given intent. */ public abstract List<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, int flags, int filterCallingUid, int userId); + String resolvedType, @ResolveInfoFlags long flags, int filterCallingUid, int userId); /** * Retrieve all services that can be performed for the given intent. * @see PackageManager#queryIntentServices(Intent, int) */ public abstract List<ResolveInfo> queryIntentServices( - Intent intent, int flags, int callingUid, int userId); + Intent intent, @ResolveInfoFlags long flags, int callingUid, int userId); /** * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}. @@ -591,20 +592,20 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * Resolves an activity intent, allowing instant apps to be resolved. */ public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType, - int flags, @PrivateResolveFlags int privateResolveFlags, int userId, + @ResolveInfoFlags long flags, @PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart, int filterCallingUid); /** * Resolves a service intent, allowing instant apps to be resolved. */ public abstract ResolveInfo resolveService(Intent intent, String resolvedType, - int flags, int userId, int callingUid); + @ResolveInfoFlags long flags, int userId, int callingUid); /** * Resolves a content provider intent. */ - public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId, - int callingUid); + public abstract ProviderInfo resolveContentProvider(String name, @ComponentInfoFlags long flags, + int userId, int callingUid); /** * Track the creator of a new isolated uid. @@ -875,8 +876,8 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP throws IOException; /** Returns {@code true} if the specified component is enabled and matches the given flags. */ - public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component, int flags, - int userId); + public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component, + @ComponentInfoFlags long flags, int userId); /** Returns {@code true} if the given user requires extra badging for icons. */ public abstract boolean userNeedsBadging(int userId); @@ -1024,7 +1025,7 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * @param flags flags about the uninstall. */ public abstract void uninstallApex(String packageName, long versionCode, int userId, - IntentSender intentSender, int flags); + IntentSender intentSender, @PackageManager.InstallFlags int installFlags); /** * Update fingerprint of build that updated the runtime permissions for a user. @@ -1260,5 +1261,5 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP /** * Reconcile all app data for the given user. */ - public abstract void reconcileAppsData(int userId, int flags, boolean migrateAppsData); + public abstract void reconcileAppsData(int userId, int storageFlags, boolean migrateAppsData); } diff --git a/services/core/java/com/android/server/AppFuseMountException.java b/services/core/java/com/android/server/AppFuseMountException.java new file mode 100644 index 000000000000..9a9379e4a1c7 --- /dev/null +++ b/services/core/java/com/android/server/AppFuseMountException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.os.Parcel; + +/** + * An exception that indicates there was an error with a + * app fuse mount operation. + */ +public class AppFuseMountException extends Exception { + public AppFuseMountException(String detailMessage) { + super(detailMessage); + } + + public AppFuseMountException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + /** + * Rethrow as a {@link RuntimeException} subclass that is handled by + * {@link Parcel#writeException(Exception)}. + */ + public IllegalArgumentException rethrowAsParcelableException() { + throw new IllegalStateException(getMessage(), this); + } +} diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java index 197321f1cb6a..263ff189a288 100644 --- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java +++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; * when Bluetooth is on and Bluetooth is in one of the following situations: * 1. Bluetooth A2DP is connected. * 2. Bluetooth Hearing Aid profile is connected. + * 3. Bluetooth LE Audio is connected */ class BluetoothAirplaneModeListener { private static final String TAG = "BluetoothAirplaneModeListener"; @@ -132,7 +133,7 @@ class BluetoothAirplaneModeListener { return false; } if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() - || !mAirplaneHelper.isA2dpOrHearingAidConnected()) { + || !mAirplaneHelper.isMediaProfileConnected()) { return false; } return true; diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index f62935ab1b13..8860a8164109 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -35,6 +35,7 @@ import android.app.BroadcastOptions; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProtoEnums; import android.bluetooth.IBluetooth; @@ -456,12 +457,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) - || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { + || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action) + || BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(action)) { final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED) && state == BluetoothProfile.STATE_DISCONNECTED - && !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + && !mBluetoothModeChangeHelper.isMediaProfileConnected()) { Slog.i(TAG, "Device disconnected, reactivating pending flag changes"); onInitFlagsChanged(); } @@ -2291,7 +2293,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED"); } mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); - if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) { + if (mBluetoothModeChangeHelper.isMediaProfileConnected()) { Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS + " ms due to existing connections"); diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java index 3642e4dccf34..e5854c968207 100644 --- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java +++ b/services/core/java/com/android/server/BluetoothModeChangeHelper.java @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; @@ -37,6 +38,7 @@ import com.android.internal.annotations.VisibleForTesting; public class BluetoothModeChangeHelper { private volatile BluetoothA2dp mA2dp; private volatile BluetoothHearingAid mHearingAid; + private volatile BluetoothLeAudio mLeAudio; private final BluetoothAdapter mAdapter; private final Context mContext; @@ -47,6 +49,7 @@ public class BluetoothModeChangeHelper { mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.HEARING_AID); + mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.LE_AUDIO); } private final ServiceListener mProfileServiceListener = new ServiceListener() { @@ -60,6 +63,9 @@ public class BluetoothModeChangeHelper { case BluetoothProfile.HEARING_AID: mHearingAid = (BluetoothHearingAid) proxy; break; + case BluetoothProfile.LE_AUDIO: + mLeAudio = (BluetoothLeAudio) proxy; + break; default: break; } @@ -75,6 +81,9 @@ public class BluetoothModeChangeHelper { case BluetoothProfile.HEARING_AID: mHearingAid = null; break; + case BluetoothProfile.LE_AUDIO: + mLeAudio = null; + break; default: break; } @@ -82,8 +91,8 @@ public class BluetoothModeChangeHelper { }; @VisibleForTesting - public boolean isA2dpOrHearingAidConnected() { - return isA2dpConnected() || isHearingAidConnected(); + public boolean isMediaProfileConnected() { + return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected(); } @VisibleForTesting @@ -142,4 +151,12 @@ public class BluetoothModeChangeHelper { } return hearingAid.getConnectedDevices().size() > 0; } + + private boolean isLeAudioConnected() { + final BluetoothLeAudio leAudio = mLeAudio; + if (leAudio == null) { + return false; + } + return leAudio.getConnectedDevices().size() > 0; + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 6f009b6c2f40..1929df8ce124 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1223,7 +1223,7 @@ class StorageManagerService extends IStorageManager.Stub } for (int i = 0; i < mVolumes.size(); i++) { final VolumeInfo vol = mVolumes.valueAt(i); - if (vol.isVisibleForRead(userId) && vol.isMountedReadable()) { + if (vol.isVisibleForUser(userId) && vol.isMountedReadable()) { final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false); mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget(); @@ -1570,7 +1570,7 @@ class StorageManagerService extends IStorageManager.Stub || Objects.equals(privateVol.fsUuid, mPrimaryStorageUuid)) { Slog.v(TAG, "Found primary storage at " + vol); vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY; - vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE; mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget(); } @@ -1580,13 +1580,13 @@ class StorageManagerService extends IStorageManager.Stub && vol.disk.isDefaultPrimary()) { Slog.v(TAG, "Found primary storage at " + vol); vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY; - vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE; } // Adoptable public disks are visible to apps, since they meet // public API requirement of being in a stable location. if (vol.disk.isAdoptable()) { - vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE; } vol.mountUserId = mCurrentUserId; @@ -1597,7 +1597,7 @@ class StorageManagerService extends IStorageManager.Stub } else if (vol.type == VolumeInfo.TYPE_STUB) { if (vol.disk.isStubVisible()) { - vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE; + vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE_FOR_WRITE; } vol.mountUserId = mCurrentUserId; mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget(); @@ -1744,7 +1744,7 @@ class StorageManagerService extends IStorageManager.Stub // started after this point will trigger additional // user-specific broadcasts. for (int userId : mSystemUnlockedUsers) { - if (vol.isVisibleForRead(userId)) { + if (vol.isVisibleForUser(userId)) { final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false); mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget(); @@ -3565,24 +3565,24 @@ class StorageManagerService extends IStorageManager.Stub } @Override - public ParcelFileDescriptor open() throws NativeDaemonConnectorException { + public ParcelFileDescriptor open() throws AppFuseMountException { try { final FileDescriptor fd = mVold.mountAppFuse(uid, mountId); mMounted = true; return new ParcelFileDescriptor(fd); } catch (Exception e) { - throw new NativeDaemonConnectorException("Failed to mount", e); + throw new AppFuseMountException("Failed to mount", e); } } @Override public ParcelFileDescriptor openFile(int mountId, int fileId, int flags) - throws NativeDaemonConnectorException { + throws AppFuseMountException { try { return new ParcelFileDescriptor( mVold.openAppFuseFile(uid, mountId, fileId, flags)); } catch (Exception e) { - throw new NativeDaemonConnectorException("Failed to open", e); + throw new AppFuseMountException("Failed to open", e); } } @@ -3622,7 +3622,7 @@ class StorageManagerService extends IStorageManager.Stub // It seems the thread of mAppFuseBridge has already been terminated. mAppFuseBridge = null; } - } catch (NativeDaemonConnectorException e) { + } catch (AppFuseMountException e) { throw e.rethrowAsParcelableException(); } } @@ -3783,7 +3783,7 @@ class StorageManagerService extends IStorageManager.Stub if (forWrite) { match = vol.isVisibleForWrite(userId); } else { - match = vol.isVisibleForRead(userId) + match = vol.isVisibleForUser(userId) || (includeInvisible && vol.getPath() != null); } if (!match) continue; diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index b068f86ff0ab..0c990ecfc827 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -141,7 +141,7 @@ import java.util.concurrent.TimeUnit; * | or its properties * v | * +-----------------------------------------------------------------------+ - * | UnderlyingNetworkTracker | + * | UnderlyingNetworkController | * | | * | Manages lifecycle of underlying physical networks, filing requests to | * | bring them up, and releasing them as they become no longer necessary | diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index b019789e1982..ac20a08c1e30 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -685,6 +685,7 @@ public class Watchdog { final UUID errorId = mTraceErrorLogger.generateErrorId(); if (mTraceErrorLogger.isAddErrorIdEnabled()) { mTraceErrorLogger.addErrorIdToTrace("system_server", errorId); + mTraceErrorLogger.addSubjectToTrace(subject, errorId); } // Log the atom as early as possible since it is used as a mechanism to trigger diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 6decdb9ab0e3..7993936cd568 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -158,6 +158,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; @@ -4524,9 +4525,12 @@ public final class ActiveServices { if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); - msg.obj = r.app; - msg.getData().putCharSequence( - ActivityManagerService.SERVICE_RECORD_KEY, r.toString()); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = r.app; + args.arg2 = r.toString(); + args.arg3 = r.getComponentName(); + + msg.obj = args; mAm.mHandler.sendMessage(msg); } } @@ -5691,11 +5695,14 @@ public final class ActiveServices { } } - void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) { - mAm.crashApplicationWithType(app.uid, app.getPid(), app.info.packageName, app.userId, + void serviceForegroundCrash(ProcessRecord app, String serviceRecord, + ComponentName service) { + mAm.crashApplicationWithTypeWithExtras( + app.uid, app.getPid(), app.info.packageName, app.userId, "Context.startForegroundService() did not then call Service.startForeground(): " + serviceRecord, false /*force*/, - ForegroundServiceDidNotStartInTimeException.TYPE_ID); + ForegroundServiceDidNotStartInTimeException.TYPE_ID, + ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } void scheduleServiceTimeoutLocked(ProcessRecord proc) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 15871aa8094b..a33aa600febd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -345,6 +345,7 @@ import com.android.internal.os.BinderTransactionNameResolver; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.policy.AttributeCache; @@ -1511,8 +1512,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int FIRST_BROADCAST_QUEUE_MSG = 200; - static final String SERVICE_RECORD_KEY = "servicerecord"; - /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -1691,8 +1690,12 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.serviceForegroundTimeout((ServiceRecord) msg.obj); } break; case SERVICE_FOREGROUND_CRASH_MSG: { - mServices.serviceForegroundCrash((ProcessRecord) msg.obj, - msg.getData().getCharSequence(SERVICE_RECORD_KEY)); + SomeArgs args = (SomeArgs) msg.obj; + mServices.serviceForegroundCrash( + (ProcessRecord) args.arg1, + (String) args.arg2, + (ComponentName) args.arg3); + args.recycle(); } break; case UPDATE_TIME_ZONE: { synchronized (mProcLock) { @@ -3048,6 +3051,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId, String message, boolean force, int exceptionTypeId) { + crashApplicationWithTypeWithExtras(uid, initialPid, packageName, userId, message, + force, exceptionTypeId, null); + } + + @Override + public void crashApplicationWithTypeWithExtras(int uid, int initialPid, String packageName, + int userId, String message, boolean force, int exceptionTypeId, + @Nullable Bundle extras) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: crashApplication() from pid=" @@ -3060,7 +3071,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, - message, force, exceptionTypeId); + message, force, exceptionTypeId, extras); } } @@ -4288,7 +4299,7 @@ public class ActivityManagerService extends IActivityManager.Stub didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId, ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit, - evenPersistent, true /* setRemoved */, + evenPersistent, true /* setRemoved */, uninstalling, packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED : ApplicationExitInfo.REASON_USER_REQUESTED, ApplicationExitInfo.SUBREASON_UNKNOWN, @@ -7371,6 +7382,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_KILL_UID, reason != null ? reason : "kill uid"); @@ -7392,6 +7404,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */, true /* callerWillRestart */, true /* doit */, true /* evenPersistent */, false /* setRemoved */, + false /* uninstalling */, ApplicationExitInfo.REASON_PERMISSION_CHANGE, ApplicationExitInfo.SUBREASON_UNKNOWN, reason != null ? reason : "kill uid"); @@ -15417,6 +15430,16 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public String getSwitchingFromUserMessage() { + return mUserController.getSwitchingFromSystemUserMessage(); + } + + @Override + public String getSwitchingToUserMessage() { + return mUserController.getSwitchingToSystemUserMessage(); + } + + @Override public void setStopUserOnSwitch(@StopUserOnSwitch int value) { mUserController.setStopUserOnSwitch(value); } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 0dfdfe94b6ec..6c1a00d9fc22 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -27,6 +27,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AnrController; @@ -40,6 +41,7 @@ import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Message; import android.os.Process; import android.os.SystemClock; @@ -489,7 +491,7 @@ class AppErrors { * @param message */ void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId, - String message, boolean force, int exceptionTypeId) { + String message, boolean force, int exceptionTypeId, @Nullable Bundle extras) { ProcessRecord proc = null; // Figure out which process to kill. We don't trust that initialPid @@ -521,7 +523,7 @@ class AppErrors { return; } - proc.scheduleCrashLocked(message, exceptionTypeId); + proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected // and then the delayed summary kill will be a no-op. diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index 7b5f2cd78947..259dd8ec567d 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -53,7 +53,7 @@ public class BaseErrorDialog extends AlertDialog { mHandler.sendEmptyMessage(DISABLE_BUTTONS); mHandler.sendMessageDelayed(mHandler.obtainMessage(ENABLE_BUTTONS), 1000); getContext().registerReceiver(mReceiver, - new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), Context.RECEIVER_EXPORTED); } @Override diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 17daa753100c..91d648807204 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -615,7 +615,7 @@ public final class BroadcastQueue { Slog.w(TAG, "Can't deliver broadcast to " + app.processName + " (pid " + app.getPid() + "). Crashing it."); app.scheduleCrashLocked("can't deliver broadcast", - CannotDeliverBroadcastException.TYPE_ID); + CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null); } throw ex; } @@ -789,7 +789,7 @@ public final class BroadcastQueue { // Ensure that broadcasts are only sent to other apps if they are explicitly marked as // exported, or are System level broadcasts - if (!skip && !filter.exported && Process.SYSTEM_UID != r.callingUid + if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid) && filter.receiverList.uid != r.callingUid) { Slog.w(TAG, "Exported Denial: sending " @@ -800,7 +800,7 @@ public final class BroadcastQueue { + " due to receiver " + filter.receiverList.app + " (uid " + filter.receiverList.uid + ")" + " not specifying RECEIVER_EXPORTED"); - skip = true; + // skip = true; } if (skip) { diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 42205060e5d5..18ad1f557ee6 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -286,6 +286,7 @@ class ProcessErrorStateRecord { && mService.mTraceErrorLogger.isAddErrorIdEnabled()) { errorId = mService.mTraceErrorLogger.generateErrorId(); mService.mTraceErrorLogger.addErrorIdToTrace(mApp.processName, errorId); + mService.mTraceErrorLogger.addSubjectToTrace(annotation, errorId); } else { errorId = null; } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bbd41f7da09d..80a8d6376825 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -135,7 +135,6 @@ import com.android.server.Watchdog; import com.android.server.am.ActivityManagerService.ProcessChangeItem; import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.DexManager; -import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; @@ -2794,8 +2793,8 @@ public final class ProcessList { int reasonCode, int subReason, String reason) { return killPackageProcessesLSP(packageName, appId, userId, minOomAdj, false /* callerWillRestart */, true /* allowRestart */, true /* doit */, - false /* evenPersistent */, false /* setRemoved */, reasonCode, - subReason, reason); + false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */, + reasonCode, subReason, reason); } @GuardedBy("mService") @@ -2828,9 +2827,10 @@ public final class ProcessList { @GuardedBy({"mService", "mProcLock"}) boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, - boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode, - int subReason, String reason) { - ArrayList<ProcessRecord> procs = new ArrayList<>(); + boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling, + int reasonCode, int subReason, String reason) { + final PackageManagerInternal pm = mService.getPackageManagerInternal(); + final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>(); // Remove all processes this package may have touched: all with the // same UID (except for the system or root user), and all whose name @@ -2847,7 +2847,18 @@ public final class ProcessList { } if (app.isRemoved()) { if (doit) { - procs.add(app); + boolean shouldAllowRestart = false; + if (!uninstalling && packageName != null) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = !app.getPkgList().containsKey(packageName) + && app.getPkgDeps() != null + && app.getPkgDeps().contains(packageName) + && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, + app.userId); + } + procs.add(new Pair<>(app, shouldAllowRestart)); } continue; } @@ -2862,6 +2873,8 @@ public final class ProcessList { continue; } + boolean shouldAllowRestart = false; + // If no package is specified, we call all processes under the // give user id. if (packageName == null) { @@ -2883,9 +2896,16 @@ public final class ProcessList { if (userId != UserHandle.USER_ALL && app.userId != userId) { continue; } - if (!app.getPkgList().containsKey(packageName) && !isDep) { + final boolean isInPkgList = app.getPkgList().containsKey(packageName); + if (!isInPkgList && !isDep) { continue; } + if (!isInPkgList && isDep && !uninstalling && app.info != null + && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) { + // This package has a dependency on the given package being stopped, + // while it's not being frozen nor uninstalled, allow to restart it. + shouldAllowRestart = true; + } } // Process has passed all conditions, kill it! @@ -2895,13 +2915,14 @@ public final class ProcessList { if (setRemoved) { app.setRemoved(true); } - procs.add(app); + procs.add(new Pair<>(app, shouldAllowRestart)); } } int N = procs.size(); for (int i=0; i<N; i++) { - removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, + final Pair<ProcessRecord, Boolean> proc = procs.get(i); + removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, reasonCode, subReason, reason); } killAppZygotesLocked(packageName, appId, userId, false /* force */); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index eba02f100ba0..c830554c398b 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -31,6 +31,7 @@ import android.content.pm.ProcessInfo; import android.content.pm.VersionedPackage; import android.content.res.CompatibilityInfo; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -985,7 +986,7 @@ class ProcessRecord implements WindowProcessListener { * of its subclasses. */ @GuardedBy("mService") - void scheduleCrashLocked(String message, int exceptionTypeId) { + void scheduleCrashLocked(String message, int exceptionTypeId, @Nullable Bundle extras) { // Checking killedbyAm should keep it from showing the crash dialog if the process // was already dead for a good / normal reason. if (!mKilledByAm) { @@ -996,7 +997,7 @@ class ProcessRecord implements WindowProcessListener { } final long ident = Binder.clearCallingIdentity(); try { - mThread.scheduleCrash(message, exceptionTypeId); + mThread.scheduleCrash(message, exceptionTypeId, extras); } catch (RemoteException e) { // If it's already dead our work is done. If it's wedged just kill it. // We won't get the crash dialog or the error reporting. diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java index c65810097433..29a9b5c501c4 100644 --- a/services/core/java/com/android/server/am/TraceErrorLogger.java +++ b/services/core/java/com/android/server/am/TraceErrorLogger.java @@ -54,4 +54,17 @@ public class TraceErrorLogger { COUNTER_PREFIX + processName + "#" + errorId.toString(), PLACEHOLDER_VALUE); } + + /** + * Pushes a counter containing an ANR/Watchdog subject and a unique id so that the subject + * can be uniquely identified. + * + * @param subject The subject to include in the trace. + * @param errorId The unique id with which to tag the trace. + */ + public void addSubjectToTrace(String subject, UUID errorId) { + Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, + String.format("Subject(for ErrorId %s):%s", errorId.toString(), subject), + PLACEHOLDER_VALUE); + } } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index d52f52e59d39..e79cba179c43 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -67,10 +67,10 @@ import android.content.Intent; import android.content.PermissionChecker; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackagePartitions; import android.content.pm.UserInfo; import android.os.BatteryStats; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -719,7 +719,7 @@ class UserController implements Handler.Callback { // purposefully block sending BOOT_COMPLETED until after all // PRE_BOOT receivers are finished to avoid ANR'ing apps final UserInfo info = getUserInfo(userId); - if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT) + if (!Objects.equals(info.lastLoggedInFingerprint, PackagePartitions.FINGERPRINT) || SystemProperties.getBoolean("persist.pm.mock-upgrade", false)) { // Suppress double notifications for managed profiles that // were unlocked automatically as part of their parent user @@ -1809,7 +1809,8 @@ class UserController implements Handler.Callback { private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { // The dialog will show and then initiate the user switch by calling startUserInForeground mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second, - getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage()); + getSwitchingFromSystemUserMessageUnchecked(), + getSwitchingToSystemUserMessageUnchecked()); } private void dispatchForegroundProfileChanged(@UserIdInt int userId) { @@ -2622,18 +2623,40 @@ class UserController implements Handler.Callback { } } - private String getSwitchingFromSystemUserMessage() { + // Called by AMS, must check permission + String getSwitchingFromSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()"); + + return getSwitchingFromSystemUserMessageUnchecked(); + } + + // Called by AMS, must check permission + String getSwitchingToSystemUserMessage() { + checkHasManageUsersPermission("getSwitchingToSystemUserMessage()"); + + return getSwitchingToSystemUserMessageUnchecked(); + } + + private String getSwitchingFromSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingFromSystemUserMessage; } } - private String getSwitchingToSystemUserMessage() { + private String getSwitchingToSystemUserMessageUnchecked() { synchronized (mLock) { return mSwitchingToSystemUserMessage; } } + private void checkHasManageUsersPermission(String operation) { + if (mInjector.checkCallingPermission( + android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) { + throw new SecurityException( + "You need MANAGE_USERS permission to call " + operation); + } + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (mLock) { long token = proto.start(fieldId); @@ -2706,6 +2729,12 @@ class UserController implements Handler.Callback { pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); + if (mSwitchingFromSystemUserMessage != null) { + pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage); + } + if (mSwitchingToSystemUserMessage != null) { + pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); + } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 61b8ded60db7..7341e744dea3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -251,7 +251,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> "Successful background authentication!"); } - mAlreadyDone = true; + markAlreadyDone(); if (mTaskStackListener != null) { mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); @@ -327,7 +327,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> final @LockoutTracker.LockoutMode int lockoutMode = handleFailedAttempt(getTargetUserId()); if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { - mAlreadyDone = true; + markAlreadyDone(); } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 9764a167fbbf..b73e91173a43 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -114,7 +114,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor // Currently only used for authentication client. The cookie generated by BiometricService // is never 0. private final int mCookie; - boolean mAlreadyDone; + private boolean mAlreadyDone = false; // Use an empty callback by default since delayed operations can receive events // before they are started and cause NPE in subclasses that access this field directly. @@ -202,11 +202,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor return callback; } - public boolean isAlreadyDone() { - return mAlreadyDone; - } - - public void destroy() { + /** Signals this operation has completed its lifecycle and should no longer be used. */ + void destroy() { + mAlreadyDone = true; if (mToken != null) { try { mToken.unlinkToDeath(this, 0); @@ -218,6 +216,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } + /** + * Call while the operation is still active, but nearly done, to prevent any action + * upon client death (only needed for authentication clients). + */ + void markAlreadyDone() { + Slog.d(TAG, "marking operation as done: " + this); + mAlreadyDone = true; + } + + /** If this operation has been marked as completely done (or cancelled). */ + public boolean isAlreadyDone() { + return mAlreadyDone; + } + @Override public void binderDied() { binderDiedInternal(true /* clearListener */); @@ -225,10 +237,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor // TODO(b/157790417): Move this to the scheduler void binderDiedInternal(boolean clearListener) { - Slog.e(TAG, "Binder died, owner: " + getOwnerString() - + ", operation: " + this.getClass().getName()); + Slog.e(TAG, "Binder died, operation: " + this); - if (isAlreadyDone()) { + if (mAlreadyDone) { Slog.w(TAG, "Binder died but client is finished, ignoring"); return; } @@ -299,7 +310,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor @Override public String toString() { return "{[" + mSequentialId + "] " - + this.getClass().getSimpleName() + + this.getClass().getName() + ", proto=" + getProtoEnum() + ", owner=" + getOwnerString() + ", cookie=" + getCookie() diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 361ec40f2877..a358bc2bad55 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -605,6 +605,9 @@ public class BiometricScheduler { if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) { Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation); // We can set it to null immediately, since the HAL was never notified to start. + if (mCurrentOperation != null) { + mCurrentOperation.mClientMonitor.destroy(); + } mCurrentOperation = null; startNextOperationIfIdle(); return; diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 74a21a733fb9..beb4d5b28d5c 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -60,17 +60,62 @@ public abstract class BrightnessMappingStrategy { private static final Plog PLOG = Plog.createSystemPlog(TAG); + /** + * Creates a BrightnessMappingStrategy for active (normal) mode. + * @param resources + * @param displayDeviceConfig + * @return the BrightnessMappingStrategy + */ @Nullable public static BrightnessMappingStrategy create(Resources resources, DisplayDeviceConfig displayDeviceConfig) { + return create(resources, displayDeviceConfig, /* isForIdleMode= */ false); + } + + /** + * Creates a BrightnessMappingStrategy for idle screen brightness mode. + * @param resources + * @param displayDeviceConfig + * @return the BrightnessMappingStrategy + */ + @Nullable + public static BrightnessMappingStrategy createForIdleMode(Resources resources, + DisplayDeviceConfig displayDeviceConfig) { + return create(resources, displayDeviceConfig, /* isForIdleMode= */ true); + } + + /** + * Creates a BrightnessMapping strategy for either active or idle screen brightness mode. + * We do not create a simple mapping strategy for idle mode. + * + * @param resources + * @param displayDeviceConfig + * @param isForIdleMode determines whether the configurations loaded are for idle screen + * brightness mode or active screen brightness mode. + * @return the BrightnessMappingStrategy + */ + @Nullable + private static BrightnessMappingStrategy create(Resources resources, + DisplayDeviceConfig displayDeviceConfig, boolean isForIdleMode) { + + // Display independent, mode dependent values + float[] brightnessLevelsNits; + float[] luxLevels; + if (isForIdleMode) { + brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)); + luxLevels = getLuxLevels(resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevelsIdle)); + } else { + brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); + luxLevels = getLuxLevels(resources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels)); + } - // Display independent values - float[] luxLevels = getLuxLevels(resources.getIntArray( - com.android.internal.R.array.config_autoBrightnessLevels)); + // Display independent, mode independent values int[] brightnessLevelsBacklight = resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); - float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( - com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); float autoBrightnessAdjustmentMaxGamma = resources.getFraction( com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1); @@ -91,7 +136,7 @@ public abstract class BrightnessMappingStrategy { builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange, autoBrightnessAdjustmentMaxGamma); - } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) { + } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) { return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight, autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout); } else { diff --git a/services/core/java/com/android/server/display/DensityMap.java b/services/core/java/com/android/server/display/DensityMap.java new file mode 100644 index 000000000000..4aafd148a6dd --- /dev/null +++ b/services/core/java/com/android/server/display/DensityMap.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Class which can compute the logical density for a display resolution. It holds a collection + * of pre-configured densities, which are used for look-up and interpolation. + */ +public class DensityMap { + + // Instead of resolutions we store the squared diagonal size. Diagonals make the map + // keys invariant to rotations and are useful for interpolation because they're scalars. + // Squared diagonals have the same properties as diagonals (the square function is monotonic) + // but also allow us to use integer types and avoid floating point arithmetics. + private final Entry[] mSortedDensityMapEntries; + + /** + * Creates a density map. The newly created object takes ownership of the passed array. + */ + static DensityMap createByOwning(Entry[] densityMapEntries) { + return new DensityMap(densityMapEntries); + } + + private DensityMap(Entry[] densityMapEntries) { + Arrays.sort(densityMapEntries, Comparator.comparingInt(entry -> entry.squaredDiagonal)); + mSortedDensityMapEntries = densityMapEntries; + verifyDensityMap(mSortedDensityMapEntries); + } + + /** + * Returns the logical density for the given resolution. + * + * If the resolution matches one of the entries in the map, the corresponding density is + * returned. Otherwise the return value is interpolated using the closest entries in the map. + */ + public int getDensityForResolution(int width, int height) { + int squaredDiagonal = width * width + height * height; + + // Search for two pre-configured entries "left" and "right" with the following criteria + // * left <= squaredDiagonal + // * squaredDiagonal - left is minimal + // * right > squaredDiagonal + // * right - squaredDiagonal is minimal + Entry left = Entry.ZEROES; + Entry right = null; + + for (Entry entry : mSortedDensityMapEntries) { + if (entry.squaredDiagonal <= squaredDiagonal) { + left = entry; + } else { + right = entry; + break; + } + } + + // Check if we found an exact match. + if (left.squaredDiagonal == squaredDiagonal) { + return left.density; + } + + // If no configured resolution is higher than the specified resolution, interpolate + // between (0,0) and (maxConfiguredDiagonal, maxConfiguredDensity). + if (right == null) { + right = left; // largest entry in the sorted array + left = Entry.ZEROES; + } + + double leftDiagonal = Math.sqrt(left.squaredDiagonal); + double rightDiagonal = Math.sqrt(right.squaredDiagonal); + double diagonal = Math.sqrt(squaredDiagonal); + + return (int) Math.round((diagonal - leftDiagonal) * (right.density - left.density) + / (rightDiagonal - leftDiagonal) + left.density); + } + + private static void verifyDensityMap(Entry[] sortedEntries) { + for (int i = 1; i < sortedEntries.length; i++) { + Entry prev = sortedEntries[i - 1]; + Entry curr = sortedEntries[i]; + + if (prev.squaredDiagonal == curr.squaredDiagonal) { + // This will most often happen because there are two entries with the same + // resolution (AxB and AxB) or rotated resolution (AxB and BxA), but it can also + // happen in the very rare cases when two different resolutions happen to have + // the same diagonal (e.g. 100x700 and 500x500). + throw new IllegalStateException("Found two entries in the density map with" + + " the same diagonal: " + prev + ", " + curr); + } else if (prev.density > curr.density) { + throw new IllegalStateException("Found two entries in the density map with" + + " increasing diagonal but decreasing density: " + prev + ", " + curr); + } + } + } + + @Override + public String toString() { + return "DensityMap{" + + "mDensityMapEntries=" + Arrays.toString(mSortedDensityMapEntries) + + '}'; + } + + static class Entry { + public static final Entry ZEROES = new Entry(0, 0, 0); + + public final int squaredDiagonal; + public final int density; + + Entry(int width, int height, int density) { + this.squaredDiagonal = width * width + height * height; + this.density = density; + } + + @Override + public String toString() { + return "DensityMapEntry{" + + "squaredDiagonal=" + squaredDiagonal + + ", density=" + density + '}'; + } + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 2ae5cbbbf24b..a9e1647446cb 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -18,6 +18,7 @@ package com.android.server.display; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; @@ -31,6 +32,7 @@ import android.view.DisplayAddress; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.config.Density; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; @@ -52,6 +54,7 @@ import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; @@ -70,6 +73,8 @@ public class DisplayDeviceConfig { private static final String ETC_DIR = "etc"; private static final String DISPLAY_CONFIG_DIR = "displayconfig"; private static final String CONFIG_FILE_FORMAT = "display_%s.xml"; + private static final String DEFAULT_CONFIG_FILE = "default.xml"; + private static final String DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT = "default_%s.xml"; private static final String PORT_SUFFIX_FORMAT = "port_%d"; private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d"; private static final String NO_SUFFIX_FORMAT = "%d"; @@ -121,6 +126,7 @@ public class DisplayDeviceConfig { private List<String> mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; + private DensityMap mDensityMap; private String mLoadedFrom = null; private DisplayDeviceConfig(Context context) { @@ -141,6 +147,33 @@ public class DisplayDeviceConfig { */ public static DisplayDeviceConfig create(Context context, long physicalDisplayId, boolean isDefaultDisplay) { + final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId, + isDefaultDisplay); + + config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context)); + return config; + } + + /** + * Creates an instance using global values since no display device config xml exists. + * Uses values from config or PowerManager. + * + * @param context + * @param useConfigXml + * @return A configuration instance. + */ + public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { + final DisplayDeviceConfig config; + if (useConfigXml) { + config = getConfigFromGlobalXml(context); + } else { + config = getConfigFromPmValues(context); + } + return config; + } + + private static DisplayDeviceConfig createWithoutDefaultValues(Context context, + long physicalDisplayId, boolean isDefaultDisplay) { DisplayDeviceConfig config; config = loadConfigFromDirectory(context, Environment.getProductDirectory(), @@ -161,22 +194,53 @@ public class DisplayDeviceConfig { return create(context, isDefaultDisplay); } - /** - * Creates an instance using global values since no display device config xml exists. - * Uses values from config or PowerManager. - * - * @param context - * @param useConfigXml - * @return A configuration instance. - */ - public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { - DisplayDeviceConfig config; - if (useConfigXml) { - config = getConfigFromGlobalXml(context); - } else { - config = getConfigFromPmValues(context); + private static DisplayConfiguration loadDefaultConfigurationXml(Context context) { + List<File> defaultXmlLocations = new ArrayList<>(); + defaultXmlLocations.add(Environment.buildPath(Environment.getProductDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + defaultXmlLocations.add(Environment.buildPath(Environment.getVendorDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + + // Read config_defaultUiModeType directly because UiModeManager hasn't started yet. + final int uiModeType = context.getResources() + .getInteger(com.android.internal.R.integer.config_defaultUiModeType); + final String uiModeTypeStr = Configuration.getUiModeTypeString(uiModeType); + if (uiModeTypeStr != null) { + defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, + String.format(DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT, uiModeTypeStr))); } - return config; + defaultXmlLocations.add(Environment.buildPath(Environment.getRootDirectory(), + ETC_DIR, DISPLAY_CONFIG_DIR, DEFAULT_CONFIG_FILE)); + + final File configFile = getFirstExistingFile(defaultXmlLocations); + if (configFile == null) { + // Display configuration files aren't required to exist. + return null; + } + + DisplayConfiguration defaultConfig = null; + + try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { + defaultConfig = XmlParser.read(in); + if (defaultConfig == null) { + Slog.i(TAG, "Default DisplayDeviceConfig file is null"); + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.e(TAG, "Encountered an error while reading/parsing display config file: " + + configFile, e); + } + + return defaultConfig; + } + + private static File getFirstExistingFile(Collection<File> files) { + for (File file : files) { + if (file.exists() && file.isFile()) { + return file; + } + } + return null; } private static DisplayDeviceConfig loadConfigFromDirectory(Context context, @@ -316,9 +380,13 @@ public class DisplayDeviceConfig { return mRefreshRateLimitations; } + public DensityMap getDensityMap() { + return mDensityMap; + } + @Override public String toString() { - String str = "DisplayDeviceConfig{" + return "DisplayDeviceConfig{" + "mLoadedFrom=" + mLoadedFrom + ", mBacklight=" + Arrays.toString(mBacklight) + ", mNits=" + Arrays.toString(mNits) @@ -340,8 +408,8 @@ public class DisplayDeviceConfig { + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) + + ", mDensityMap= " + mDensityMap + "}"; - return str; } private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, @@ -384,6 +452,7 @@ public class DisplayDeviceConfig { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { final DisplayConfiguration config = XmlParser.read(in); if (config != null) { + loadDensityMap(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); loadBrightnessMap(config); @@ -429,6 +498,35 @@ public class DisplayDeviceConfig { setProxSensorUnspecified(); } + private void copyUninitializedValuesFromSecondaryConfig(DisplayConfiguration defaultConfig) { + if (defaultConfig == null) { + return; + } + + if (mDensityMap == null) { + loadDensityMap(defaultConfig); + } + } + + private void loadDensityMap(DisplayConfiguration config) { + if (config.getDensityMap() == null) { + return; + } + + final List<Density> entriesFromXml = config.getDensityMap().getDensity(); + + final DensityMap.Entry[] entries = + new DensityMap.Entry[entriesFromXml.size()]; + for (int i = 0; i < entriesFromXml.size(); i++) { + final Density density = entriesFromXml.get(i); + entries[i] = new DensityMap.Entry( + density.getWidth().intValue(), + density.getHeight().intValue(), + density.getDensity().intValue()); + } + mDensityMap = DensityMap.createByOwning(entries); + } + private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) { // Default brightness values are stored in the displayDeviceConfig file, // Or we fallback standard values if not. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e4bed3d06714..a36a1a9183fb 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -110,6 +110,7 @@ import android.view.DisplayEventReceiver; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; +import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -239,6 +240,10 @@ public final class DisplayManagerService extends SystemService { public final SparseArray<CallbackRecord> mCallbacks = new SparseArray<CallbackRecord>(); + /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */ + final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController = + new SparseArray<>(); + // List of all currently registered display adapters. private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>(); @@ -1115,46 +1120,211 @@ public final class DisplayManagerService extends SystemService { } } - private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, - IMediaProjection projection, int callingUid, String packageName, Surface surface, - int flags, VirtualDisplayConfig virtualDisplayConfig) { - synchronized (mSyncRoot) { - if (mVirtualDisplayAdapter == null) { - Slog.w(TAG, "Rejecting request to create private virtual display " - + "because the virtual display adapter is not available."); - return -1; + private boolean validatePackageName(int uid, String packageName) { + if (packageName != null) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + if (packageNames != null) { + for (String n : packageNames) { + if (n.equals(packageName)) { + return true; + } + } } + } + return false; + } - DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( - callback, projection, callingUid, packageName, surface, flags, - virtualDisplayConfig); - if (device == null) { - return -1; + private boolean canProjectVideo(IMediaProjection projection) { + if (projection != null) { + try { + if (projection.canProjectVideo()) { + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to query projection service for permissions", e); } + } + if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) { + return true; + } + return canProjectSecureVideo(projection); + } - // DisplayDevice events are handled manually for Virtual Displays. - // TODO: multi-display Fix this so that generic add/remove events are not handled in a - // different code path for virtual displays. Currently this happens so that we can - // return a valid display ID synchronously upon successful Virtual Display creation. - // This code can run on any binder thread, while onDisplayDeviceAdded() callbacks are - // called on the DisplayThread (which we don't want to wait for?). - // One option would be to actually wait here on the binder thread - // to be notified when the virtual display is created (or failed). - mDisplayDeviceRepo.onDisplayDeviceEvent(device, - DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + private boolean canProjectSecureVideo(IMediaProjection projection) { + if (projection != null) { + try { + if (projection.canProjectSecureVideo()) { + return true; + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to query projection service for permissions", e); + } + } + return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()"); + } - final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); - if (display != null) { - return display.getDisplayIdLocked(); + private boolean checkCallingPermission(String permission, String func) { + if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { + return true; + } + final String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + " requires " + permission; + Slog.w(TAG, msg); + return false; + } + + private int createVirtualDisplayInternal(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, + DisplayWindowPolicyController controller) { + final int callingUid = Binder.getCallingUid(); + if (!validatePackageName(callingUid, packageName)) { + throw new SecurityException("packageName must match the calling uid"); + } + if (callback == null) { + throw new IllegalArgumentException("appToken must not be null"); + } + if (virtualDisplayConfig == null) { + throw new IllegalArgumentException("virtualDisplayConfig must not be null"); + } + final Surface surface = virtualDisplayConfig.getSurface(); + int flags = virtualDisplayConfig.getFlags(); + + if (surface != null && surface.isSingleBuffered()) { + throw new IllegalArgumentException("Surface can't be single-buffered"); + } + + if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { + flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + + // Public displays can't be allowed to show content when locked. + if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { + throw new IllegalArgumentException( + "Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE"); + } + } + if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) { + flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; + } + if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; + } + + if (projection != null) { + try { + if (!getProjectionService().isValidMediaProjection(projection)) { + throw new SecurityException("Invalid media projection"); + } + flags = projection.applyVirtualDisplayFlags(flags); + } catch (RemoteException e) { + throw new SecurityException("unable to validate media projection or flags"); } + } - // Something weird happened and the logical display was not created. - Slog.w(TAG, "Rejecting request to create virtual display " - + "because the logical display was not created."); - mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder()); - mDisplayDeviceRepo.onDisplayDeviceEvent(device, - DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); + if (callingUid != Process.SYSTEM_UID + && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { + if (!canProjectVideo(projection)) { + throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or " + + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate " + + "MediaProjection token in order to create a screen sharing virtual " + + "display."); + } + } + if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) { + if (!canProjectSecureVideo(projection)) { + throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT " + + "or an appropriate MediaProjection token to create a " + + "secure virtual display."); + } } + + if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { + if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { + EventLog.writeEvent(0x534e4554, "162627132", callingUid, + "Attempt to create a trusted display without holding permission!"); + throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " + + "create a trusted virtual display."); + } + } + + if (callingUid != Process.SYSTEM_UID + && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { + if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { + throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " + + "create a virtual display which is not in the default DisplayGroup."); + } + } + + if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) { + flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; + } + + // Sometimes users can have sensitive information in system decoration windows. An app + // could create a virtual display with system decorations support and read the user info + // from the surface. + // We should only allow adding flag VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + // to trusted virtual displays. + final int trustedDisplayWithSysDecorFlag = + (VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + | VIRTUAL_DISPLAY_FLAG_TRUSTED); + if ((flags & trustedDisplayWithSysDecorFlag) + == VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + && !checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) { + throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + return createVirtualDisplayLocked(callback, projection, callingUid, packageName, + surface, flags, virtualDisplayConfig, controller); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private int createVirtualDisplayLocked(IVirtualDisplayCallback callback, + IMediaProjection projection, int callingUid, String packageName, Surface surface, + int flags, VirtualDisplayConfig virtualDisplayConfig, + DisplayWindowPolicyController controller) { + if (mVirtualDisplayAdapter == null) { + Slog.w(TAG, "Rejecting request to create private virtual display " + + "because the virtual display adapter is not available."); + return -1; + } + + DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( + callback, projection, callingUid, packageName, surface, flags, + virtualDisplayConfig); + if (device == null) { + return -1; + } + + // DisplayDevice events are handled manually for Virtual Displays. + // TODO: multi-display Fix this so that generic add/remove events are not handled in a + // different code path for virtual displays. Currently this happens so that we can + // return a valid display ID synchronously upon successful Virtual Display creation. + // This code can run on any binder thread, while onDisplayDeviceAdded() callbacks are + // called on the DisplayThread (which we don't want to wait for?). + // One option would be to actually wait here on the binder thread + // to be notified when the virtual display is created (or failed). + mDisplayDeviceRepo.onDisplayDeviceEvent(device, + DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); + if (display != null) { + if (controller != null) { + mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller); + } + return display.getDisplayIdLocked(); + } + + // Something weird happened and the logical display was not created. + Slog.w(TAG, "Rejecting request to create virtual display " + + "because the logical display was not created."); + mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder()); + mDisplayDeviceRepo.onDisplayDeviceEvent(device, + DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); return -1; } @@ -1188,6 +1358,10 @@ public final class DisplayManagerService extends SystemService { DisplayDevice device = mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); if (device != null) { + final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); + if (display != null) { + mDisplayWindowPolicyController.delete(display.getDisplayIdLocked()); + } // TODO: multi-display - handle virtual displays the same as other display adapters. mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); @@ -2139,6 +2313,15 @@ public final class DisplayManagerService extends SystemService { } pw.println(); mPersistentDataStore.dump(pw); + + final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size(); + pw.println(); + pw.println("Display Window Policy Controllers: size=" + + displayWindowPolicyControllerCount); + for (int i = 0; i < displayWindowPolicyControllerCount; i++) { + pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":"); + mDisplayWindowPolicyController.valueAt(i).dump(" ", pw); + } } pw.println(); mDisplayModeDirector.dump(pw); @@ -2704,109 +2887,8 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) { - final int callingUid = Binder.getCallingUid(); - if (!validatePackageName(callingUid, packageName)) { - throw new SecurityException("packageName must match the calling uid"); - } - if (callback == null) { - throw new IllegalArgumentException("appToken must not be null"); - } - if (virtualDisplayConfig == null) { - throw new IllegalArgumentException("virtualDisplayConfig must not be null"); - } - final Surface surface = virtualDisplayConfig.getSurface(); - int flags = virtualDisplayConfig.getFlags(); - - if (surface != null && surface.isSingleBuffered()) { - throw new IllegalArgumentException("Surface can't be single-buffered"); - } - - if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { - flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; - - // Public displays can't be allowed to show content when locked. - if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { - throw new IllegalArgumentException( - "Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE"); - } - } - if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) { - flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; - } - if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { - flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; - } - - if (projection != null) { - try { - if (!getProjectionService().isValidMediaProjection(projection)) { - throw new SecurityException("Invalid media projection"); - } - flags = projection.applyVirtualDisplayFlags(flags); - } catch (RemoteException e) { - throw new SecurityException("unable to validate media projection or flags"); - } - } - - if (callingUid != Process.SYSTEM_UID && - (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { - if (!canProjectVideo(projection)) { - throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or " - + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate " - + "MediaProjection token in order to create a screen sharing virtual " - + "display."); - } - } - if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) { - if (!canProjectSecureVideo(projection)) { - throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT " - + "or an appropriate MediaProjection token to create a " - + "secure virtual display."); - } - } - - if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { - if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { - EventLog.writeEvent(0x534e4554, "162627132", callingUid, - "Attempt to create a trusted display without holding permission!"); - throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " - + "create a trusted virtual display."); - } - } - - if (callingUid != Process.SYSTEM_UID - && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { - if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) { - throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to " - + "create a virtual display which is not in the default DisplayGroup."); - } - } - - if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) { - flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; - } - - // Sometimes users can have sensitive information in system decoration windows. An app - // could create a virtual display with system decorations support and read the user info - // from the surface. - // We should only allow adding flag VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - // to trusted virtual displays. - final int trustedDisplayWithSysDecorFlag = - (VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - | VIRTUAL_DISPLAY_FLAG_TRUSTED); - if ((flags & trustedDisplayWithSysDecorFlag) - == VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS - && !checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) { - throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); - } - - final long token = Binder.clearCallingIdentity(); - try { - return createVirtualDisplayInternal(callback, projection, callingUid, packageName, - surface, flags, virtualDisplayConfig); - } finally { - Binder.restoreCallingIdentity(token); - } + return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection, + packageName, null /* controller */); } @Override // Binder call @@ -3236,60 +3318,6 @@ public final class DisplayManagerService extends SystemService { Binder.restoreCallingIdentity(token); } } - - private boolean validatePackageName(int uid, String packageName) { - if (packageName != null) { - String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); - if (packageNames != null) { - for (String n : packageNames) { - if (n.equals(packageName)) { - return true; - } - } - } - } - return false; - } - - private boolean canProjectVideo(IMediaProjection projection) { - if (projection != null) { - try { - if (projection.canProjectVideo()) { - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to query projection service for permissions", e); - } - } - if (checkCallingPermission(CAPTURE_VIDEO_OUTPUT, "canProjectVideo()")) { - return true; - } - return canProjectSecureVideo(projection); - } - - private boolean canProjectSecureVideo(IMediaProjection projection) { - if (projection != null) { - try { - if (projection.canProjectSecureVideo()){ - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to query projection service for permissions", e); - } - } - return checkCallingPermission(CAPTURE_SECURE_VIDEO_OUTPUT, "canProjectSecureVideo()"); - } - - private boolean checkCallingPermission(String permission, String func) { - if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { - return true; - } - final String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() + " requires " + permission; - Slog.w(TAG, msg); - return false; - } - } private static boolean isValidBrightness(float brightness) { @@ -3624,6 +3652,21 @@ public final class DisplayManagerService extends SystemService { public void onEarlyInteractivityChange(boolean interactive) { mLogicalDisplayMapper.onEarlyInteractivityChange(interactive); } + + @Override + public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, + IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, + DisplayWindowPolicyController controller) { + return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection, + packageName, controller); + } + + @Override + public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) { + synchronized (mSyncRoot) { + return mDisplayWindowPolicyController.get(displayId); + } + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 5f79f727ca97..7f78cac651dc 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -54,6 +54,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.view.Display; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.display.BrightnessSynchronizer; @@ -386,8 +387,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private Sensor mLightSensor; // The mapper between ambient lux, display backlight values, and display brightness. + // This mapper holds the current one that is being used. We will switch between the idle + // mapper and active mapper here. @Nullable - private BrightnessMappingStrategy mBrightnessMapper; + private BrightnessMappingStrategy mCurrentBrightnessMapper; + + // Mapper used for active (normal) screen brightness mode + @Nullable + private BrightnessMappingStrategy mInteractiveModeBrightnessMapper; + // Mapper used for idle screen brightness mode + @Nullable + private BrightnessMappingStrategy mIdleModeBrightnessMapper; // The current brightness configuration. @Nullable @@ -408,7 +418,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The temporary screen brightness. Typically set when a user is interacting with the // brightness slider but hasn't settled on a choice yet. Set to - // PowerManager.BRIGHNTESS_INVALID_FLOAT when there's no temporary brightness set. + // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set. private float mTemporaryScreenBrightness; // The current screen brightness while in VR mode. @@ -600,7 +610,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void handleRbcChanged(boolean strengthChanged, boolean justActivated) { - if (mBrightnessMapper == null) { + if (mCurrentBrightnessMapper == null) { Log.w(TAG, "No brightness mapping available to recalculate splines"); return; } @@ -609,7 +619,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call for (int i = 0; i < mNitsRange.length; i++) { adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]); } - mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits); + mCurrentBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), + adjustedNits); mPendingRbcOnOrChanged = strengthChanged || justActivated; @@ -867,9 +878,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return; } - mBrightnessMapper = BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig); + final boolean isIdleScreenBrightnessEnabled = resources.getBoolean( + R.bool.config_enableIdleScreenBrightnessMode); + mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources, + mDisplayDeviceConfig); + if (isIdleScreenBrightnessEnabled) { + mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources, + mDisplayDeviceConfig); + } + mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper; - if (mBrightnessMapper != null) { + if (mCurrentBrightnessMapper != null) { final float dozeScaleFactor = resources.getFraction( com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, 1, 1); @@ -920,7 +939,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.stop(); } mAutomaticBrightnessController = new AutomaticBrightnessController(this, - handler.getLooper(), mSensorManager, mLightSensor, mBrightnessMapper, + handler.getLooper(), mSensorManager, mLightSensor, mCurrentBrightnessMapper, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, @@ -2143,8 +2162,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private float convertToNits(float brightness) { - if (mBrightnessMapper != null) { - return mBrightnessMapper.convertToNits(brightness); + if (mCurrentBrightnessMapper != null) { + return mCurrentBrightnessMapper.convertToNits(brightness); } else { return -1.0f; } @@ -2296,6 +2315,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mReportedToPolicy=" + reportedToPolicyToString(mReportedScreenStateToPolicy)); + if (mIdleModeBrightnessMapper != null) { + pw.println(" mIdleModeBrightnessMapper= "); + mIdleModeBrightnessMapper.dump(pw); + } + if (mScreenBrightnessRampAnimator != null) { pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" + mScreenBrightnessRampAnimator.isAnimating()); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index b6d13e0c5bbf..300f59ee1dd4 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -426,6 +426,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { : mDefaultModeId; } + private int getLogicalDensity() { + DensityMap densityMap = getDisplayDeviceConfig().getDensityMap(); + if (densityMap == null) { + return (int) (mStaticDisplayInfo.density * 160 + 0.5); + } + + return densityMap.getDensityForResolution(mInfo.width, mInfo.height); + } + private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); @@ -591,7 +600,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { final DisplayAddress.Physical physicalAddress = DisplayAddress.fromPhysicalDisplayId(mPhysicalDisplayId); mInfo.address = physicalAddress; - mInfo.densityDpi = (int) (mStaticDisplayInfo.density * 160 + 0.5f); + mInfo.densityDpi = getLogicalDensity(); mInfo.xDpi = mActiveSfDisplayMode.xDpi; mInfo.yDpi = mActiveSfDisplayMode.yDpi; mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo; @@ -1029,7 +1038,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { for (int i = 0; i < mSupportedModes.size(); i++) { pw.println(" " + mSupportedModes.valueAt(i)); } - pw.println("mSupportedColorModes=" + mSupportedColorModes.toString()); + pw.println("mSupportedColorModes=" + mSupportedColorModes); pw.println("mDisplayDeviceConfig=" + mDisplayDeviceConfig); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 1fa6241e8b94..7e71589302f1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -265,13 +265,20 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // to request Short Audio Descriptor. Since ARC and SAM are independent, // we can turn on ARC anyways when audio system device just boots up. initArcOnFromAvr(); - int systemAudioControlOnPowerOnProp = - SystemProperties.getInt( - PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, - ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON); - boolean lastSystemAudioControlStatus = - SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); - systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); + + // This prevents turning on of System Audio Mode during a quiescent boot. If the quiescent + // boot is exited just after this check, this code will be executed only at the next + // wake-up. + if (!mService.isScreenOff()) { + int systemAudioControlOnPowerOnProp = + SystemProperties.getInt( + PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, + ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON); + boolean lastSystemAudioControlStatus = + SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); + systemAudioControlOnPowerOn( + systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); + } mService.getHdmiCecNetwork().clearDeviceList(); launchDeviceDiscovery(); startQueuedActions(); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index d6ac25a72a6e..b23395f36e63 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -21,6 +21,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.SystemProperties; @@ -47,6 +48,10 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { private static final boolean SET_MENU_LANGUAGE = HdmiProperties.set_menu_language_enabled().orElse(false); + // How long to wait after hotplug out before possibly going to Standby. + @VisibleForTesting + static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000; + // Used to keep the device awake while it is the active source. For devices that // cannot wake up via CEC commands, this address the inconvenience of having to // turn them on. True by default, and can be disabled (i.e. device can go to sleep @@ -55,6 +60,9 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // Lazily initialized - should call getWakeLock() to get the instance. private ActiveWakeLock mWakeLock; + // Handler for queueing a delayed Standby runnable after hotplug out. + private Handler mDelayedStandbyHandler; + // Determines what action should be taken upon receiving Routing Control messages. @VisibleForTesting protected HdmiProperties.playback_device_action_on_routing_control_values @@ -64,6 +72,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { HdmiCecLocalDevicePlayback(HdmiControlService service) { super(service, HdmiDeviceInfo.DEVICE_PLAYBACK); + + mDelayedStandbyHandler = new Handler(service.getServiceLooper()); } @Override @@ -195,9 +205,33 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); mCecMessageCache.flushAll(); - // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3. - if (!connected) { + + if (connected) { + mDelayedStandbyHandler.removeCallbacksAndMessages(null); + } else { + // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3 getWakeLock().release(); + + mDelayedStandbyHandler.removeCallbacksAndMessages(null); + mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(), + STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + } + } + + /** + * Runnable for going to Standby if the device has been inactive for a certain amount of time. + * Posts a new instance of itself as a delayed message if the device was active. + */ + private class DelayedStandbyRunnable implements Runnable { + @Override + public void run() { + if (mService.getPowerManagerInternal().wasDeviceIdleFor( + STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) { + mService.standby(); + } else { + mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(), + STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 69f7af23fe4c..6dd9aa029ded 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -37,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; +import android.hardware.display.DisplayManager; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiHotplugEvent; @@ -81,6 +82,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; +import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -408,8 +410,14 @@ public class HdmiControlService extends SystemService { private PowerManagerWrapper mPowerManager; @Nullable + private PowerManagerInternalWrapper mPowerManagerInternal; + + @Nullable private Looper mIoLooper; + @Nullable + private DisplayManager mDisplayManager; + @HdmiControlManager.HdmiCecVersion private int mCecVersion; @@ -676,6 +684,11 @@ public class HdmiControlService extends SystemService { }, mServiceThreadExecutor); } + /** Returns true if the device screen is off */ + boolean isScreenOff() { + return mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_OFF; + } + private void bootCompleted() { // on boot, if device is interactive, set HDMI CEC state as powered on as well if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) { @@ -731,9 +744,11 @@ public class HdmiControlService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mDisplayManager = getContext().getSystemService(DisplayManager.class); mTvInputManager = (TvInputManager) getContext().getSystemService( Context.TV_INPUT_SERVICE); mPowerManager = new PowerManagerWrapper(getContext()); + mPowerManagerInternal = new PowerManagerInternalWrapper(); } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { runOnServiceThread(this::bootCompleted); } @@ -758,10 +773,19 @@ public class HdmiControlService extends SystemService { mPowerManager = powerManager; } + @VisibleForTesting + void setPowerManagerInternal(PowerManagerInternalWrapper powerManagerInternal) { + mPowerManagerInternal = powerManagerInternal; + } + PowerManagerWrapper getPowerManager() { return mPowerManager; } + PowerManagerInternalWrapper getPowerManagerInternal() { + return mPowerManagerInternal; + } + /** * Called when the initialization of local devices is complete. */ @@ -3239,18 +3263,19 @@ public class HdmiControlService extends SystemService { assertRunOnServiceThread(); Slog.v(TAG, "onPendingActionsCleared"); - if (!mPowerStatusController.isPowerStatusTransientToStandby()) { - return; - } - mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY); - for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { - device.onStandby(mStandbyMessageReceived, standbyAction); + if (mPowerStatusController.isPowerStatusTransientToStandby()) { + mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY); + for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { + device.onStandby(mStandbyMessageReceived, standbyAction); + } + if (!isAudioSystemDevice()) { + mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); + } } + + // Always reset this flag to set up for the next standby mStandbyMessageReceived = false; - if (!isAudioSystemDevice()) { - mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false); - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); - } } private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { diff --git a/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java b/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java new file mode 100644 index 000000000000..59671a87473e --- /dev/null +++ b/services/core/java/com/android/server/hdmi/PowerManagerInternalWrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +import android.os.PowerManagerInternal; + +import com.android.server.LocalServices; + +/** + * Abstraction around {@link PowerManagerInternal} to allow faking it in tests. + */ +public class PowerManagerInternalWrapper { + private static final String TAG = "PowerManagerInternalWrapper"; + + private PowerManagerInternal mPowerManagerInternal; + + public PowerManagerInternalWrapper() { + mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + } + + /** + * Wraps {@link PowerManagerInternal#wasDeviceIdleFor(long)} + */ + public boolean wasDeviceIdleFor(long ms) { + return mPowerManagerInternal.wasDeviceIdleFor(ms); + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index b8bb399a4442..f70ad0537413 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -224,7 +224,7 @@ public class InputMethodMenuController { public Context getSettingsContext(int displayId) { if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) { final Context systemUiContext = ActivityThread.currentActivityThread() - .createSystemUiContext(displayId); + .getSystemUiContext(displayId); final Context windowContext = systemUiContext.createWindowContext( WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG, null /* options */); mSettingsContext = new ContextThemeWrapper( diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index b676f282904f..efd30377851b 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -30,6 +30,7 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.Intent; +import android.hardware.contexthub.HostEndpointInfo; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubTransaction; @@ -42,6 +43,7 @@ import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -329,6 +331,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mAppOpsManager = context.getSystemService(AppOpsManager.class); startMonitoringOpChanges(); + + HostEndpointInfo info = new HostEndpointInfo(); + info.hostEndpointId = (char) mHostEndPointId; + info.packageName = mPackage; + info.attributionTag = mAttributionTag; + info.type = (mUid == Process.SYSTEM_UID) + ? HostEndpointInfo.Type.TYPE_FRAMEWORK + : HostEndpointInfo.Type.TYPE_APP; + mContextHubProxy.onHostEndpointConnected(info); } /* package */ ContextHubClientBroker( @@ -862,6 +873,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub mRegistered = false; } mAppOpsManager.stopWatchingMode(this); + + mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId); } private String authStateToString(@ContextHubManager.AuthorizationState int state) { diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 74630d1708a4..9078f3ffeb54 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -17,6 +17,7 @@ package com.android.server.location.contexthub; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.contexthub.HostEndpointInfo; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; import android.hardware.contexthub.V1_0.TransactionResult; @@ -239,6 +240,20 @@ public abstract class IContextHubWrapper { public abstract void onMicrophoneSettingChanged(boolean enabled); /** + * Invoked whenever a host client connects with the framework. + * + * @param info The host endpoint info. + */ + public void onHostEndpointConnected(HostEndpointInfo info) {} + + /** + * Invoked whenever a host client disconnects from the framework. + * + * @param hostEndpointId The ID of the host endpoint that disconnected. + */ + public void onHostEndpointDisconnected(short hostEndpointId) {} + + /** * Sends a message to the Context Hub. * * @param hostEndpointId The host endpoint ID of the sender. @@ -407,6 +422,24 @@ public abstract class IContextHubWrapper { onSettingChanged(android.hardware.contexthub.Setting.WIFI_SCANNING, enabled); } + @Override + public void onHostEndpointConnected(HostEndpointInfo info) { + try { + mHub.onHostEndpointConnected(info); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in onHostEndpointConnected"); + } + } + + @Override + public void onHostEndpointDisconnected(short hostEndpointId) { + try { + mHub.onHostEndpointDisconnected((char) hostEndpointId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in onHostEndpointDisconnected"); + } + } + @ContextHubTransaction.Result public int sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message) diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java index 7db234a29942..5fe77109eea3 100644 --- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java @@ -71,7 +71,8 @@ public class GnssConfiguration { private static final String CONFIG_GPS_LOCK = "GPS_LOCK"; private static final String CONFIG_ES_EXTENSION_SEC = "ES_EXTENSION_SEC"; public static final String CONFIG_NFW_PROXY_APPS = "NFW_PROXY_APPS"; - + public static final String CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD = + "ENABLE_PSDS_PERIODIC_DOWNLOAD"; // Limit on NI emergency mode time extension after emergency sessions ends private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300; // 5 minute maximum @@ -194,6 +195,13 @@ public class GnssConfiguration { } /** + * Returns true if PSDS periodic download is enabled, false otherwise. + */ + boolean isPsdsPeriodicDownloadEnabled() { + return getBooleanConfig(CONFIG_ENABLE_PSDS_PERIODIC_DOWNLOAD, false); + } + + /** * Updates the GNSS HAL satellite denylist. */ void setSatelliteBlocklist(int[] constellations, int[] svids) { @@ -374,6 +382,14 @@ public class GnssConfiguration { } } + private boolean getBooleanConfig(String configParameter, boolean defaultValue) { + String valueString = mProperties.getProperty(configParameter); + if (TextUtils.isEmpty(valueString)) { + return defaultValue; + } + return Boolean.parseBoolean(valueString); + } + private static boolean isConfigEsExtensionSecSupported( HalInterfaceVersion gnssConfiguartionIfaceVersion) { // ES_EXTENSION_SEC is introduced in @2.0::IGnssConfiguration.hal diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 6c1df7f98da0..f1141845f2bd 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -16,6 +16,8 @@ package com.android.server.location.gnss; +import static android.content.pm.PackageManager.FEATURE_WATCH; + import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.location.provider.ProviderProperties.ACCURACY_FINE; import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH; @@ -55,6 +57,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.location.GnssCapabilities; import android.location.GnssStatus; @@ -142,6 +145,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final int AGPS_SUPL_MODE_MSA = 0x02; private static final int AGPS_SUPL_MODE_MSB = 0x01; + // PSDS stands for Predicted Satellite Data Service + private static final int DOWNLOAD_PSDS_DATA = 6; + // TCP/IP constants. // Valid TCP/UDP port range is (0, 65535]. private static final int TCP_MIN_PORT = 0; @@ -651,6 +657,14 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mPsdsBackOff.reset(); } }); + PackageManager pm = mContext.getPackageManager(); + if (pm != null && pm.hasSystemFeature(FEATURE_WATCH) + && mGnssConfiguration.isPsdsPeriodicDownloadEnabled()) { + if (DEBUG) Log.d(TAG, "scheduling next Psds download"); + mHandler.removeMessages(DOWNLOAD_PSDS_DATA); + mHandler.sendEmptyMessageDelayed(DOWNLOAD_PSDS_DATA, + GnssPsdsDownloader.PSDS_INTERVAL); + } } else { // Try download PSDS data again later according to backoff time. // Since this is delayed and not urgent, we do not hold a wake lock here. diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 8460d6797543..1781588b0ba2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -52,8 +52,6 @@ public final class GnssMeasurementsProvider extends private class GnssMeasurementListenerRegistration extends GnssListenerRegistration { - private static final String GNSS_MEASUREMENTS_BUCKET = "gnss_measurement"; - protected GnssMeasurementListenerRegistration( @Nullable GnssMeasurementRequest request, CallerIdentity callerIdentity, @@ -70,15 +68,13 @@ public final class GnssMeasurementsProvider extends @Nullable @Override protected void onActive() { - mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity()); } @Nullable @Override protected void onInactive() { - mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity()); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java index 443a6c0a5a92..dce9a12ff798 100644 --- a/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java +++ b/services/core/java/com/android/server/location/gnss/GnssPsdsDownloader.java @@ -38,6 +38,10 @@ import java.util.concurrent.TimeUnit; */ class GnssPsdsDownloader { + // how often to request PSDS download, in milliseconds + // current setting 24 hours + static final long PSDS_INTERVAL = 24 * 60 * 60 * 1000; + private static final String TAG = "GnssPsdsDownloader"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final long MAXIMUM_CONTENT_LENGTH_BYTES = 1000000; // 1MB. diff --git a/services/core/java/com/android/server/location/gnss/gps_debug.conf b/services/core/java/com/android/server/location/gnss/gps_debug.conf index 34ce96f3c8b3..90daf8c1c4a0 100644 --- a/services/core/java/com/android/server/location/gnss/gps_debug.conf +++ b/services/core/java/com/android/server/location/gnss/gps_debug.conf @@ -50,3 +50,12 @@ # Set bit 0x2 if NI GPS functionalities are to be locked # default - non is locked for backward compatibility #GPS_LOCK = 0 + +################################ +##### PSDS download settings ##### +################################ +# For wear devices only. +# Enable periodic PSDS download once a day. +# true: Enable periodic PSDS download +# false: Disable periodic PSDS download +#ENABLE_PSDS_PERIODIC_DOWNLOAD=false diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java index 5cb360be819a..483875230ac4 100644 --- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java @@ -24,55 +24,23 @@ import static com.android.server.location.LocationManagerService.TAG; import android.location.util.identity.CallerIdentity; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.Map; -import java.util.Objects; -import java.util.Set; /** * Helps manage appop monitoring for multiple location clients. */ public class LocationAttributionHelper { - private static class BucketKey { - private final String mBucket; - private final Object mKey; - - private BucketKey(String bucket, Object key) { - mBucket = Objects.requireNonNull(bucket); - mKey = Objects.requireNonNull(key); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - BucketKey that = (BucketKey) o; - return mBucket.equals(that.mBucket) - && mKey.equals(that.mKey); - } - - @Override - public int hashCode() { - return Objects.hash(mBucket, mKey); - } - } - private final AppOpsHelper mAppOpsHelper; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mAttributions; + private final Map<CallerIdentity, Integer> mAttributions; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mHighPowerAttributions; + private final Map<CallerIdentity, Integer> mHighPowerAttributions; public LocationAttributionHelper(AppOpsHelper appOpsHelper) { mAppOpsHelper = appOpsHelper; @@ -84,15 +52,16 @@ public class LocationAttributionHelper { /** * Report normal location usage for the given caller in the given bucket, with a unique key. */ - public synchronized void reportLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { - if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { - mAttributions.remove(identity); + public synchronized void reportLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { + mAttributions.put(identity, 1); } + } else { + mAttributions.put(identity, count + 1); } } @@ -100,13 +69,15 @@ public class LocationAttributionHelper { * Report normal location usage has stopped for the given caller in the given bucket, with a * unique key. */ - public synchronized void reportLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 1) { mAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); + } else if (count > 1) { + mAttributions.put(identity, count - 1); } } @@ -114,19 +85,19 @@ public class LocationAttributionHelper { * Report high power location usage for the given caller in the given bucket, with a unique * key. */ - public synchronized void reportHighPowerLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { + public synchronized void reportHighPowerLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (D) { + Log.v(TAG, "starting high power location attribution for " + identity); + } if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { - if (D) { - Log.v(TAG, "starting high power location attribution for " + identity); - } - } else { - mHighPowerAttributions.remove(identity); + mHighPowerAttributions.put(identity, 1); } + } else { + mHighPowerAttributions.put(identity, count + 1); } } @@ -134,16 +105,18 @@ public class LocationAttributionHelper { * Report high power location usage has stopped for the given caller in the given bucket, * with a unique key. */ - public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportHighPowerLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 1) { if (D) { Log.v(TAG, "stopping high power location attribution for " + identity); } mHighPowerAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); + } else if (count > 1) { + mHighPowerAttributions.put(identity, count - 1); } } } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 17efeb334013..0ce24dda666e 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -398,7 +398,7 @@ public class LocationProviderManager extends EVENT_LOG.logProviderClientActive(mName, getIdentity()); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStart(getIdentity()); } onHighPowerUsageChanged(); @@ -413,7 +413,7 @@ public class LocationProviderManager extends onHighPowerUsageChanged(); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStop(getIdentity()); } onProviderListenerInactive(); @@ -488,10 +488,10 @@ public class LocationProviderManager extends if (!getRequest().isHiddenFromAppOps()) { if (mIsUsingHighPower) { mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), getName(), getKey()); + getIdentity()); } else { mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), getName(), getKey()); + getIdentity()); } } } diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java index df1eb6d9fe3c..d17dbde496ce 100644 --- a/services/core/java/com/android/server/net/IpConfigStore.java +++ b/services/core/java/com/android/server/net/IpConfigStore.java @@ -322,8 +322,11 @@ public class IpConfigStore { gateway = InetAddresses.parseNumericAddress(in.readUTF()); } // If the destination is a default IPv4 route, use the gateway - // address unless already set. - if (dest.getAddress() instanceof Inet4Address + // address unless already set. If there is no destination, assume + // it is default route and use the gateway address in all cases. + if (dest == null) { + gatewayAddress = gateway; + } else if (dest.getAddress() instanceof Inet4Address && dest.getPrefixLength() == 0 && gatewayAddress == null) { gatewayAddress = gateway; } else { diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 64f72c52fcef..bf50db85d984 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4739,6 +4739,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0); newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid) ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0); + newAllowedReasons |= (uidBlockedState.allowedReasons + & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS); uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK) | newBlockedReasons; diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index f4b72a15d0e3..c876d411fac1 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -150,6 +150,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; +import com.android.net.module.util.BinderUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; @@ -2097,14 +2098,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void notifyAlertReached() throws RemoteException { - mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */); + // This binder object can only have been obtained by a process that holds + // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required. + BinderUtils.withCleanCallingIdentity(() -> + mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */)); } @Override public void notifyWarningOrLimitReached() { Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); - LocalServices.getService(NetworkPolicyManagerInternal.class) - .onStatsProviderWarningOrLimitReached(mTag); + BinderUtils.withCleanCallingIdentity(() -> + LocalServices.getService(NetworkPolicyManagerInternal.class) + .onStatsProviderWarningOrLimitReached(mTag)); } @Override diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e117cc6f7a22..99fdb2d884f3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -9639,7 +9639,7 @@ public class NotificationManagerService extends SystemService { } final long identity = Binder.clearCallingIdentity(); try { - List<String> associations = mCompanionManager.getAssociations( + List<?> associations = mCompanionManager.getAssociations( info.component.getPackageName(), info.userid); if (!ArrayUtils.isEmpty(associations)) { return true; diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index 5199ef68fb7f..be5f2194997a 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -89,7 +89,7 @@ public final class VibratorHelper { */ public void vibrate(VibrationEffect effect, AudioAttributes attrs, String reason) { mVibrator.vibrate(Process.SYSTEM_UID, PackageManagerService.PLATFORM_PACKAGE_NAME, - effect, reason, new VibrationAttributes.Builder(attrs, effect).build()); + effect, reason, new VibrationAttributes.Builder(attrs).build()); } /** Stop all notification vibrations (ringtone, alarm, notification usages). */ diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 53c2802ab76a..6f54625224bf 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -500,16 +500,10 @@ public class AppsFilter implements Watchable, Snappable { forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern(); } } - final StateProvider stateProvider = new StateProvider() { - // TODO: This lock and its handling should be owned by AppsFilter - private final Object mLock = new Object(); - - @Override - public void runWithState(CurrentStateCallback command) { - synchronized (mLock) { - command.currentState(pms.getPackageStates(), - injector.getUserManagerInternal().getUserInfos()); - } + final StateProvider stateProvider = command -> { + synchronized (injector.getLock()) { + command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(), + injector.getUserManagerInternal().getUserInfos()); } }; AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig, diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index dca86544b3c8..6ec3405727eb 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -318,7 +318,7 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryActivities(Intent intent, String resolvedType, long flags, int userId) { synchronized (mLock) { return mActivities.queryIntent(intent, resolvedType, flags, userId); @@ -326,7 +326,7 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryActivities(Intent intent, String resolvedType, long flags, List<ParsedActivity> activities, int userId) { synchronized (mLock) { return mActivities.queryIntentForPackage( @@ -335,14 +335,14 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryProviders(Intent intent, String resolvedType, int flags, int userId) { + List<ResolveInfo> queryProviders(Intent intent, String resolvedType, long flags, int userId) { synchronized (mLock) { return mProviders.queryIntent(intent, resolvedType, flags, userId); } } @Nullable - List<ResolveInfo> queryProviders(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryProviders(Intent intent, String resolvedType, long flags, List<ParsedProvider> providers, int userId) { synchronized (mLock) { return mProviders.queryIntentForPackage(intent, resolvedType, flags, providers, userId); @@ -350,7 +350,7 @@ public class ComponentResolver } @Nullable - List<ProviderInfo> queryProviders(String processName, String metaDataKey, int uid, int flags, + List<ProviderInfo> queryProviders(String processName, String metaDataKey, int uid, long flags, int userId) { if (!sUserManager.exists(userId)) { return null; @@ -409,7 +409,7 @@ public class ComponentResolver } @Nullable - ProviderInfo queryProvider(String authority, int flags, int userId) { + ProviderInfo queryProvider(String authority, long flags, int userId) { synchronized (mLock) { final ParsedProvider p = mProvidersByAuthority.get(authority); if (p == null) { @@ -480,14 +480,14 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags, int userId) { + List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, long flags, int userId) { synchronized (mLock) { return mReceivers.queryIntent(intent, resolvedType, flags, userId); } } @Nullable - List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, long flags, List<ParsedActivity> receivers, int userId) { synchronized (mLock) { return mReceivers.queryIntentForPackage(intent, resolvedType, flags, receivers, userId); @@ -495,14 +495,14 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryServices(Intent intent, String resolvedType, int flags, int userId) { + List<ResolveInfo> queryServices(Intent intent, String resolvedType, long flags, int userId) { synchronized (mLock) { return mServices.queryIntent(intent, resolvedType, flags, userId); } } @Nullable - List<ResolveInfo> queryServices(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryServices(Intent intent, String resolvedType, long flags, List<ParsedService> services, int userId) { synchronized (mLock) { return mServices.queryIntentForPackage(intent, resolvedType, flags, services, userId); @@ -1380,7 +1380,7 @@ public class ComponentResolver return super.queryIntent(intent, resolvedType, defaultOnly, userId); } - List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags, int userId) { if (!sUserManager.exists(userId)) { return null; @@ -1392,7 +1392,7 @@ public class ComponentResolver } List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, - int flags, List<ParsedActivity> packageActivities, int userId) { + long flags, List<ParsedActivity> packageActivities, int userId) { if (!sUserManager.exists(userId)) { return null; } @@ -1669,7 +1669,7 @@ public class ComponentResolver // ActivityIntentResolver. protected final ArrayMap<ComponentName, ParsedActivity> mActivities = new ArrayMap<>(); - private int mFlags; + private long mFlags; } // Both receivers and activities share a class, but point to different get methods @@ -1711,7 +1711,7 @@ public class ComponentResolver } @Nullable - List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags, int userId) { if (!sUserManager.exists(userId)) { return null; @@ -1724,7 +1724,7 @@ public class ComponentResolver @Nullable List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, - int flags, List<ParsedProvider> packageProviders, int userId) { + long flags, List<ParsedProvider> packageProviders, int userId) { if (!sUserManager.exists(userId)) { return null; } @@ -1946,7 +1946,7 @@ public class ComponentResolver } private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>(); - private int mFlags; + private long mFlags; } private static final class ServiceIntentResolver @@ -1969,7 +1969,7 @@ public class ComponentResolver return super.queryIntent(intent, resolvedType, defaultOnly, userId); } - List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + List<ResolveInfo> queryIntent(Intent intent, String resolvedType, long flags, int userId) { if (!sUserManager.exists(userId)) return null; mFlags = flags; @@ -1979,7 +1979,7 @@ public class ComponentResolver } List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, - int flags, List<ParsedService> packageServices, int userId) { + long flags, List<ParsedService> packageServices, int userId) { if (!sUserManager.exists(userId)) return null; if (packageServices == null) { return Collections.emptyList(); @@ -2190,7 +2190,7 @@ public class ComponentResolver // Keys are String (activity class name), values are Activity. private final ArrayMap<ComponentName, ParsedService> mServices = new ArrayMap<>(); - private int mFlags; + private long mFlags; } static final class InstantAppIntentResolver diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index a9df4bab6fd8..3e849f811594 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -133,35 +133,36 @@ public interface Computer { } @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, - int flags, @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, + @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, - int flags, int userId); + long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, String resolvedType, - int flags, int userId, int callingUid, boolean includeInstantApps); + long flags, int userId, int callingUid, boolean includeInstantApps); @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(Intent intent, - String resolvedType, int flags, int filterCallingUid, int userId, + String resolvedType, long flags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, String pkgName, String instantAppPkgName); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ActivityInfo getActivityInfo(ComponentName component, int flags, int userId); + ActivityInfo getActivityInfo(ComponentName component, long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ActivityInfo getActivityInfoInternal(ComponentName component, int flags, + ActivityInfo getActivityInfoInternal(ComponentName component, long flags, int filterCallingUid, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) AndroidPackage getPackage(String packageName); @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) AndroidPackage getPackage(int uid); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ApplicationInfo generateApplicationInfoFromSettings(String packageName, int flags, + ApplicationInfo generateApplicationInfoFromSettings(String packageName, long flags, int filterCallingUid, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ApplicationInfo getApplicationInfo(String packageName, int flags, int userId); + ApplicationInfo getApplicationInfo(String packageName, long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + ApplicationInfo getApplicationInfoInternal(String packageName, long flags, int filterCallingUid, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) ComponentName getDefaultHomeActivity(int userId); @@ -169,7 +170,7 @@ public interface Computer { ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, String resolvedType, - int flags, int sourceUserId, int parentUserId); + long flags, int sourceUserId, int parentUserId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) Intent getHomeIntent(); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @@ -180,11 +181,11 @@ public interface Computer { String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid, boolean resolveForStart, int userId, Intent intent); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - PackageInfo generatePackageInfo(PackageStateInternal ps, int flags, int userId); + PackageInfo generatePackageInfo(PackageStateInternal ps, long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - PackageInfo getPackageInfo(String packageName, int flags, int userId); + PackageInfo getPackageInfo(String packageName, long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - PackageInfo getPackageInfoInternal(String packageName, long versionCode, int flags, + PackageInfo getPackageInfoInternal(String packageName, long versionCode, long flags, int filterCallingUid, int userId); /** @@ -202,12 +203,12 @@ public interface Computer { @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) @Nullable PackageState getPackageStateCopied(@NonNull String packageName); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId); + ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, int sourceUserId, int targetUserId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ServiceInfo getServiceInfo(ComponentName component, int flags, int userId); + ServiceInfo getServiceInfo(ComponentName component, long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) SharedLibraryInfo getSharedLibraryInfo(String name, long version); @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) @@ -224,7 +225,7 @@ public interface Computer { boolean canViewInstantApps(int callingUid, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, int userId, - int flags); + long flags); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) boolean isCallerSameApp(String packageName, int uid); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @@ -234,7 +235,7 @@ public interface Computer { @PackageManager.ComponentType int type); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, - String resolvedType, int flags); + String resolvedType, long flags); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) boolean isInstantApp(String packageName, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) @@ -254,18 +255,18 @@ public interface Computer { @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) int checkUidPermission(String permName, int uid); @Computer.LiveImplementation(override = Computer.LiveImplementation.MANDATORY) - int getPackageUidInternal(String packageName, int flags, int userId, int callingUid); + int getPackageUidInternal(String packageName, long flags, int userId, int callingUid); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - int updateFlagsForApplication(int flags, int userId); + long updateFlagsForApplication(long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - int updateFlagsForComponent(int flags, int userId); + long updateFlagsForComponent(long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - int updateFlagsForPackage(int flags, int userId); + long updateFlagsForPackage(long flags, int userId); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps, + long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId, @@ -291,10 +292,10 @@ public interface Computer { void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityInternal( - Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always, + Intent intent, String resolvedType, long flags, List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered); @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED) - ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags, + ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, long flags, List<ResolveInfo> query, boolean debug, int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @@ -337,7 +338,8 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @NonNull - int[] getPackageGids(@NonNull String packageName, int flags, @UserIdInt int userId); + int[] getPackageGids(@NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) int getTargetSdkVersion(@NonNull String packageName); @@ -348,13 +350,13 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable - ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId); + ActivityInfo getReceiverInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName, - int flags, @UserIdInt int userId); + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) boolean canRequestPackageInstalls(@NonNull String packageName, int callingUid, @@ -367,17 +369,18 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - int flags, int callingUid, @UserIdInt int userId); + @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, int flags, @UserIdInt int userId); + @NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable - ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId); + ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable @@ -436,17 +439,17 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @NonNull ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(@NonNull String[] permissions, - int flags, @UserIdInt int userId); + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @NonNull - List<ApplicationInfo> getInstalledApplications(int flags, @UserIdInt int userId, - int callingUid); + List<ApplicationInfo> getInstalledApplications(@PackageManager.ApplicationInfoFlags long flags, + @UserIdInt int userId, int callingUid); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable - ProviderInfo resolveContentProvider(@NonNull String name, int flags, - @UserIdInt int userId, int callingUid); + ProviderInfo resolveContentProvider(@NonNull String name, + @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable @@ -460,7 +463,7 @@ public interface Computer { @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @NonNull ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, int uid, - int flags, @Nullable String metaDataKey); + @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) @Nullable @@ -557,7 +560,8 @@ public interface Computer { boolean canQueryPackage(int callingUid, @Nullable String targetPackageName); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) - int getPackageUid(@NonNull String packageName, int flags, @UserIdInt int userId); + int getPackageUid(@NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId); @Computer.LiveImplementation(override = LiveImplementation.MANDATORY) boolean canAccessComponent(int callingUid, @NonNull ComponentName component, diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 887dfff41384..2d61773e6796 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -125,7 +125,6 @@ import android.util.SparseBooleanArray; import android.util.TypedXmlSerializer; import android.util.Xml; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -165,7 +164,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -215,7 +213,7 @@ public class ComputerEngine implements Computer { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public boolean isEnabledAndMatch(AndroidPackage pkg, ParsedMainComponent component, - int flags, int userId) { + long flags, int userId) { PackageStateInternal pkgState = getPackage(component.getPackageName()); if (pkgState == null) { return false; @@ -431,8 +429,8 @@ public class ComputerEngine implements Computer { } public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, - @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { if (!mUserManager.exists(userId)) return Collections.emptyList(); @@ -543,15 +541,15 @@ public class ComputerEngine implements Computer { } public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { return queryIntentActivitiesInternal( intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(), userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); } public final @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, - String resolvedType, int flags, int userId, int callingUid, - boolean includeInstantApps) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId, + int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); enforceCrossUserOrProfilePermission(callingUid, userId, @@ -627,8 +625,8 @@ public class ComputerEngine implements Computer { } protected @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent, - String resolvedType, int flags, int userId, int callingUid, - String instantAppPkgName) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId, + int callingUid, String instantAppPkgName) { // reader String pkgName = intent.getPackage(); if (pkgName == null) { @@ -655,9 +653,9 @@ public class ComputerEngine implements Computer { } public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( - Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, - boolean resolveForStart, boolean allowDynamicSplits, String pkgName, - String instantAppPkgName) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, + String pkgName, String instantAppPkgName) { // reader boolean sortResult = false; boolean addInstant = false; @@ -787,7 +785,8 @@ public class ComputerEngine implements Computer { return null; } - public final ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + public final ActivityInfo getActivityInfo(ComponentName component, + @PackageManager.ResolveInfoFlags long flags, int userId) { return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId); } @@ -797,8 +796,8 @@ public class ComputerEngine implements Computer { * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - public final ActivityInfo getActivityInfoInternal(ComponentName component, int flags, - int filterCallingUid, int userId) { + public final ActivityInfo getActivityInfoInternal(ComponentName component, + @PackageManager.ResolveInfoFlags long flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); @@ -811,8 +810,8 @@ public class ComputerEngine implements Computer { return getActivityInfoInternalBody(component, flags, filterCallingUid, userId); } - protected ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags, - int filterCallingUid, int userId) { + protected ActivityInfo getActivityInfoInternalBody(ComponentName component, + @PackageManager.ResolveInfoFlags long flags, int filterCallingUid, int userId) { ParsedActivity a = mComponentResolver.getActivity(component); if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); @@ -852,7 +851,7 @@ public class ComputerEngine implements Computer { } public final ApplicationInfo generateApplicationInfoFromSettings(String packageName, - int flags, int filterCallingUid, int userId) { + long flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; PackageStateInternal ps = mSettings.getPackage(packageName); if (ps != null) { @@ -879,7 +878,8 @@ public class ComputerEngine implements Computer { return null; } - public final ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + public final ApplicationInfo getApplicationInfo(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int userId) { return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId); } @@ -889,7 +889,8 @@ public class ComputerEngine implements Computer { * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - public final ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + public final ApplicationInfo getApplicationInfoInternal(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForApplication(flags, userId); @@ -903,7 +904,8 @@ public class ComputerEngine implements Computer { return getApplicationInfoInternalBody(packageName, flags, filterCallingUid, userId); } - protected ApplicationInfo getApplicationInfoInternalBody(String packageName, int flags, + protected ApplicationInfo getApplicationInfoInternalBody(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) { // writer // Normalize package name to handle renamed packages and static libs @@ -960,7 +962,7 @@ public class ComputerEngine implements Computer { } protected ArrayList<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPrBody( - Intent intent, int matchFlags, List<ResolveInfo> candidates, + Intent intent, long matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) { final ArrayList<ResolveInfo> result = new ArrayList<>(); final ArrayList<ResolveInfo> matchAllList = new ArrayList<>(); @@ -1159,7 +1161,8 @@ public class ComputerEngine implements Computer { } public final CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, - String resolvedType, int flags, int sourceUserId, int parentUserId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId, + int parentUserId) { if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, sourceUserId)) { return null; @@ -1380,7 +1383,7 @@ public class ComputerEngine implements Computer { } private List<ResolveInfo> filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, - int matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, + long matchFlags, List<ResolveInfo> candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { final boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0; @@ -1423,9 +1426,8 @@ public class ComputerEngine implements Computer { } private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result, - Intent intent, - String resolvedType, int flags, int userId, boolean resolveForStart, - boolean isRequesterInstantApp) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int userId, boolean resolveForStart, boolean isRequesterInstantApp) { // first, check to see if we've got an instant app already installed final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0; ResolveInfo localInstantApp = null; @@ -1529,7 +1531,8 @@ public class ComputerEngine implements Computer { return result; } - public final PackageInfo generatePackageInfo(PackageStateInternal ps, int flags, int userId) { + public final PackageInfo generatePackageInfo(PackageStateInternal ps, + @PackageManager.PackageInfoFlags long flags, int userId) { if (!mUserManager.exists(userId)) return null; if (ps == null) { return null; @@ -1603,7 +1606,8 @@ public class ComputerEngine implements Computer { } } - public final PackageInfo getPackageInfo(String packageName, int flags, int userId) { + public final PackageInfo getPackageInfo(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId) { return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST, flags, Binder.getCallingUid(), userId); } @@ -1615,7 +1619,7 @@ public class ComputerEngine implements Computer { * trusted and will be used as-is; unlike userId which will be validated by this method. */ public final PackageInfo getPackageInfoInternal(String packageName, long versionCode, - int flags, int filterCallingUid, int userId) { + long flags, int filterCallingUid, int userId) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForPackage(flags, userId); enforceCrossUserPermission(Binder.getCallingUid(), userId, @@ -1626,7 +1630,7 @@ public class ComputerEngine implements Computer { } protected PackageInfo getPackageInfoInternalBody(String packageName, long versionCode, - int flags, int filterCallingUid, int userId) { + long flags, int filterCallingUid, int userId) { // reader // Normalize package name to handle renamed packages and static libs packageName = resolveInternalPackageNameLPr(packageName, versionCode); @@ -1711,7 +1715,7 @@ public class ComputerEngine implements Computer { return pkgSetting == null ? null : PackageStateImpl.copy(pkgSetting); } - public final ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { + public final ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId) { final int callingUid = Binder.getCallingUid(); if (getInstantAppPackageName(callingUid) != null) { return ParceledListSlice.emptyList(); @@ -1725,7 +1729,7 @@ public class ComputerEngine implements Computer { return getInstalledPackagesBody(flags, userId, callingUid); } - protected ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId, + protected ParceledListSlice<PackageInfo> getInstalledPackagesBody(long flags, int userId, int callingUid) { // writer final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0; @@ -1804,7 +1808,8 @@ public class ComputerEngine implements Computer { @Nullable private CrossProfileDomainInfo createForwardingResolveInfo( @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent, - @Nullable String resolvedType, int flags, int sourceUserId) { + @Nullable String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int sourceUserId) { int targetUserId = filter.getTargetUserId(); if (!isUserEnabled(targetUserId)) { return null; @@ -1891,7 +1896,7 @@ public class ComputerEngine implements Computer { @Nullable private CrossProfileDomainInfo queryCrossProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, - int flags, int sourceUserId, boolean matchInCurrentProfile) { + long flags, int sourceUserId, boolean matchInCurrentProfile) { if (matchingFilters == null) { return null; } @@ -1945,7 +1950,7 @@ public class ComputerEngine implements Computer { private ResolveInfo querySkipCurrentProfileIntents( List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType, - int flags, int sourceUserId) { + long flags, int sourceUserId) { if (matchingFilters != null) { int size = matchingFilters.size(); for (int i = 0; i < size; i++) { @@ -1964,7 +1969,8 @@ public class ComputerEngine implements Computer { return null; } - public final ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { + public final ServiceInfo getServiceInfo(ComponentName component, + @PackageManager.ResolveInfoFlags long flags, int userId) { if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); @@ -1974,8 +1980,8 @@ public class ComputerEngine implements Computer { return getServiceInfoBody(component, flags, userId, callingUid); } - protected ServiceInfo getServiceInfoBody(ComponentName component, int flags, int userId, - int callingUid) { + protected ServiceInfo getServiceInfoBody(ComponentName component, + @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) { ParsedService s = mComponentResolver.getService(component); if (DEBUG_PACKAGE_INFO) { Log.v( @@ -2227,7 +2233,7 @@ public class ComputerEngine implements Computer { } public final boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, - int userId, int flags) { + int userId, @PackageManager.ComponentInfoFlags long flags) { // Callers can access only the libs they depend on, otherwise they need to explicitly // ask for the shared libraries given the caller is allowed to access all static libs. if ((flags & PackageManager.MATCH_STATIC_SHARED_LIBRARIES) != 0) { @@ -2375,7 +2381,7 @@ public class ComputerEngine implements Computer { * activity was not set by the DPC. */ public final boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, - int userId, String resolvedType, int flags) { + int userId, String resolvedType, @PackageManager.ResolveInfoFlags long flags) { return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm( intent, userId, resolvedType, flags); } @@ -2416,7 +2422,7 @@ public class ComputerEngine implements Computer { private boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck, int flags) { + boolean skipPackageCheck, @PackageManager.ResolveInfoFlags long flags) { if (mInstantAppResolverConnection == null) { return false; } @@ -2456,7 +2462,7 @@ public class ComputerEngine implements Computer { // Or if there's already an ephemeral app installed that handles the action protected boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck, int flags) { + boolean skipPackageCheck, @PackageManager.ResolveInfoFlags long flags) { final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); for (int n = 0; n < count; n++) { final ResolveInfo info = resolvedActivities.get(n); @@ -2488,7 +2494,7 @@ public class ComputerEngine implements Computer { } private boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, - String resolvedType, int flags) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags) { PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId); //TODO(b/158003772): Remove double query @@ -2645,8 +2651,8 @@ public class ComputerEngine implements Computer { return mPermissionManager.checkUidPermission(uid, permName); } - public int getPackageUidInternal(String packageName, int flags, int userId, - int callingUid) { + public int getPackageUidInternal(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) { // reader final AndroidPackage p = mPackages.get(packageName); if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) { @@ -2670,7 +2676,7 @@ public class ComputerEngine implements Computer { /** * Update given flags based on encryption status of current user. */ - private int updateFlags(int flags, int userId) { + private long updateFlags(long flags, int userId) { if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE)) != 0) { // Caller expressed an explicit opinion about what encryption @@ -2691,21 +2697,21 @@ public class ComputerEngine implements Computer { /** * Update given flags when being used to request {@link ApplicationInfo}. */ - public final int updateFlagsForApplication(int flags, int userId) { + public final long updateFlagsForApplication(long flags, int userId) { return updateFlagsForPackage(flags, userId); } /** * Update given flags when being used to request {@link ComponentInfo}. */ - public final int updateFlagsForComponent(int flags, int userId) { + public final long updateFlagsForComponent(long flags, int userId) { return updateFlags(flags, userId); } /** * Update given flags when being used to request {@link PackageInfo}. */ - public final int updateFlagsForPackage(int flags, int userId) { + public final long updateFlagsForPackage(long flags, int userId) { final boolean isCallerSystemUser = UserHandle.getCallingUserId() == UserHandle.USER_SYSTEM; if ((flags & PackageManager.MATCH_ANY_USER) != 0) { @@ -2741,14 +2747,14 @@ public class ComputerEngine implements Computer { * action and a {@code android.intent.category.BROWSABLE} category</li> * </ul> */ - public final int updateFlagsForResolve(int flags, int userId, int callingUid, + public final long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { return updateFlagsForResolve(flags, userId, callingUid, wantInstantApps, false /*onlyExposedExplicitly*/, isImplicitImageCaptureIntentAndNotSetByDpc); } - public final int updateFlagsForResolve(int flags, int userId, int callingUid, + public final long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { // Safe mode means we shouldn't match any third-party components @@ -3169,7 +3175,7 @@ public class ComputerEngine implements Computer { // The body of findPreferredActivity. protected PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityBody( - Intent intent, String resolvedType, int flags, + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered, int callingUid, boolean isDeviceProvisioned) { @@ -3378,7 +3384,7 @@ public class ComputerEngine implements Computer { } public final PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityInternal( - Intent intent, String resolvedType, int flags, + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) { @@ -3395,8 +3401,8 @@ public class ComputerEngine implements Computer { } public final ResolveInfo findPersistentPreferredActivityLP(Intent intent, - String resolvedType, - int flags, List<ResolveInfo> query, boolean debug, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + List<ResolveInfo> query, boolean debug, int userId) { final int n = query.size(); PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId); @@ -3592,7 +3598,8 @@ public class ComputerEngine implements Computer { } @Override - public int[] getPackageGids(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int[] getPackageGids(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForPackage(flags, userId); @@ -3668,8 +3675,8 @@ public class ComputerEngine implements Computer { @Nullable @Override - public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ActivityInfo getReceiverInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); @@ -3702,7 +3709,7 @@ public class ComputerEngine implements Computer { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName, - int flags, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return null; Preconditions.checkArgumentNonnegative(userId, "userId must be >= 0"); final int callingUid = Binder.getCallingUid(); @@ -3831,7 +3838,7 @@ public class ComputerEngine implements Computer { @Override public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - int flags, int callingUid, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) { List<VersionedPackage> versionedPackages = null; final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates(); final int packageCount = packageStates.size(); @@ -3888,7 +3895,8 @@ public class ComputerEngine implements Computer { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, int flags, @UserIdInt int userId) { + @NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES, "getDeclaredSharedLibraries"); int callingUid = Binder.getCallingUid(); @@ -3963,8 +3971,8 @@ public class ComputerEngine implements Computer { @Nullable @Override - public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); @@ -4438,7 +4446,8 @@ public class ComputerEngine implements Computer { @NonNull @Override public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( - @NonNull String[] permissions, int flags, @UserIdInt int userId) { + @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList(); flags = updateFlagsForPackage(flags, userId); enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, @@ -4458,7 +4467,8 @@ public class ComputerEngine implements Computer { } private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageStateInternal ps, - String[] permissions, boolean[] tmp, int flags, int userId) { + String[] permissions, boolean[] tmp, @PackageManager.PackageInfoFlags long flags, + int userId) { int numMatch = 0; for (int i=0; i<permissions.length; i++) { final String permission = permissions[i]; @@ -4478,7 +4488,7 @@ public class ComputerEngine implements Computer { // The above might return null in cases of uninstalled apps or install-state // skew across users/profiles. if (pi != null) { - if ((flags&PackageManager.GET_PERMISSIONS) == 0) { + if ((flags & PackageManager.GET_PERMISSIONS) == 0) { if (numMatch == permissions.length) { pi.requestedPermissions = permissions; } else { @@ -4498,7 +4508,8 @@ public class ComputerEngine implements Computer { @NonNull @Override - public List<ApplicationInfo> getInstalledApplications(int flags, @UserIdInt int userId, + public List<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId, int callingUid) { if (getInstantAppPackageName(callingUid) != null) { return Collections.emptyList(); @@ -4521,7 +4532,7 @@ public class ComputerEngine implements Computer { list = new ArrayList<>(packageStates.size()); for (PackageStateInternal ps : packageStates.values()) { ApplicationInfo ai; - int effectiveFlags = flags; + long effectiveFlags = flags; if (ps.isSystem()) { effectiveFlags |= PackageManager.MATCH_ANY_USER; } @@ -4574,8 +4585,8 @@ public class ComputerEngine implements Computer { @Nullable @Override - public ProviderInfo resolveContentProvider(@NonNull String name, int flags, - @UserIdInt int userId, int callingUid) { + public ProviderInfo resolveContentProvider(@NonNull String name, + @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) { if (!mUserManager.exists(userId)) return null; flags = updateFlagsForComponent(flags, userId); final ProviderInfo providerInfo = mComponentResolver.queryProvider(name, flags, userId); @@ -4664,7 +4675,7 @@ public class ComputerEngine implements Computer { @NonNull @Override public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, - int uid, int flags, @Nullable String metaDataKey) { + int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) { final int callingUid = Binder.getCallingUid(); final int userId = processName != null ? UserHandle.getUserId(uid) : UserHandle.getCallingUserId(); @@ -5209,7 +5220,8 @@ public class ComputerEngine implements Computer { } @Override - public int getPackageUid(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int getPackageUid(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { if (!mUserManager.exists(userId)) return -1; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForPackage(flags, userId); @@ -5312,7 +5324,7 @@ public class ComputerEngine implements Computer { if (parent == null) { return false; } - int flags = updateFlagsForResolve(0, parent.id, callingUid, + long flags = updateFlagsForResolve(0, parent.id, callingUid, false /*includeInstantApps*/, isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, parent.id, resolvedType, 0)); diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java index 801aaeff8ada..f180d19a36d7 100644 --- a/services/core/java/com/android/server/pm/ComputerLocked.java +++ b/services/core/java/com/android/server/pm/ComputerLocked.java @@ -29,6 +29,7 @@ import android.content.pm.InstallSourceInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.KeySet; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; @@ -89,7 +90,7 @@ public final class ComputerLocked extends ComputerEngine { } } public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( - Intent intent, String resolvedType, int flags, int filterCallingUid, int userId, + Intent intent, String resolvedType, long flags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, String pkgName, String instantAppPkgName) { synchronized (mLock) { @@ -312,7 +313,8 @@ public final class ComputerLocked extends ComputerEngine { } @Override - public int[] getPackageGids(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int[] getPackageGids(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { synchronized (mLock) { return super.getPackageGids(packageName, flags, userId); } @@ -336,8 +338,8 @@ public final class ComputerLocked extends ComputerEngine { @Nullable @Override - public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ActivityInfo getReceiverInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { synchronized (mLock) { return super.getReceiverInfo(component, flags, userId); } @@ -346,7 +348,7 @@ public final class ComputerLocked extends ComputerEngine { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName, - int flags, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { synchronized (mLock) { return super.getSharedLibraries(packageName, flags, userId); } @@ -363,7 +365,7 @@ public final class ComputerLocked extends ComputerEngine { @Override public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - int flags, int callingUid, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) { synchronized (mLock) { return super.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); } @@ -372,7 +374,8 @@ public final class ComputerLocked extends ComputerEngine { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, int flags, @UserIdInt int userId) { + @NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { synchronized (mLock) { return super.getDeclaredSharedLibraries(packageName, flags, userId); } @@ -380,8 +383,8 @@ public final class ComputerLocked extends ComputerEngine { @Nullable @Override - public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { synchronized (mLock) { return super.getProviderInfo(component, flags, userId); } @@ -495,7 +498,8 @@ public final class ComputerLocked extends ComputerEngine { @NonNull @Override public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( - @NonNull String[] permissions, int flags, @UserIdInt int userId) { + @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { synchronized (mLock) { return super.getPackagesHoldingPermissions(permissions, flags, userId); } @@ -503,7 +507,8 @@ public final class ComputerLocked extends ComputerEngine { @NonNull @Override - public List<ApplicationInfo> getInstalledApplications(int flags, @UserIdInt int userId, + public List<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId, int callingUid) { synchronized (mLock) { return super.getInstalledApplications(flags, userId, callingUid); @@ -512,8 +517,8 @@ public final class ComputerLocked extends ComputerEngine { @Nullable @Override - public ProviderInfo resolveContentProvider(@NonNull String name, int flags, - @UserIdInt int userId, int callingUid) { + public ProviderInfo resolveContentProvider(@NonNull String name, + @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) { synchronized (mLock) { return super.resolveContentProvider(name, flags, userId, callingUid); } @@ -539,7 +544,7 @@ public final class ComputerLocked extends ComputerEngine { @NonNull @Override public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, - int uid, int flags, @Nullable String metaDataKey) { + int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) { synchronized (mLock) { return super.queryContentProviders(processName, uid, flags, metaDataKey); } @@ -711,7 +716,8 @@ public final class ComputerLocked extends ComputerEngine { } @Override - public int getPackageUid(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int getPackageUid(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { synchronized (mLock) { return super.getPackageUid(packageName, flags, userId); } diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java index ca17d66f4e25..a3cd0929dd65 100644 --- a/services/core/java/com/android/server/pm/ComputerTracker.java +++ b/services/core/java/com/android/server/pm/ComputerTracker.java @@ -97,8 +97,8 @@ public final class ComputerTracker implements Computer { } public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, - @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { ThreadComputer current = snapshot(); @@ -111,7 +111,7 @@ public final class ComputerTracker implements Computer { } } public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { ThreadComputer current = snapshot(); try { return current.mComputer.queryIntentActivitiesInternal(intent, resolvedType, flags, @@ -121,8 +121,8 @@ public final class ComputerTracker implements Computer { } } public @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, - String resolvedType, int flags, int userId, int callingUid, - boolean includeInstantApps) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId, + int callingUid, boolean includeInstantApps) { ThreadComputer current = snapshot(); try { return current.mComputer.queryIntentServicesInternal(intent, resolvedType, flags, @@ -132,10 +132,9 @@ public final class ComputerTracker implements Computer { } } public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody( - Intent intent, - String resolvedType, int flags, int filterCallingUid, int userId, - boolean resolveForStart, boolean allowDynamicSplits, String pkgName, - String instantAppPkgName) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits, + String pkgName, String instantAppPkgName) { ThreadComputer current = live(); try { return current.mComputer.queryIntentActivitiesInternalBody(intent, resolvedType, @@ -145,7 +144,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + public ActivityInfo getActivityInfo(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int userId) { ThreadComputer current = snapshot(); try { return current.mComputer.getActivityInfo(component, flags, userId); @@ -153,7 +153,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public ActivityInfo getActivityInfoInternal(ComponentName component, int flags, + public ActivityInfo getActivityInfoInternal(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int filterCallingUid, int userId) { ThreadComputer current = live(); try { @@ -180,7 +181,7 @@ public final class ComputerTracker implements Computer { } } public ApplicationInfo generateApplicationInfoFromSettings(String packageName, - int flags, int filterCallingUid, int userId) { + long flags, int filterCallingUid, int userId) { ThreadComputer current = live(); try { return current.mComputer.generateApplicationInfoFromSettings(packageName, flags, @@ -189,7 +190,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + public ApplicationInfo getApplicationInfo(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int userId) { ThreadComputer current = snapshot(); try { return current.mComputer.getApplicationInfo(packageName, flags, userId); @@ -197,8 +199,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public ApplicationInfo getApplicationInfoInternal(String packageName, int flags, - int filterCallingUid, int userId) { + public ApplicationInfo getApplicationInfoInternal(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) { ThreadComputer current = live(); try { return current.mComputer.getApplicationInfoInternal(packageName, flags, @@ -225,7 +227,8 @@ public final class ComputerTracker implements Computer { } } public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, - String resolvedType, int flags, int sourceUserId, int parentUserId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId, + int parentUserId) { ThreadComputer current = live(); try { return current.mComputer.getCrossProfileDomainPreferredLpr(intent, resolvedType, @@ -264,7 +267,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public PackageInfo generatePackageInfo(PackageStateInternal ps, int flags, int userId) { + public PackageInfo generatePackageInfo(PackageStateInternal ps, + @PackageManager.PackageInfoFlags long flags, int userId) { ThreadComputer current = live(); try { return current.mComputer.generatePackageInfo(ps, flags, userId); @@ -272,7 +276,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public PackageInfo getPackageInfo(String packageName, int flags, int userId) { + public PackageInfo getPackageInfo(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId) { ThreadComputer current = snapshot(); try { return current.mComputer.getPackageInfo(packageName, flags, userId); @@ -281,7 +286,7 @@ public final class ComputerTracker implements Computer { } } public PackageInfo getPackageInfoInternal(String packageName, long versionCode, - int flags, int filterCallingUid, int userId) { + long flags, int filterCallingUid, int userId) { ThreadComputer current = live(); try { return current.mComputer.getPackageInfoInternal(packageName, versionCode, flags, @@ -317,7 +322,7 @@ public final class ComputerTracker implements Computer { } } - public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { + public ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId) { ThreadComputer current = snapshot(); try { return current.mComputer.getInstalledPackages(flags, userId); @@ -335,7 +340,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { + public ServiceInfo getServiceInfo(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int userId) { ThreadComputer current = live(); try { return current.mComputer.getServiceInfo(component, flags, userId); @@ -440,7 +446,7 @@ public final class ComputerTracker implements Computer { } } public boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, - int userId, int flags) { + int userId, @PackageManager.ComponentInfoFlags long flags) { ThreadComputer current = live(); try { return current.mComputer.filterSharedLibPackage(ps, uid, userId, flags); @@ -474,7 +480,7 @@ public final class ComputerTracker implements Computer { } } public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, - int userId, String resolvedType, int flags) { + int userId, String resolvedType, @PackageManager.ResolveInfoFlags long flags) { ThreadComputer current = live(); try { return current.mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, @@ -546,8 +552,8 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int getPackageUidInternal(String packageName, int flags, int userId, - int callingUid) { + public int getPackageUidInternal(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) { ThreadComputer current = live(); try { return current.mComputer.getPackageUidInternal(packageName, flags, userId, @@ -556,7 +562,7 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int updateFlagsForApplication(int flags, int userId) { + public long updateFlagsForApplication(long flags, int userId) { ThreadComputer current = live(); try { return current.mComputer.updateFlagsForApplication(flags, userId); @@ -564,7 +570,7 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int updateFlagsForComponent(int flags, int userId) { + public long updateFlagsForComponent(long flags, int userId) { ThreadComputer current = live(); try { return current.mComputer.updateFlagsForComponent(flags, userId); @@ -572,7 +578,7 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int updateFlagsForPackage(int flags, int userId) { + public long updateFlagsForPackage(long flags, int userId) { ThreadComputer current = live(); try { return current.mComputer.updateFlagsForPackage(flags, userId); @@ -580,7 +586,7 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int updateFlagsForResolve(int flags, int userId, int callingUid, + public long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { ThreadComputer current = snapshot(); try { @@ -590,7 +596,7 @@ public final class ComputerTracker implements Computer { current.release(); } } - public int updateFlagsForResolve(int flags, int userId, int callingUid, + public long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { ThreadComputer current = live(); @@ -642,8 +648,9 @@ public final class ComputerTracker implements Computer { } } public PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityInternal( - Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always, - boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, + int userId, boolean queryMayBeFiltered) { ThreadComputer current = live(); try { return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags, @@ -653,8 +660,8 @@ public final class ComputerTracker implements Computer { } } public ResolveInfo findPersistentPreferredActivityLP(Intent intent, - String resolvedType, int flags, List<ResolveInfo> query, boolean debug, - int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + List<ResolveInfo> query, boolean debug, int userId) { ThreadComputer current = live(); try { return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType, @@ -741,7 +748,8 @@ public final class ComputerTracker implements Computer { } @Override - public int[] getPackageGids(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int[] getPackageGids(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getPackageGids(packageName, flags, userId); } @@ -765,8 +773,8 @@ public final class ComputerTracker implements Computer { @Nullable @Override - public ActivityInfo getReceiverInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ActivityInfo getReceiverInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getReceiverInfo(component, flags, userId); } @@ -775,7 +783,7 @@ public final class ComputerTracker implements Computer { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName, - int flags, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getSharedLibraries(packageName, flags, userId); } @@ -800,7 +808,7 @@ public final class ComputerTracker implements Computer { @Override public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - int flags, int callingUid, @UserIdInt int userId) { + @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); @@ -810,7 +818,8 @@ public final class ComputerTracker implements Computer { @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, int flags, @UserIdInt int userId) { + @NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getDeclaredSharedLibraries(packageName, flags, userId); } @@ -818,8 +827,8 @@ public final class ComputerTracker implements Computer { @Nullable @Override - public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getProviderInfo(component, flags, userId); } @@ -934,7 +943,8 @@ public final class ComputerTracker implements Computer { @NonNull @Override public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( - @NonNull String[] permissions, int flags, @UserIdInt int userId) { + @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getPackagesHoldingPermissions(permissions, flags, userId); } @@ -942,7 +952,8 @@ public final class ComputerTracker implements Computer { @NonNull @Override - public List<ApplicationInfo> getInstalledApplications(int flags, @UserIdInt int userId, + public List<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId, int callingUid) { try (ThreadComputer current = snapshot()) { return current.mComputer.getInstalledApplications(flags, userId, callingUid); @@ -951,8 +962,8 @@ public final class ComputerTracker implements Computer { @Nullable @Override - public ProviderInfo resolveContentProvider(@NonNull String name, int flags, - @UserIdInt int userId, int callingUid) { + public ProviderInfo resolveContentProvider(@NonNull String name, + @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) { try (ThreadComputer current = snapshot()) { return current.mComputer.resolveContentProvider(name, flags, userId, callingUid); } @@ -979,7 +990,7 @@ public final class ComputerTracker implements Computer { @NonNull @Override public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, - int uid, int flags, @Nullable String metaDataKey) { + int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) { try (ThreadComputer current = snapshot()) { return current.mComputer.queryContentProviders(processName, uid, flags, metaDataKey); } @@ -1152,7 +1163,8 @@ public final class ComputerTracker implements Computer { } @Override - public int getPackageUid(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int getPackageUid(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { try (ThreadComputer current = snapshot()) { return current.mComputer.getPackageUid(packageName, flags, userId); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 861fec7cc236..5ca0618d723f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -649,12 +649,17 @@ final class InstallPackageHelper { mPm.checkPackageFrozen(pkgName); } - // Also need to kill any apps that are dependent on the library. + final boolean isReplace = + reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace; + // Also need to kill any apps that are dependent on the library, except the case of + // installation of new version static shared library. if (clientLibPkgs != null) { - for (int i = 0; i < clientLibPkgs.size(); i++) { - AndroidPackage clientPkg = clientLibPkgs.get(i); - mPm.killApplication(clientPkg.getPackageName(), - clientPkg.getUid(), "update lib"); + if (pkg.getStaticSharedLibName() == null || isReplace) { + for (int i = 0; i < clientLibPkgs.size(); i++) { + AndroidPackage clientPkg = clientLibPkgs.get(i); + mPm.killApplication(clientPkg.getPackageName(), + clientPkg.getUid(), "update lib"); + } } } @@ -676,8 +681,6 @@ final class InstallPackageHelper { ksms.addScannedPackageLPw(pkg); mPm.mComponentResolver.addAllComponents(pkg, chatty); - final boolean isReplace = - reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace; mPm.mAppsFilter.addPackage(pkgSetting, isReplace); mPm.addAllPackageProperties(pkg); @@ -2050,6 +2053,7 @@ final class InstallPackageHelper { } } + final String packageName = pkg.getPackageName(); for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) { final String filePath = entry.getKey(); final String signaturePath = entry.getValue(); @@ -2077,10 +2081,13 @@ final class InstallPackageHelper { try { // A file may already have fs-verity, e.g. when reused during a split // install. If the measurement succeeds, no need to attempt to set up. - mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath, + rootHash); } catch (Installer.InstallerException e) { - mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize()); - mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash); + mPm.mInstaller.installApkVerity(packageName, filePath, fd, + result.getContentSize()); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath, + rootHash); } } finally { IoUtils.closeQuietly(fd); @@ -3125,10 +3132,12 @@ final class InstallPackageHelper { true, true, pkgList, uidArray, null); } } else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib + // No need to kill consumers if it's installation of new version static shared lib. + final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null; for (int i = 0; i < res.mLibraryConsumers.size(); i++) { AndroidPackage pkg = res.mLibraryConsumers.get(i); // send broadcast that all consumers of the static shared library have changed - mPm.sendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */, + mPm.sendPackageChangedBroadcast(pkg.getPackageName(), dontKillApp, new ArrayList<>(Collections.singletonList(pkg.getPackageName())), pkg.getUid(), null); } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index c8bd2c0bcecf..a3803447083a 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -497,7 +497,7 @@ public class Installer extends SystemService { * * @throws InstallerException if {@code dexopt} fails. */ - public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, + public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext, @Nullable String seInfo, boolean downgrade, int targetSdkVersion, @@ -585,11 +585,14 @@ public class Installer extends SystemService { } } - public void rmPackageDir(String packageDir) throws InstallerException { + /** + * Remove a directory belonging to a package. + */ + public void rmPackageDir(String packageName, String packageDir) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(packageDir); try { - mInstalld.rmPackageDir(packageDir); + mInstalld.rmPackageDir(packageName, packageDir); } catch (Exception e) { throw InstallerException.from(e); } @@ -662,35 +665,44 @@ public class Installer extends SystemService { } } - public void createOatDir(String oatDir, String dexInstructionSet) + /** + * Creates an oat dir for given package and instruction set. + */ + public void createOatDir(String packageName, String oatDir, String dexInstructionSet) throws InstallerException { if (!checkBeforeRemote()) return; try { - mInstalld.createOatDir(oatDir, dexInstructionSet); + mInstalld.createOatDir(packageName, oatDir, dexInstructionSet); } catch (Exception e) { throw InstallerException.from(e); } } - public void linkFile(String relativePath, String fromBase, String toBase) + /** + * Creates a hardlink for a path. + */ + public void linkFile(String packageName, String relativePath, String fromBase, String toBase) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(fromBase); BlockGuard.getVmPolicy().onPathAccess(toBase); try { - mInstalld.linkFile(relativePath, fromBase, toBase); + mInstalld.linkFile(packageName, relativePath, fromBase, toBase); } catch (Exception e) { throw InstallerException.from(e); } } - public void moveAb(String apkPath, String instructionSet, String outputPath) + /** + * Moves oat/vdex/art from "B" set defined by ro.boot.slot_suffix to the default set. + */ + public void moveAb(String packageName, String apkPath, String instructionSet, String outputPath) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(apkPath); BlockGuard.getVmPolicy().onPathAccess(outputPath); try { - mInstalld.moveAb(apkPath, instructionSet, outputPath); + mInstalld.moveAb(packageName, apkPath, instructionSet, outputPath); } catch (Exception e) { throw InstallerException.from(e); } @@ -700,35 +712,41 @@ public class Installer extends SystemService { * Deletes the optimized artifacts generated by ART and returns the number * of freed bytes. */ - public long deleteOdex(String apkPath, String instructionSet, String outputPath) - throws InstallerException { + public long deleteOdex(String packageName, String apkPath, String instructionSet, + String outputPath) throws InstallerException { if (!checkBeforeRemote()) return -1; BlockGuard.getVmPolicy().onPathAccess(apkPath); BlockGuard.getVmPolicy().onPathAccess(outputPath); try { - return mInstalld.deleteOdex(apkPath, instructionSet, outputPath); + return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath); } catch (Exception e) { throw InstallerException.from(e); } } - public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize) - throws InstallerException { + /** + * Enables legacy apk-verity for an apk. + */ + public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput, + int contentSize) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(filePath); try { - mInstalld.installApkVerity(filePath, verityInput, contentSize); + mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize); } catch (Exception e) { throw InstallerException.from(e); } } - public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash) - throws InstallerException { + /** + * Checks if provided hash matches the file's fs-verity merkle tree root hash. + */ + public void assertFsverityRootHashMatches(String packageName, String filePath, + @NonNull byte[] expectedHash) throws InstallerException { if (!checkBeforeRemote()) return; BlockGuard.getVmPolicy().onPathAccess(filePath); try { - mInstalld.assertFsverityRootHashMatches(filePath, expectedHash); + mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 9e6f4f7b75d2..c125fe10e899 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -411,6 +411,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { final List<String> paths = AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); + final String packageName = pkg.getPackageName(); for (String dexCodeInstructionSet : dexCodeInstructionSets) { for (String path : paths) { String oatDir = PackageDexOptimizer.getOatDir( @@ -420,7 +421,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { packagePaths++; try { - installer.moveAb(path, dexCodeInstructionSet, oatDir); + installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir); pathsSuccessful++; } catch (InstallerException e) { } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index de1c2ad44d67..4767d3a4c065 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -123,6 +123,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private static final String TAG = "PackageInstaller"; private static final boolean LOGD = false; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; + // TODO: remove outstanding sessions when installer package goes away // TODO: notify listeners in other users when package has been installed there // TODO: purge expired sessions periodically in addition to at reboot @@ -411,13 +413,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements removeStagingDirs(unclaimedStagingDirsOnVolume); } - public static boolean isStageName(String name) { - final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); - final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); - final boolean isLegacyContainer = name.startsWith("smdl2tmp"); - return isFile || isContainer || isLegacyContainer; - } - @Deprecated public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException { synchronized (mSessions) { @@ -935,6 +930,23 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new IllegalStateException("Failed to allocate session ID"); } + static boolean isStageName(String name) { + final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); + final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); + final boolean isLegacyContainer = name.startsWith("smdl2tmp"); + return isFile || isContainer || isLegacyContainer; + } + + static int tryParseSessionId(@NonNull String tmpSessionDir) + throws IllegalArgumentException { + if (!tmpSessionDir.startsWith("vmdl") || !tmpSessionDir.endsWith(".tmp")) { + throw new IllegalArgumentException("Not a temporary session directory"); + } + String sessionId = tmpSessionDir.substring("vmdl".length(), + tmpSessionDir.length() - ".tmp".length()); + return Integer.parseInt(sessionId); + } + private File getTmpSessionDir(String volumeUuid) { return Environment.getDataAppDirectory(volumeUuid); } @@ -949,7 +961,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid); return new File(sessionStagingDir, "session_" + sessionId); } - return buildTmpSessionDir(sessionId, params.volumeUuid); + final File result = buildTmpSessionDir(sessionId, params.volumeUuid); + if (DEBUG && !Objects.equals(tryParseSessionId(result.getName()), sessionId)) { + throw new RuntimeException( + "session folder format is off: " + result.getName() + " (" + sessionId + ")"); + } + return result; } static void prepareStageDir(File stageDir) throws IOException { @@ -1444,8 +1461,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private TreeMap<PackageInstallerSession, TreeSet<PackageInstallerSession>> mSessionMap; private final Comparator<PackageInstallerSession> mSessionCreationComparator = - Comparator.comparingLong((PackageInstallerSession sess) -> sess.createdMillis) - .thenComparingInt(sess -> sess.sessionId); + Comparator.comparingLong( + (PackageInstallerSession sess) -> sess != null ? sess.createdMillis : -1) + .thenComparingInt(sess -> sess != null ? sess.sessionId : -1); ParentChildSessionMap() { mSessionMap = new TreeMap<>(mSessionCreationComparator); @@ -1483,10 +1501,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements for (Map.Entry<PackageInstallerSession, TreeSet<PackageInstallerSession>> entry : mSessionMap.entrySet()) { PackageInstallerSession parentSession = entry.getKey(); - pw.print(tag + " "); - parentSession.dump(pw); - pw.println(); - pw.increaseIndent(); + if (parentSession != null) { + pw.print(tag + " "); + parentSession.dump(pw); + pw.println(); + pw.increaseIndent(); + } for (PackageInstallerSession childSession : entry.getValue()) { pw.print(tag + " Child "); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 803a2833dc3c..dbbc1638ba79 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -70,6 +70,7 @@ import android.content.pm.DataLoaderParamsParcel; import android.content.pm.FileSystemControlParcel; import android.content.pm.IDataLoader; import android.content.pm.IDataLoaderStatusListener; +import android.content.pm.IOnChecksumsReadyListener; import android.content.pm.IPackageInstallObserver2; import android.content.pm.IPackageInstallerSession; import android.content.pm.IPackageInstallerSessionFileSystemConnector; @@ -166,6 +167,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileFilter; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.NoSuchAlgorithmException; @@ -1397,6 +1399,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } @Override + public void requestChecksums(@NonNull String name, @Checksum.TypeMask int optional, + @Checksum.TypeMask int required, @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) { + final File file = new File(stageDir, name); + final String installerPackageName = getInstallSource().initiatingPackageName; + try { + mPm.requestFileChecksums(file, installerPackageName, optional, required, + trustedInstallers, onChecksumsReadyListener); + } catch (FileNotFoundException e) { + throw new ParcelableException(e); + } + } + + @Override public void removeSplit(String splitName) { if (isDataLoaderInstallation()) { throw new IllegalStateException( @@ -2402,6 +2418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { try { final List<File> fromFiles = mResolvedInheritedFiles; final File toDir = stageDir; + final String tempPackageName = toDir.getName(); if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles); if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) { @@ -2411,7 +2428,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (isLinkPossible(fromFiles, toDir)) { if (!mResolvedInstructionSets.isEmpty()) { final File oatDir = new File(toDir, "oat"); - createOatDirs(mResolvedInstructionSets, oatDir); + createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir); } // pre-create lib dirs for linking if necessary if (!mResolvedNativeLibPaths.isEmpty()) { @@ -2434,7 +2451,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { new File(libDir, archDirPath)); } } - linkFiles(fromFiles, toDir, mInheritedFilesBase); + linkFiles(tempPackageName, fromFiles, toDir, mInheritedFilesBase); } else { // TODO: this should delegate to DCS so the system process // avoids holding open FDs into containers. @@ -3529,18 +3546,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new IOException("File: " + pathStr + " outside base: " + baseStr); } - private void createOatDirs(List<String> instructionSets, File fromDir) + private void createOatDirs(String packageName, List<String> instructionSets, File fromDir) throws PackageManagerException { for (String instructionSet : instructionSets) { try { - mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet); + mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet); } catch (InstallerException e) { throw PackageManagerException.from(e); } } } - private void linkFile(String relativePath, String fromBase, String toBase) throws IOException { + private void linkFile(String packageName, String relativePath, String fromBase, String toBase) + throws IOException { try { // Try final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages(); @@ -3548,21 +3566,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { fromBase, toBase)) { return; } - mInstaller.linkFile(relativePath, fromBase, toBase); + mInstaller.linkFile(packageName, relativePath, fromBase, toBase); } catch (InstallerException | IOException e) { throw new IOException("failed linkOrCreateDir(" + relativePath + ", " + fromBase + ", " + toBase + ")", e); } } - private void linkFiles(List<File> fromFiles, File toDir, File fromDir) + private void linkFiles(String packageName, List<File> fromFiles, File toDir, File fromDir) throws IOException { for (File fromFile : fromFiles) { final String relativePath = getRelativePath(fromFile, fromDir); final String fromBase = fromDir.getAbsolutePath(); final String toBase = toDir.getAbsolutePath(); - linkFile(relativePath, fromBase, toBase); + linkFile(packageName, relativePath, fromBase, toBase); } Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir); @@ -4299,7 +4317,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { incrementalFileStorages.cleanUpAndMarkComplete(); } if (stageDir != null) { - mInstaller.rmPackageDir(stageDir.getAbsolutePath()); + final String tempPackageName = stageDir.getName(); + mInstaller.rmPackageDir(tempPackageName, stageDir.getAbsolutePath()); } } catch (InstallerException ignored) { } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 747bbfa57bd1..9f5adcb6f16f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -262,6 +262,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -554,6 +555,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final int REASON_LAST = REASON_SHARED; static final String RANDOM_DIR_PREFIX = "~~"; + static final char RANDOM_CODEPATH_PREFIX = '-'; final Handler mHandler; @@ -1261,15 +1263,50 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public void requestChecksums(@NonNull String packageName, boolean includeSplits, - @Checksum.TypeMask int optional, - @Checksum.TypeMask int required, @Nullable List trustedInstallers, + public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits, + @Checksum.TypeMask int optional, @Checksum.TypeMask int required, + @Nullable List trustedInstallers, @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) { requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers, onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(), mInjector.getBackgroundHandler()); } + /** + * Requests checksums for the APK file. + * See {@link PackageInstaller.Session#requestChecksums} for details. + */ + public void requestFileChecksums(@NonNull File file, + @NonNull String installerPackageName, @Checksum.TypeMask int optional, + @Checksum.TypeMask int required, @Nullable List trustedInstallers, + @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) + throws FileNotFoundException { + if (!file.exists()) { + throw new FileNotFoundException(file.getAbsolutePath()); + } + if (TextUtils.isEmpty(installerPackageName)) { + throw new FileNotFoundException(file.getAbsolutePath()); + } + + final Executor executor = mInjector.getBackgroundExecutor(); + final Handler handler = mInjector.getBackgroundHandler(); + final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates( + trustedInstallers) : null; + + final List<Pair<String, File>> filesToChecksum = new ArrayList<>(1); + filesToChecksum.add(Pair.create(null, file)); + + executor.execute(() -> { + ApkChecksums.Injector injector = new ApkChecksums.Injector( + () -> mContext, + () -> handler, + () -> mInjector.getIncrementalManager(), + () -> mPmInternal); + ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName, + trustedCerts, onChecksumsReadyListener, injector); + }); + } + private void requestChecksumsInternal(@NonNull String packageName, boolean includeSplits, @Checksum.TypeMask int optional, @Checksum.TypeMask int required, @Nullable List trustedInstallers, @@ -1493,8 +1530,8 @@ public class PackageManagerService extends IPackageManager.Stub } PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest, - Build.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, Build.VERSION.SDK_INT, - Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED); + PackagePartitions.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, + Build.VERSION.SDK_INT, Build.VERSION.INCREMENTAL, SNAPSHOT_ENABLED); t.traceEnd(); // "create package manager" final CompatChange.ChangeListener selinuxChangeListener = packageName -> { @@ -1907,8 +1944,8 @@ public class PackageManagerService extends IPackageManager.Stub mIsUpgrade = !buildFingerprint.equals(ver.fingerprint); if (mIsUpgrade) { - PackageManagerServiceUtils.logCriticalInfo(Log.INFO, - "Upgrading from " + ver.fingerprint + " to " + Build.FINGERPRINT); + PackageManagerServiceUtils.logCriticalInfo(Log.INFO, "Upgrading from " + + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT); } // when upgrading from pre-M, promote system app permissions from install to runtime @@ -2006,7 +2043,8 @@ public class PackageManagerService extends IPackageManager.Stub // this situation. if (mIsUpgrade) { Slog.i(TAG, "Build fingerprint changed from " + ver.fingerprint + " to " - + Build.FINGERPRINT + "; regranting permissions for internal storage"); + + PackagePartitions.FINGERPRINT + + "; regranting permissions for internal storage"); } mPermissionManager.onStorageVolumeMounted( StorageManager.UUID_PRIVATE_INTERNAL, mIsUpgrade); @@ -2040,7 +2078,7 @@ public class PackageManagerService extends IPackageManager.Stub | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES); } } - ver.fingerprint = Build.FINGERPRINT; + ver.fingerprint = PackagePartitions.FINGERPRINT; } // Legacy existing (installed before Q) non-system apps to hide @@ -2538,8 +2576,8 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.canViewInstantApps(callingUid, userId); } - private PackageInfo generatePackageInfo(@NonNull PackageStateInternal ps, int flags, - int userId) { + private PackageInfo generatePackageInfo(@NonNull PackageStateInternal ps, + @PackageManager.PackageInfoFlags long flags, int userId) { return mComputer.generatePackageInfo(ps, flags, userId); } @@ -2578,13 +2616,14 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public PackageInfo getPackageInfo(String packageName, int flags, int userId) { + public PackageInfo getPackageInfo(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId) { return mComputer.getPackageInfo(packageName, flags, userId); } @Override public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage, - int flags, int userId) { + @PackageManager.PackageInfoFlags long flags, int userId) { return mComputer.getPackageInfoInternal(versionedPackage.getPackageName(), versionedPackage.getLongVersionCode(), flags, Binder.getCallingUid(), userId); } @@ -2621,7 +2660,7 @@ public class PackageManagerService extends IPackageManager.Stub } private boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid, - int userId, int flags) { + int userId, @PackageManager.ComponentInfoFlags long flags) { return mComputer.filterSharedLibPackage(ps, uid, userId, flags); } @@ -2636,16 +2675,19 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public int getPackageUid(@NonNull String packageName, int flags, @UserIdInt int userId) { + public int getPackageUid(@NonNull String packageName, + @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) { return mComputer.getPackageUid(packageName, flags, userId); } - private int getPackageUidInternal(String packageName, int flags, int userId, int callingUid) { + private int getPackageUidInternal(String packageName, + @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) { return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid); } @Override - public int[] getPackageGids(String packageName, int flags, int userId) { + public int[] getPackageGids(String packageName, @PackageManager.PackageInfoFlags long flags, + int userId) { return mComputer.getPackageGids(packageName, flags, userId); } @@ -2658,14 +2700,15 @@ public class PackageManagerService extends IPackageManager.Stub .getPermissionGroupInfo(groupName, flags); } - private ApplicationInfo generateApplicationInfoFromSettings(String packageName, int flags, - int filterCallingUid, int userId) { - return mComputer.generateApplicationInfoFromSettings(packageName, flags, - filterCallingUid, userId); + private ApplicationInfo generateApplicationInfoFromSettings(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) { + return mComputer.generateApplicationInfoFromSettings(packageName, flags, filterCallingUid, + userId); } @Override - public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) { + public ApplicationInfo getApplicationInfo(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int userId) { return mComputer.getApplicationInfo(packageName, flags, userId); } @@ -2675,7 +2718,8 @@ public class PackageManagerService extends IPackageManager.Stub * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - private ApplicationInfo getApplicationInfoInternal(String packageName, int flags, + private ApplicationInfo getApplicationInfoInternal(String packageName, + @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) { return mComputer.getApplicationInfoInternal(packageName, flags, filterCallingUid, userId); @@ -2910,21 +2954,21 @@ public class PackageManagerService extends IPackageManager.Stub /** * Update given flags when being used to request {@link PackageInfo}. */ - private int updateFlagsForPackage(int flags, int userId) { + private long updateFlagsForPackage(long flags, int userId) { return mComputer.updateFlagsForPackage(flags, userId); } /** * Update given flags when being used to request {@link ApplicationInfo}. */ - private int updateFlagsForApplication(int flags, int userId) { + private long updateFlagsForApplication(long flags, int userId) { return mComputer.updateFlagsForApplication(flags, userId); } /** * Update given flags when being used to request {@link ComponentInfo}. */ - private int updateFlagsForComponent(int flags, int userId) { + private long updateFlagsForComponent(long flags, int userId) { return mComputer.updateFlagsForComponent(flags, userId); } @@ -2940,7 +2984,7 @@ public class PackageManagerService extends IPackageManager.Stub * action and a {@code android.intent.category.BROWSABLE} category</li> * </ul> */ - int updateFlagsForResolve(int flags, int userId, int callingUid, + long updateFlagsForResolve(long flags, int userId, int callingUid, boolean wantInstantApps, boolean isImplicitImageCaptureIntentAndNotSetByDpc) { return mComputer.updateFlagsForResolve(flags, userId, callingUid, wantInstantApps, isImplicitImageCaptureIntentAndNotSetByDpc); @@ -2952,7 +2996,8 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { + public ActivityInfo getActivityInfo(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int userId) { return mComputer.getActivityInfo(component, flags, userId); } @@ -2962,8 +3007,8 @@ public class PackageManagerService extends IPackageManager.Stub * to clearing. Because it can only be provided by trusted code, its value can be * trusted and will be used as-is; unlike userId which will be validated by this method. */ - private ActivityInfo getActivityInfoInternal(ComponentName component, int flags, - int filterCallingUid, int userId) { + private ActivityInfo getActivityInfoInternal(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int filterCallingUid, int userId) { return mComputer.getActivityInfoInternal(component, flags, filterCallingUid, userId); } @@ -2976,40 +3021,43 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { + public ActivityInfo getReceiverInfo(ComponentName component, + @PackageManager.ComponentInfoFlags long flags, int userId) { return mComputer.getReceiverInfo(component, flags, userId); } @Override public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(String packageName, - int flags, int userId) { + @PackageManager.PackageInfoFlags long flags, int userId) { return mComputer.getSharedLibraries(packageName, flags, userId); } @Nullable @Override public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( - @NonNull String packageName, int flags, @UserIdInt int userId) { + @NonNull String packageName, @PackageManager.PackageInfoFlags long flags, + @NonNull int userId) { return mComputer.getDeclaredSharedLibraries(packageName, flags, userId); } @Nullable List<VersionedPackage> getPackagesUsingSharedLibrary( - SharedLibraryInfo libInfo, int flags, int callingUid, int userId) { + SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlags long flags, int callingUid, + int userId) { return mComputer.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); } @Nullable @Override - public ServiceInfo getServiceInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ServiceInfo getServiceInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { return mComputer.getServiceInfo(component, flags, userId); } @Nullable @Override - public ProviderInfo getProviderInfo(@NonNull ComponentName component, int flags, - @UserIdInt int userId) { + public ProviderInfo getProviderInfo(@NonNull ComponentName component, + @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) { return mComputer.getProviderInfo(component, flags, userId); } @@ -3298,7 +3346,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, - int flags, int userId) { + @PackageManager.ResolveInfoFlags long flags, int userId) { return mResolveIntentHelper.resolveIntentInternal(intent, resolvedType, flags, 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid()); } @@ -3343,7 +3391,7 @@ public class PackageManagerService extends IPackageManager.Stub */ @GuardedBy("mLock") boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId, - String resolvedType, int flags) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags) { return mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, flags); } @@ -3351,10 +3399,10 @@ public class PackageManagerService extends IPackageManager.Stub @GuardedBy("mLock") ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, - int flags, List<ResolveInfo> query, boolean debug, int userId) { + @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean debug, + int userId) { return mComputer.findPersistentPreferredActivityLP(intent, - resolvedType, - flags, query, debug, userId); + resolvedType, flags, query, debug, userId); } // findPreferredActivityBody returns two items: a "things changed" flag and a @@ -3365,7 +3413,7 @@ public class PackageManagerService extends IPackageManager.Stub } FindPreferredActivityBodyResult findPreferredActivityInternal( - Intent intent, String resolvedType, int flags, + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) { return mComputer.findPreferredActivityInternal( @@ -3395,7 +3443,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivities(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities"); @@ -3415,21 +3463,23 @@ public class PackageManagerService extends IPackageManager.Stub } @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { return mComputer.queryIntentActivitiesInternal(intent, resolvedType, flags, userId); } @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, - String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, - int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + @PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId, + boolean resolveForStart, boolean allowDynamicSplits) { return mComputer.queryIntentActivitiesInternal(intent, resolvedType, flags, privateResolveFlags, filterCallingUid, userId, resolveForStart, allowDynamicSplits); } private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent, - String resolvedType, int flags, int sourceUserId, int parentUserId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId, + int parentUserId) { return mComputer.getCrossProfileDomainPreferredLpr(intent, resolvedType, flags, sourceUserId, parentUserId); } @@ -3456,20 +3506,21 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal( caller, specifics, specificTypes, intent, resolvedType, flags, userId)); } @Override public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(intent, resolvedType, flags, userId, Binder.getCallingUid())); } @Override - public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) { + public ResolveInfo resolveService(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlags long flags, int userId) { final int callingUid = Binder.getCallingUid(); return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId, callingUid); @@ -3477,15 +3528,15 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @NonNull ParceledListSlice<ResolveInfo> queryIntentServices(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { final int callingUid = Binder.getCallingUid(); return new ParceledListSlice<>(queryIntentServicesInternal( intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/)); } @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent, - String resolvedType, int flags, int userId, int callingUid, - boolean includeInstantApps) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId, + int callingUid, boolean includeInstantApps) { return mComputer.queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid, includeInstantApps); @@ -3493,24 +3544,27 @@ public class PackageManagerService extends IPackageManager.Stub @Override public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal( intent, resolvedType, flags, userId)); } @Override - public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { + public ParceledListSlice<PackageInfo> getInstalledPackages( + @PackageManager.PackageInfoFlags long flags, int userId) { return mComputer.getInstalledPackages(flags, userId); } @Override public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions( - @NonNull String[] permissions, int flags, @UserIdInt int userId) { + @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags, + @UserIdInt int userId) { return mComputer.getPackagesHoldingPermissions(permissions, flags, userId); } @Override - public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId) { + public ParceledListSlice<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlags long flags, int userId) { final int callingUid = Binder.getCallingUid(); return new ParceledListSlice<>( mComputer.getInstalledApplications(flags, userId, callingUid)); @@ -3614,7 +3668,8 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public ProviderInfo resolveContentProvider(String name, int flags, int userId) { + public ProviderInfo resolveContentProvider(String name, + @PackageManager.ResolveInfoFlags long flags, int userId) { return mComputer.resolveContentProvider(name, flags, userId, Binder.getCallingUid()); } @@ -3626,7 +3681,7 @@ public class PackageManagerService extends IPackageManager.Stub @NonNull @Override public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, - int uid, int flags, @Nullable String metaDataKey) { + int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) { return mComputer.queryContentProviders(processName, uid, flags, metaDataKey); } @@ -7471,8 +7526,8 @@ public class PackageManagerService extends IPackageManager.Stub private class PackageManagerInternalImpl extends PackageManagerInternal { @Override - public List<ApplicationInfo> getInstalledApplications(int flags, int userId, - int callingUid) { + public List<ApplicationInfo> getInstalledApplications( + @PackageManager.ApplicationInfoFlags long flags, int userId, int callingUid) { return PackageManagerService.this.mComputer.getInstalledApplications(flags, userId, callingUid); } @@ -7667,7 +7722,8 @@ public class PackageManagerService extends IPackageManager.Stub @Override public PackageInfo getPackageInfo( - String packageName, int flags, int filterCallingUid, int userId) { + String packageName, @PackageManager.PackageInfoFlags long flags, + int filterCallingUid, int userId) { return PackageManagerService.this.mComputer .getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST, flags, filterCallingUid, userId); @@ -7795,28 +7851,32 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public int getPackageUid(String packageName, int flags, int userId) { + public int getPackageUid(String packageName, @PackageManager.PackageInfoFlags long flags, + int userId) { return PackageManagerService.this .getPackageUidInternal(packageName, flags, userId, Process.SYSTEM_UID); } @Override public ApplicationInfo getApplicationInfo( - String packageName, int flags, int filterCallingUid, int userId) { + String packageName, @PackageManager.ApplicationInfoFlags long flags, + int filterCallingUid, int userId) { return PackageManagerService.this .getApplicationInfoInternal(packageName, flags, filterCallingUid, userId); } @Override public ActivityInfo getActivityInfo( - ComponentName component, int flags, int filterCallingUid, int userId) { + ComponentName component, @PackageManager.ComponentInfoFlags long flags, + int filterCallingUid, int userId) { return PackageManagerService.this .getActivityInfoInternal(component, flags, filterCallingUid, userId); } @Override public List<ResolveInfo> queryIntentActivities( - Intent intent, String resolvedType, int flags, int filterCallingUid, int userId) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int filterCallingUid, int userId) { return PackageManagerService.this .queryIntentActivitiesInternal(intent, resolvedType, flags, 0, filterCallingUid, userId, false /*resolveForStart*/, true /*allowDynamicSplits*/); @@ -7824,14 +7884,16 @@ public class PackageManagerService extends IPackageManager.Stub @Override public List<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, int flags, int filterCallingUid, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int filterCallingUid, int userId) { return PackageManagerService.this.mResolveIntentHelper.queryIntentReceiversInternal( intent, resolvedType, flags, userId, filterCallingUid); } @Override public List<ResolveInfo> queryIntentServices( - Intent intent, int flags, int callingUid, int userId) { + Intent intent, @PackageManager.ResolveInfoFlags long flags, int callingUid, + int userId) { final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver()); return PackageManagerService.this .queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid, @@ -7897,7 +7959,7 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public boolean isEnabledAndMatches(ParsedMainComponent component, int flags, int userId) { + public boolean isEnabledAndMatches(ParsedMainComponent component, long flags, int userId) { return PackageStateUtils.isEnabledAndMatches( getPackageStateInternal(component.getPackageName()), component, flags, userId); } @@ -8099,8 +8161,9 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, - int flags, int privateResolveFlags, int userId, boolean resolveForStart, - int filterCallingUid) { + @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId, + boolean resolveForStart, int filterCallingUid) { return mResolveIntentHelper.resolveIntentInternal( intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart, filterCallingUid); @@ -8108,14 +8171,14 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ResolveInfo resolveService(Intent intent, String resolvedType, - int flags, int userId, int callingUid) { + @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) { return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId, callingUid); } @Override - public ProviderInfo resolveContentProvider(String name, int flags, int userId, - int callingUid) { + public ProviderInfo resolveContentProvider(String name, + @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) { return PackageManagerService.this.mComputer .resolveContentProvider(name, flags, userId,callingUid); } @@ -8999,9 +9062,12 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId, int userId) { - mutateInstalledPackageSetting(packageName, Binder.getCallingUid(), userId, pkgSetting -> { - pkgSetting.setSplashScreenTheme(userId, themeId); - }); + final int callingUid = Binder.getCallingUid(); + enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "setSplashScreenTheme"); + enforceOwnerRights(packageName, callingUid); + mutateInstalledPackageSetting(packageName, callingUid, userId, + pkgSetting -> pkgSetting.setSplashScreenTheme(userId, themeId)); } @Override @@ -9376,7 +9442,8 @@ public class PackageManagerService extends IPackageManager.Stub mResolveActivity.processName = "system:ui"; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER; - mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS + | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 6d031dd1c20d..3b643b5c982f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -25,6 +25,7 @@ import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION; import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING; import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED; +import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX; import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX; import static com.android.server.pm.PackageManagerService.STUB_SUFFIX; import static com.android.server.pm.PackageManagerService.TAG; @@ -41,6 +42,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; +import android.content.pm.PackagePartitions; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; @@ -123,6 +125,8 @@ import java.util.zip.GZIPInputStream; public class PackageManagerServiceUtils { private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 3 * 1000 * 1000; // 3MB + private static final boolean DEBUG = Build.IS_DEBUGGABLE; + public final static Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG = pkgSetting -> pkgSetting.getPkg() == null; @@ -1075,7 +1079,7 @@ public class PackageManagerServiceUtils { public static boolean hasAnyDomainApproval( @NonNull DomainVerificationManagerInternal manager, @NonNull PackageStateInternal pkgSetting, @NonNull Intent intent, - @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { + @PackageManager.ResolveInfoFlags long resolveInfoFlags, @UserIdInt int userId) { return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; } @@ -1122,13 +1126,28 @@ public class PackageManagerServiceUtils { File firstLevelDir; do { random.nextBytes(bytes); - String dirName = RANDOM_DIR_PREFIX + String firstLevelDirName = RANDOM_DIR_PREFIX + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP); - firstLevelDir = new File(targetDir, dirName); + firstLevelDir = new File(targetDir, firstLevelDirName); } while (firstLevelDir.exists()); + random.nextBytes(bytes); - String suffix = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP); - return new File(firstLevelDir, packageName + "-" + suffix); + String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes, + Base64.URL_SAFE | Base64.NO_WRAP); + final File result = new File(firstLevelDir, dirName); + if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) { + throw new RuntimeException( + "codepath is off: " + result.getName() + " (" + packageName + ")"); + } + return result; + } + + static String tryParsePackageName(@NonNull String codePath) throws IllegalArgumentException { + int packageNameEnds = codePath.indexOf(RANDOM_CODEPATH_PREFIX); + if (packageNameEnds == -1) { + throw new IllegalArgumentException("Not a valid package folder name"); + } + return codePath.substring(0, packageNameEnds); } /** @@ -1215,7 +1234,7 @@ public class PackageManagerServiceUtils { // identify cached items. In particular, changing the value of certain // feature flags should cause us to invalidate any caches. final String cacheName = FORCE_PACKAGE_PARSED_CACHE_ENABLED ? "debug" - : SystemProperties.digestOf("ro.build.fingerprint"); + : PackagePartitions.FINGERPRINT; // Reconcile cache directories, keeping only what we'd actually use. for (File cacheDir : FileUtils.listFilesOrEmpty(cacheBaseDir)) { diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index cb97e2ab8e6f..8c91b16f2d83 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -75,8 +75,8 @@ final class PreferredActivityHelper { } private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, - int flags, List<ResolveInfo> query, boolean always, boolean removeMatches, - boolean debug, int userId) { + @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean always, + boolean removeMatches, boolean debug, int userId) { return findPreferredActivityNotLocked( intent, resolvedType, flags, query, always, removeMatches, debug, userId, UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID); @@ -85,8 +85,9 @@ final class PreferredActivityHelper { // TODO: handle preferred activities missing while user has amnesia /** <b>must not hold {@link PackageManagerService.mLock}</b> */ public ResolveInfo findPreferredActivityNotLocked( - Intent intent, String resolvedType, int flags, List<ResolveInfo> query, boolean always, - boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug, + int userId, boolean queryMayBeFiltered) { if (Thread.holdsLock(mPm.mLock)) { Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding mLock", new Throwable()); @@ -675,7 +676,7 @@ final class PreferredActivityHelper { final int callingUid = Binder.getCallingUid(); intent = PackageManagerServiceUtils.updateIntentForResolve(intent); final String resolvedType = intent.resolveTypeIfNeeded(mPm.mContext.getContentResolver()); - final int flags = mPm.updateFlagsForResolve( + final long flags = mPm.updateFlagsForResolve( 0, userId, callingUid, false /*includeInstantApps*/, mPm.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType, 0)); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3a2ac1c9b6df..48b893bda546 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -94,9 +94,10 @@ final class RemovePackageHelper { } } - mInstaller.rmPackageDir(codePath.getAbsolutePath()); + final String packageName = codePath.getName(); + mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath()); if (needRemoveParent) { - mInstaller.rmPackageDir(codePathParent.getAbsolutePath()); + mInstaller.rmPackageDir(packageName, codePathParent.getAbsolutePath()); removeCachedResult(codePathParent); } } catch (Installer.InstallerException e) { diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index 70855a9bf430..0ee1f894573f 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -74,8 +74,9 @@ final class ResolveIntentHelper { * However, if {@code resolveForStart} is {@code true}, all instant apps are visible * since we need to allow the system to start any installed application. */ - public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags, - @PackageManagerInternal.PrivateResolveFlags int privateResolveFlags, int userId, + public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart, int filterCallingUid) { try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent"); @@ -114,8 +115,9 @@ final class ResolveIntentHelper { } private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, - int flags, int privateResolveFlags, List<ResolveInfo> query, int userId, - boolean queryMayBeFiltered) { + @PackageManager.ResolveInfoFlags long flags, + @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, + List<ResolveInfo> query, int userId, boolean queryMayBeFiltered) { if (query != null) { final int n = query.size(); if (n == 1) { @@ -276,7 +278,8 @@ final class ResolveIntentHelper { // In this method, we have to know the actual calling UID, but in some cases Binder's // call identity is removed, so the UID has to be passed in explicitly. public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent, - String resolvedType, int flags, int userId, int filterCallingUid) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId, + int filterCallingUid) { if (!mPm.mUserManager.exists(userId)) return Collections.emptyList(); mPm.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/, false /*checkShell*/, "query intent receivers"); @@ -370,8 +373,8 @@ final class ResolveIntentHelper { } - public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags, - int userId, int callingUid) { + public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, + @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) { if (!mPm.mUserManager.exists(userId)) return null; flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, false /* isImplicitImageCaptureIntentAndNotSetByDpc */); @@ -388,7 +391,8 @@ final class ResolveIntentHelper { } public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal( - Intent intent, String resolvedType, int flags, int userId) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags, + int userId) { if (!mPm.mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); final String instantAppPkgName = mPm.getInstantAppPackageName(callingUid); @@ -529,7 +533,7 @@ final class ResolveIntentHelper { public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller, Intent[] specifics, String[] specificTypes, Intent intent, - String resolvedType, int flags, int userId) { + String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) { if (!mPm.mUserManager.exists(userId)) return Collections.emptyList(); final int callingUid = Binder.getCallingUid(); flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/, diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java index 6cc94ce1f657..9b08ef9b3525 100644 --- a/services/core/java/com/android/server/pm/ScanPackageHelper.java +++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java @@ -894,14 +894,15 @@ final class ScanPackageHelper { * Returns if forced apk verification can be skipped for the whole package, including splits. */ private boolean canSkipForcedPackageVerification(AndroidPackage pkg) { - if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) { + final String packageName = pkg.getPackageName(); + if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) { return false; } // TODO: Allow base and splits to be verified individually. String[] splitCodePaths = pkg.getSplitCodePaths(); if (!ArrayUtils.isEmpty(splitCodePaths)) { for (int i = 0; i < splitCodePaths.length; i++) { - if (!canSkipForcedApkVerification(splitCodePaths[i])) { + if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) { return false; } } @@ -914,7 +915,7 @@ final class ScanPackageHelper { * whether the apk contains signed root hash. Note that the signer's certificate still needs to * match one in a trusted source, and should be done separately. */ - private boolean canSkipForcedApkVerification(String apkPath) { + private boolean canSkipForcedApkVerification(String packageName, String apkPath) { if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) { return VerityUtils.hasFsverity(apkPath); } @@ -926,7 +927,8 @@ final class ScanPackageHelper { } synchronized (mPm.mInstallLock) { // Returns whether the observed root hash matches what kernel has. - mPm.mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved); + mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath, + rootHashObserved); return true; } } catch (Installer.InstallerException | IOException | DigestException diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 4a410473e10a..6a163b2fdacb 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -42,6 +42,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackagePartitions; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.Signature; @@ -153,7 +154,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; @@ -437,7 +437,7 @@ public final class Settings implements Watchable, Snappable { public void forceCurrent() { sdkVersion = Build.VERSION.SDK_INT; databaseVersion = CURRENT_DATABASE_VERSION; - fingerprint = Build.FINGERPRINT; + fingerprint = PackagePartitions.FINGERPRINT; } } @@ -4149,7 +4149,7 @@ public final class Settings implements Watchable, Snappable { return getDisabledSystemPkgLPr(enabledPackageSetting.getPackageName()); } - boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) { + boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, long flags, int userId) { final PackageSetting ps = mPackages.get(componentInfo.packageName); if (ps == null) return false; @@ -4159,7 +4159,7 @@ public final class Settings implements Watchable, Snappable { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, - int flags, int userId) { + long flags, int userId) { final PackageSetting ps = mPackages.get(component.getPackageName()); if (ps == null) return false; @@ -5357,7 +5357,7 @@ public final class Settings implements Watchable, Snappable { } private String getExtendedFingerprint(long version) { - return Build.FINGERPRINT + "?pc_version=" + version; + return PackagePartitions.FINGERPRINT + "?pc_version=" + version; } public void writeStateForUserAsyncLPr(int userId) { diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 6b88081380a7..1433abd1b6c9 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -29,10 +29,10 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.app.ResourcesManager; import android.content.IIntentReceiver; import android.content.pm.PackageManager; +import android.content.pm.PackagePartitions; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.content.pm.parsing.ParsingPackageUtils; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.UserHandle; @@ -157,7 +157,7 @@ public final class StorageEventHelper extends StorageEventListener { Slog.w(TAG, "Failed to scan " + ps.getPath() + ": " + e.getMessage()); } - if (!Build.FINGERPRINT.equals(ver.fingerprint)) { + if (!PackagePartitions.FINGERPRINT.equals(ver.fingerprint)) { appDataHelper.clearAppDataLIF( ps.getPkg(), UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY @@ -195,10 +195,10 @@ public final class StorageEventHelper extends StorageEventListener { } synchronized (mPm.mLock) { - final boolean isUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint); + final boolean isUpgrade = !PackagePartitions.FINGERPRINT.equals(ver.fingerprint); if (isUpgrade) { logCriticalInfo(Log.INFO, "Build fingerprint changed from " + ver.fingerprint - + " to " + Build.FINGERPRINT + "; regranting permissions for " + + " to " + PackagePartitions.FINGERPRINT + "; regranting permissions for " + volumeUuid); } mPm.mPermissionManager.onStorageVolumeMounted(volumeUuid, isUpgrade); diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index d54acb7d6bd6..a5a8d5ce9bd4 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -24,20 +24,6 @@ "name": "CtsMatchFlagTestCases" }, { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - }, - { "name": "FrameworksMockingServicesTests", "options": [ { @@ -46,45 +32,6 @@ ] }, { - "name": "FrameworksServicesTests", - "file_patterns": ["(/|^)ShortcutService\\.java"], - "options": [ - { - "include-filter": "com.android.server.pm.ShortcutManagerTest1" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest2" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest3" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest4" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest5" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest6" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest7" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest8" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest9" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest10" - }, - { - "include-filter": "com.android.server.pm.ShortcutManagerTest11" - } - ] - }, - { "name": "CtsShortcutHostTestCases", "file_patterns": ["(/|^)ShortcutService\\.java"] }, @@ -188,47 +135,6 @@ }, { "name": "PackageManagerServiceHostTests" - }, - { - "name": "FrameworksServicesTests", - "options": [ - { - "install-arg": "-t" - }, - { - "include-filter": "com.android.server.pm.UserDataPreparerTest" - }, - { - "include-filter": "com.android.server.pm.UserLifecycleStressTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerServiceCreateProfileTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerServiceIdRecyclingTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerServiceTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerServiceUserInfoTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerServiceUserTypeTest" - }, - { - "include-filter": "com.android.server.pm.UserManagerTest" - }, - { - "include-filter": "com.android.server.pm.UserRestrictionsUtilsTest" - }, - { - "include-filter": "com.android.server.pm.UserSystemPackageInstallerTest" - }, - { - "include-filter": "com.android.server.pm.parsing.SystemPartitionParseTest" - } - ] } ], "imports": [ diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 40d884598ceb..bc4c8b058f7e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -46,6 +46,7 @@ import android.content.IntentSender; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; +import android.content.pm.PackagePartitions; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; @@ -2396,6 +2397,25 @@ public class UserManagerService extends IUserManager.Stub { return count; } + /** + * Returns whether more users of the given type can be added (based on how many users of that + * type already exist). + */ + @Override + public boolean canAddMoreUsersOfType(String userType) { + checkManageOrCreateUsersPermission("check if more users can be added."); + final UserTypeDetails userTypeDetails = mUserTypes.get(userType); + return userTypeDetails != null && canAddMoreUsersOfType(userTypeDetails); + } + + /** Returns whether the creation of users of the given user type is enabled on this device. */ + @Override + public boolean isUserTypeEnabled(String userType) { + checkManageOrCreateUsersPermission("check if user type is enabled."); + final UserTypeDetails userTypeDetails = mUserTypes.get(userType); + return userTypeDetails != null && userTypeDetails.isEnabled(); + } + @Override public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) { return canAddMoreProfilesToUser(UserManager.USER_TYPE_PROFILE_MANAGED, userId, @@ -3716,7 +3736,7 @@ public class UserManagerService extends IUserManager.Stub { userInfo.creationTime = getCreationTime(); userInfo.partial = true; userInfo.preCreated = preCreate; - userInfo.lastLoggedInFingerprint = Build.FINGERPRINT; + userInfo.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT; if (userTypeDetails.hasBadge() && parentId != UserHandle.USER_NULL) { userInfo.profileBadge = getFreeProfileBadgeLU(parentId, userType); } @@ -4847,7 +4867,8 @@ public class UserManagerService extends IUserManager.Stub { t.traceBegin("onBeforeStartUser-" + userId); final int userSerial = userInfo.serialNumber; // Migrate only if build fingerprints mismatch - boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint); + boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals( + userInfo.lastLoggedInFingerprint); t.traceBegin("prepareUserData"); mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE); t.traceEnd(); @@ -4877,7 +4898,8 @@ public class UserManagerService extends IUserManager.Stub { } final int userSerial = userInfo.serialNumber; // Migrate only if build fingerprints mismatch - boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint); + boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals( + userInfo.lastLoggedInFingerprint); final TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("prepareUserData-" + userId); @@ -4921,7 +4943,7 @@ public class UserManagerService extends IUserManager.Stub { if (now > EPOCH_PLUS_30_YEARS) { userData.info.lastLoggedInTime = now; } - userData.info.lastLoggedInFingerprint = Build.FINGERPRINT; + userData.info.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT; scheduleWriteUser(userData); } diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 7f4237410ea9..328a55f72976 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -142,7 +142,9 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_MICROPHONE_TOGGLE, UserManager.DISALLOW_CAMERA_TOGGLE, UserManager.DISALLOW_CHANGE_WIFI_STATE, - UserManager.DISALLOW_WIFI_TETHERING + UserManager.DISALLOW_WIFI_TETHERING, + UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI + }); public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet( diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java index 4dbc5d5db7fd..6d681399460e 100644 --- a/services/core/java/com/android/server/pm/VerificationParams.java +++ b/services/core/java/com/android/server/pm/VerificationParams.java @@ -399,6 +399,8 @@ final class VerificationParams extends HandlerParams { verification.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, mDataLoaderType); + verification.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); + populateInstallerExtras(verification); final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite, diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 58204891293c..5371454db43c 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -1043,10 +1043,12 @@ public class DexManager { public long deleteOptimizedFiles(ArtPackageInfo packageInfo) { long freedBytes = 0; boolean hadErrors = false; + final String packageName = packageInfo.getPackageName(); for (String codePath : packageInfo.getCodePaths()) { for (String isa : packageInfo.getInstructionSets()) { try { - freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir()); + freedBytes += mInstaller.deleteOdex(packageName, codePath, isa, + packageInfo.getOatDir()); } catch (InstallerException e) { Log.e(TAG, "Failed deleting oat files for " + codePath, e); hadErrors = true; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 0ab1d36582b1..bcb5e722a969 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -84,8 +84,8 @@ public class PackageInfoUtils { */ @Nullable public static PackageInfo generate(AndroidPackage pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, - Set<String> grantedPermissions, PackageUserState state, int userId, + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable PackageStateInternal pkgSetting) { return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId, null, pkgSetting); @@ -105,8 +105,8 @@ public class PackageInfoUtils { * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage. */ private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids, - @PackageManager.PackageInfoFlags int flags, long firstInstallTime, long lastUpdateTime, - Set<String> grantedPermissions, PackageUserState state, int userId, + @PackageManager.PackageInfoFlags long flags, long firstInstallTime, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo, @Nullable PackageStateInternal pkgSetting) { ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId, pkgSetting); @@ -209,7 +209,7 @@ public class PackageInfoUtils { */ @Nullable public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg, - @PackageManager.ApplicationInfoFlags int flags, @NonNull PackageUserState state, + @PackageManager.ApplicationInfoFlags long flags, @NonNull PackageUserState state, int userId, @Nullable PackageStateInternal pkgSetting) { if (pkg == null) { return null; @@ -255,7 +255,7 @@ public class PackageInfoUtils { */ @Nullable public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId, + @PackageManager.ComponentInfoFlags long flags, PackageUserState state, int userId, @Nullable PackageStateInternal pkgSetting) { return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting); } @@ -265,7 +265,7 @@ public class PackageInfoUtils { */ @Nullable private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlags int flags, PackageUserState state, + @PackageManager.ComponentInfoFlags long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId, @Nullable PackageStateInternal pkgSetting) { if (a == null) return null; @@ -291,7 +291,7 @@ public class PackageInfoUtils { */ @Nullable public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s, - @PackageManager.ComponentInfoFlags int flags, PackageUserState state, int userId, + @PackageManager.ComponentInfoFlags long flags, PackageUserState state, int userId, @Nullable PackageStateInternal pkgSetting) { return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting); } @@ -301,7 +301,7 @@ public class PackageInfoUtils { */ @Nullable private static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s, - @PackageManager.ComponentInfoFlags int flags, PackageUserState state, + @PackageManager.ComponentInfoFlags long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId, @Nullable PackageStateInternal pkgSetting) { if (s == null) return null; @@ -326,7 +326,7 @@ public class PackageInfoUtils { */ @Nullable public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlags int flags, PackageUserState state, + @PackageManager.ComponentInfoFlags long flags, PackageUserState state, @NonNull ApplicationInfo applicationInfo, int userId, @Nullable PackageStateInternal pkgSetting) { if (p == null) return null; @@ -353,7 +353,7 @@ public class PackageInfoUtils { */ @Nullable public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i, - AndroidPackage pkg, @PackageManager.ComponentInfoFlags int flags, int userId, + AndroidPackage pkg, @PackageManager.ComponentInfoFlags long flags, int userId, @Nullable PackageStateInternal pkgSetting) { if (i == null) return null; @@ -381,7 +381,7 @@ public class PackageInfoUtils { // PackageStateInternal os that checkUseInstalledOrHidden filter can apply @Nullable public static PermissionInfo generatePermissionInfo(ParsedPermission p, - @PackageManager.ComponentInfoFlags int flags) { + @PackageManager.ComponentInfoFlags long flags) { // TODO(b/135203078): Remove null checks and make all usages @NonNull if (p == null) return null; @@ -391,7 +391,7 @@ public class PackageInfoUtils { @Nullable public static PermissionGroupInfo generatePermissionGroupInfo(ParsedPermissionGroup pg, - @PackageManager.ComponentInfoFlags int flags) { + @PackageManager.ComponentInfoFlags long flags) { if (pg == null) return null; // For now, permissions don't have state-adjustable fields; return directly @@ -400,7 +400,7 @@ public class PackageInfoUtils { @Nullable public static ArrayMap<String, ProcessInfo> generateProcessInfo( - Map<String, ParsedProcess> procs, @PackageManager.ComponentInfoFlags int flags) { + Map<String, ParsedProcess> procs, @PackageManager.ComponentInfoFlags long flags) { if (procs == null) { return null; } @@ -423,7 +423,7 @@ public class PackageInfoUtils { */ public static boolean checkUseInstalledOrHidden(AndroidPackage pkg, PackageStateInternal pkgSetting, PackageUserState state, - @PackageManager.PackageInfoFlags int flags) { + @PackageManager.PackageInfoFlags long flags) { // Returns false if the package is hidden system app until installed. if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0 && !state.isInstalled() @@ -628,7 +628,7 @@ public class PackageInfoUtils { */ @Nullable public ApplicationInfo generate(AndroidPackage pkg, - @PackageManager.ApplicationInfoFlags int flags, PackageUserState state, int userId, + @PackageManager.ApplicationInfoFlags long flags, PackageUserState state, int userId, @Nullable PackageStateInternal pkgSetting) { ApplicationInfo appInfo = mCache.get(pkg.getPackageName()); if (appInfo != null) { diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 61fd5ee01658..32b1e5dbd3db 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -256,7 +256,7 @@ public class AndroidPackageUtils { * Returns false iff the provided flags include the {@link PackageManager#MATCH_SYSTEM_ONLY} * flag and the provided package is not a system package. Otherwise returns {@code true}. */ - public static boolean isMatchForSystemOnly(AndroidPackage pkg, int flags) { + public static boolean isMatchForSystemOnly(AndroidPackage pkg, long flags) { if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { return pkg.isSystem(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 6744ff597e20..09b9d31e2fe2 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -27,7 +27,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; public class PackageStateUtils { - public static boolean isMatch(PackageState packageState, int flags) { + public static boolean isMatch(PackageState packageState, long flags) { if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { return packageState.isSystem(); } @@ -54,7 +54,7 @@ public class PackageStateUtils { } public static boolean isEnabledAndMatches(@Nullable PackageStateInternal packageState, - ComponentInfo componentInfo, int flags, int userId) { + ComponentInfo componentInfo, long flags, int userId) { if (packageState == null) return false; final PackageUserState userState = packageState.getUserStateOrDefault(userId); @@ -62,7 +62,7 @@ public class PackageStateUtils { } public static boolean isEnabledAndMatches(@Nullable PackageStateInternal packageState, - @NonNull ParsedMainComponent component, int flags, int userId) { + @NonNull ParsedMainComponent component, long flags, int userId) { if (packageState == null) { return false; } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 1f024eaa52e4..471f38ad49ac 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -391,7 +391,7 @@ public interface DomainVerificationManagerInternal { */ @ApprovalLevel int approvalLevelForDomain(@NonNull PackageStateInternal pkgSetting, @NonNull Intent intent, - @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId); + @PackageManager.ResolveInfoFlags long resolveInfoFlags, @UserIdInt int userId); /** * @return the domain verification set ID for the given package, or null if the ID is diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 0fb8475bc3af..661e67d424ae 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -1721,7 +1721,7 @@ public class DomainVerificationService extends SystemService @Override public int approvalLevelForDomain(@NonNull PackageStateInternal pkgSetting, - @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags, + @NonNull Intent intent, @PackageManager.ResolveInfoFlags long resolveInfoFlags, @UserIdInt int userId) { String packageName = pkgSetting.getPackageName(); if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 246810f4d796..12cce0d9ac70 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -49,7 +49,7 @@ public final class DomainVerificationUtils { } public static boolean isDomainVerificationIntent(Intent intent, - @PackageManager.ResolveInfoFlags int resolveInfoFlags) { + @PackageManager.ResolveInfoFlags long resolveInfoFlags) { if (!intent.isWebIntent()) { return false; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2369c5e984f2..f61b56280b89 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -332,6 +332,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800; private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); /** * Keyguard stuff @@ -517,7 +521,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean mPendingKeyguardOccluded; private boolean mKeyguardOccludedChanged; - private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer; Intent mHomeIntent; Intent mCarDockIntent; Intent mDeskDockIntent; @@ -1115,21 +1118,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case LONG_PRESS_POWER_GLOBAL_ACTIONS: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Global Actions"); showGlobalActions(); break; case LONG_PRESS_POWER_SHUT_OFF: case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Shut Off"); sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS); mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF); break; case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Long Press - Go To Voice Assist"); // Some devices allow the voice assistant intent during setup (and use that intent // to launch something else, like Settings). So we explicitly allow that via the @@ -1153,7 +1156,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS: mPowerKeyHandled = true; - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power - Very Long Press - Show Global Actions"); showGlobalActions(); break; @@ -1815,9 +1818,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId); mLogger = new MetricsLogger(); - mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal - .createSleepTokenAcquirer("ScreenOff"); - Resources res = mContext.getResources(); mWakeOnDpadKeyPress = res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress); @@ -2098,7 +2098,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyHandled = true; break; case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS: - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, + performHapticFeedback( + HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false, "Power + Volume Up - Global Actions"); showGlobalActions(); mPowerKeyHandled = true; @@ -4483,7 +4484,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off..."); if (displayId == DEFAULT_DISPLAY) { - updateScreenOffSleepToken(true); mRequestedOrSleepingDefaultDisplay = false; mDefaultDisplayPolicy.screenTurnedOff(); synchronized (mLock) { @@ -4516,7 +4516,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (displayId == DEFAULT_DISPLAY) { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); - updateScreenOffSleepToken(false); mDefaultDisplayPolicy.screenTurnedOn(screenOnListener); mBootAnimationDismissable = false; @@ -5037,15 +5036,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // TODO (multidisplay): Support multiple displays in WindowManagerPolicy. - private void updateScreenOffSleepToken(boolean acquire) { - if (acquire) { - mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY); - } else { - mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY); - } - } - /** {@inheritDoc} */ @Override public void enableScreenAfterBoot() { @@ -5291,7 +5281,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } - mVibrator.vibrate(uid, packageName, effect, reason, TOUCH_VIBRATION_ATTRIBUTES); + mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId)); return true; } @@ -5320,6 +5310,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case HapticFeedbackConstants.GESTURE_START: return VibrationEffect.get(VibrationEffect.EFFECT_CLICK); case HapticFeedbackConstants.LONG_PRESS: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: case HapticFeedbackConstants.EDGE_SQUEEZE: return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); case HapticFeedbackConstants.REJECT: @@ -5358,6 +5349,22 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + private VibrationAttributes getVibrationAttributes(int effectId) { + switch (effectId) { + case HapticFeedbackConstants.EDGE_SQUEEZE: + case HapticFeedbackConstants.EDGE_RELEASE: + return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES; + case HapticFeedbackConstants.ASSISTANT_BUTTON: + case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON: + case HapticFeedbackConstants.ROTARY_SCROLL_TICK: + case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS: + case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT: + return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES; + default: + return TOUCH_VIBRATION_ATTRIBUTES; + } + } + @Override public void keepScreenOnStartedLw() { } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 5ec2f839cf2f..4571cf395287 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -27,7 +27,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVE import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -190,14 +189,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { String getOwningPackage(); /** - * Retrieve the current LayoutParams of the window. - * - * @return WindowManager.LayoutParams The window's internal LayoutParams - * instance. - */ - public WindowManager.LayoutParams getAttrs(); - - /** * Retrieve the type of the top-level window. * * @return the base type of the parent window if attached or its own type otherwise @@ -658,26 +649,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs); /** - * @return whether {@param win} can be hidden by Keyguard - */ - default boolean canBeHiddenByKeyguardLw(WindowState win) { - // Keyguard visibility of window from activities are determined over activity visibility. - if (win.getBaseType() == TYPE_BASE_APPLICATION) { - return false; - } - switch (win.getAttrs().type) { - case TYPE_NOTIFICATION_SHADE: - case TYPE_STATUS_BAR: - case TYPE_NAVIGATION_BAR: - case TYPE_WALLPAPER: - return false; - default: - // Hide only windows below the keyguard host window. - return getWindowLayerLw(win) < getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE); - } - } - - /** * Create and return an animation to re-display a window that was force hidden by Keyguard. */ public Animation createHiddenByKeyguardExit(boolean onWallpaper, diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 6d0f08de64de..70a804b8135b 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -110,8 +110,8 @@ public class Notifier { private static final VibrationEffect CHARGING_VIBRATION_EFFECT = VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE, -1); - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); private final Object mLock = new Object(); @@ -807,7 +807,7 @@ public class Notifier { final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0; if (vibrate) { - mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, TOUCH_VIBRATION_ATTRIBUTES); + mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); } // play sound diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java new file mode 100644 index 000000000000..f519ceda1e50 --- /dev/null +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.security; + +import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelDuration; +import android.os.RemoteException; +import android.security.attestationverification.AttestationProfile; +import android.security.attestationverification.IAttestationVerificationManagerService; +import android.security.attestationverification.IVerificationResult; +import android.security.attestationverification.VerificationToken; +import android.util.ExceptionUtils; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; +import com.android.server.SystemService; + +/** + * A {@link SystemService} which provides functionality related to verifying attestations of + * (usually) remote computing environments. + * + * @hide + */ +public class AttestationVerificationManagerService extends SystemService { + + private static final String TAG = "AVF"; + + public AttestationVerificationManagerService(final Context context) { + super(context); + } + + private final IBinder mService = new IAttestationVerificationManagerService.Stub() { + @Override + public void verifyAttestation( + AttestationProfile profile, + int localBindingType, + Bundle requirements, + byte[] attestation, + AndroidFuture resultCallback) throws RemoteException { + try { + Slog.d(TAG, "verifyAttestation"); + verifyAttestationForAllVerifiers(profile, localBindingType, requirements, + attestation, resultCallback); + } catch (Throwable t) { + Slog.e(TAG, "failed to verify attestation", t); + throw ExceptionUtils.propagate(t, RemoteException.class); + } + } + + @Override + public void verifyToken(VerificationToken token, ParcelDuration parcelDuration, + AndroidFuture resultCallback) throws RemoteException { + // TODO(b/201696614): Implement + resultCallback.complete(RESULT_UNKNOWN); + } + }; + + private void verifyAttestationForAllVerifiers( + AttestationProfile profile, int localBindingType, Bundle requirements, + byte[] attestation, AndroidFuture<IVerificationResult> resultCallback) { + // TODO(b/201696614): Implement + IVerificationResult result = new IVerificationResult(); + result.resultCode = RESULT_UNKNOWN; + result.token = null; + resultCallback.complete(result); + } + + @Override + public void onStart() { + Slog.d(TAG, "Started"); + publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService); + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 7cdae58e9a99..eb69ff7b285d 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -56,6 +56,7 @@ import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANS import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; +import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; @@ -63,6 +64,8 @@ import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs; import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; +import static libcore.io.IoUtils.closeQuietly; + import static java.lang.Math.min; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -221,6 +224,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -3407,14 +3411,17 @@ public class StatsPullAtomService extends SystemService { pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, metricsState.isTelephonyDetectionSupported(), metricsState.isGeoDetectionSupported(), - metricsState.isUserLocationEnabled(), + metricsState.getUserLocationEnabledSetting(), metricsState.getAutoDetectionEnabledSetting(), metricsState.getGeoDetectionEnabledSetting(), convertToMetricsDetectionMode(metricsState.getDetectionMode()), metricsState.getDeviceTimeZoneIdOrdinal(), - metricsState.getLatestManualSuggestionProtoBytes(), - metricsState.getLatestTelephonySuggestionProtoBytes(), - metricsState.getLatestGeolocationSuggestionProtoBytes() + convertTimeZoneSuggestionToProtoBytes( + metricsState.getLatestManualSuggestion()), + convertTimeZoneSuggestionToProtoBytes( + metricsState.getLatestTelephonySuggestion()), + convertTimeZoneSuggestionToProtoBytes( + metricsState.getLatestGeolocationSuggestion()) )); } catch (RuntimeException e) { Slog.e(TAG, "Getting time zone detection state failed: ", e); @@ -3425,7 +3432,8 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private int convertToMetricsDetectionMode(int detectionMode) { + private static int convertToMetricsDetectionMode( + @MetricsTimeZoneDetectorState.DetectionMode int detectionMode) { switch (detectionMode) { case MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL: return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL; @@ -3434,8 +3442,36 @@ public class StatsPullAtomService extends SystemService { case MetricsTimeZoneDetectorState.DETECTION_MODE_TELEPHONY: return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY; default: - throw new IllegalArgumentException("" + detectionMode); + return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; + } + } + + @Nullable + private static byte[] convertTimeZoneSuggestionToProtoBytes( + @Nullable MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion suggestion) { + if (suggestion == null) { + return null; + } + + // We don't get access to the atoms.proto definition for nested proto fields, so we use + // an identically specified proto. + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ProtoOutputStream protoOutputStream = new ProtoOutputStream(byteArrayOutputStream); + int typeProtoValue = suggestion.isCertain() + ? android.app.time.MetricsTimeZoneSuggestion.CERTAIN + : android.app.time.MetricsTimeZoneSuggestion.UNCERTAIN; + protoOutputStream.write(android.app.time.MetricsTimeZoneSuggestion.TYPE, + typeProtoValue); + if (suggestion.isCertain()) { + for (int zoneIdOrdinal : suggestion.getZoneIdOrdinals()) { + protoOutputStream.write( + android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS, + zoneIdOrdinal); + } } + protoOutputStream.flush(); + closeQuietly(byteArrayOutputStream); + return byteArrayOutputStream.toByteArray(); } private void registerExternalStorageInfo() { diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java index b00540fd2a52..292314840efb 100644 --- a/services/core/java/com/android/server/storage/AppFuseBridge.java +++ b/services/core/java/com/android/server/storage/AppFuseBridge.java @@ -24,7 +24,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.util.Preconditions; -import com.android.server.NativeDaemonConnectorException; +import com.android.server.AppFuseMountException; import libcore.io.IoUtils; import java.util.concurrent.CountDownLatch; @@ -55,7 +55,7 @@ public class AppFuseBridge implements Runnable { } public ParcelFileDescriptor addBridge(MountScope mountScope) - throws FuseUnavailableMountException, NativeDaemonConnectorException { + throws FuseUnavailableMountException, AppFuseMountException { /* ** Dead Lock between Java lock (AppFuseBridge.java) and Native lock (FuseBridgeLoop.cc) ** @@ -112,7 +112,7 @@ public class AppFuseBridge implements Runnable { try { int flags = FileUtils.translateModePfdToPosix(mode); return scope.openFile(mountId, fileId, flags); - } catch (NativeDaemonConnectorException error) { + } catch (AppFuseMountException error) { throw new FuseUnavailableMountException(mountId); } } @@ -160,9 +160,9 @@ public class AppFuseBridge implements Runnable { return mMountResult; } - public abstract ParcelFileDescriptor open() throws NativeDaemonConnectorException; + public abstract ParcelFileDescriptor open() throws AppFuseMountException; public abstract ParcelFileDescriptor openFile(int mountId, int fileId, int flags) - throws NativeDaemonConnectorException; + throws AppFuseMountException; } private native long native_new(); diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java index 477ebf640252..d24a3df56420 100644 --- a/services/core/java/com/android/server/timedetector/ServerFlags.java +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -65,6 +65,7 @@ public final class ServerFlags { KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE, KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE, + KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, }) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) @Retention(RetentionPolicy.SOURCE) @@ -139,6 +140,14 @@ public final class ServerFlags { "location_time_zone_detection_setting_enabled_default"; /** + * The key to control support for time zone detection falling back to telephony detection under + * certain circumstances. + */ + public static final @DeviceConfigKey String + KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED = + "time_zone_detector_telephony_fallback_supported"; + + /** * The key to override the time detector origin priorities configuration. A comma-separated list * of strings that will be passed to {@link TimeDetectorStrategy#stringToOrigin(String)}. * All values must be recognized or the override value will be ignored. diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 22814b31a156..65f077ea60e0 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -39,21 +39,23 @@ public final class ConfigurationInternal { private final boolean mTelephonyDetectionSupported; private final boolean mGeoDetectionSupported; - private final boolean mAutoDetectionEnabled; + private final boolean mTelephonyFallbackSupported; + private final boolean mAutoDetectionEnabledSetting; private final @UserIdInt int mUserId; private final boolean mUserConfigAllowed; - private final boolean mLocationEnabled; - private final boolean mGeoDetectionEnabled; + private final boolean mLocationEnabledSetting; + private final boolean mGeoDetectionEnabledSetting; private ConfigurationInternal(Builder builder) { mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; - mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported; + mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting; mUserId = builder.mUserId; mUserConfigAllowed = builder.mUserConfigAllowed; - mLocationEnabled = builder.mLocationEnabled; - mGeoDetectionEnabled = builder.mGeoDetectionEnabled; + mLocationEnabledSetting = builder.mLocationEnabledSetting; + mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting; } /** Returns true if the device supports any form of auto time zone detection. */ @@ -71,9 +73,17 @@ public final class ConfigurationInternal { return mGeoDetectionSupported; } + /** + * Returns true if the device supports time zone detection falling back to telephony detection + * under certain circumstances. + */ + public boolean isTelephonyFallbackSupported() { + return mTelephonyFallbackSupported; + } + /** Returns the value of the auto time zone detection enabled setting. */ public boolean getAutoDetectionEnabledSetting() { - return mAutoDetectionEnabled; + return mAutoDetectionEnabledSetting; } /** @@ -81,7 +91,7 @@ public final class ConfigurationInternal { * from the raw setting value. */ public boolean getAutoDetectionEnabledBehavior() { - return isAutoDetectionSupported() && mAutoDetectionEnabled; + return isAutoDetectionSupported() && mAutoDetectionEnabledSetting; } /** Returns the ID of the user this configuration is associated with. */ @@ -101,13 +111,13 @@ public final class ConfigurationInternal { } /** Returns true if user's location can be used generally. */ - public boolean isLocationEnabled() { - return mLocationEnabled; + public boolean getLocationEnabledSetting() { + return mLocationEnabledSetting; } /** Returns the value of the geolocation time zone detection enabled setting. */ public boolean getGeoDetectionEnabledSetting() { - return mGeoDetectionEnabled; + return mGeoDetectionEnabledSetting; } /** @@ -117,7 +127,7 @@ public final class ConfigurationInternal { public boolean getGeoDetectionEnabledBehavior() { return getAutoDetectionEnabledBehavior() && isGeoDetectionSupported() - && isLocationEnabled() + && getLocationEnabledSetting() && getGeoDetectionEnabledSetting(); } @@ -154,7 +164,7 @@ public final class ConfigurationInternal { final int configureGeolocationDetectionEnabledCapability; if (!deviceHasLocationTimeZoneDetection) { configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED; - } else if (!mAutoDetectionEnabled || !isLocationEnabled()) { + } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) { configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE; } else { configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED; @@ -195,10 +205,10 @@ public final class ConfigurationInternal { public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) { Builder builder = new Builder(this); if (newConfiguration.hasIsAutoDetectionEnabled()) { - builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled()); + builder.setAutoDetectionEnabledSetting(newConfiguration.isAutoDetectionEnabled()); } if (newConfiguration.hasIsGeoDetectionEnabled()) { - builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled()); + builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled()); } return builder.build(); } @@ -216,16 +226,17 @@ public final class ConfigurationInternal { && mUserConfigAllowed == that.mUserConfigAllowed && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported && mGeoDetectionSupported == that.mGeoDetectionSupported - && mAutoDetectionEnabled == that.mAutoDetectionEnabled - && mLocationEnabled == that.mLocationEnabled - && mGeoDetectionEnabled == that.mGeoDetectionEnabled; + && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported + && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting + && mLocationEnabledSetting == that.mLocationEnabledSetting + && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting; } @Override public int hashCode() { return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported, - mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled, - mGeoDetectionEnabled); + mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting, + mLocationEnabledSetting, mGeoDetectionEnabledSetting); } @Override @@ -235,9 +246,10 @@ public final class ConfigurationInternal { + ", mUserConfigAllowed=" + mUserConfigAllowed + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported + ", mGeoDetectionSupported=" + mGeoDetectionSupported - + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled - + ", mLocationEnabled=" + mLocationEnabled - + ", mGeoDetectionEnabled=" + mGeoDetectionEnabled + + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported + + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting + + ", mLocationEnabledSetting=" + mLocationEnabledSetting + + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting + '}'; } @@ -251,9 +263,10 @@ public final class ConfigurationInternal { private boolean mUserConfigAllowed; private boolean mTelephonyDetectionSupported; private boolean mGeoDetectionSupported; - private boolean mAutoDetectionEnabled; - private boolean mLocationEnabled; - private boolean mGeoDetectionEnabled; + private boolean mTelephonyFallbackSupported; + private boolean mAutoDetectionEnabledSetting; + private boolean mLocationEnabledSetting; + private boolean mGeoDetectionEnabledSetting; /** * Creates a new Builder with only the userId set. @@ -269,10 +282,11 @@ public final class ConfigurationInternal { this.mUserId = toCopy.mUserId; this.mUserConfigAllowed = toCopy.mUserConfigAllowed; this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported; + this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported; this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported; - this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled; - this.mLocationEnabled = toCopy.mLocationEnabled; - this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled; + this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting; + this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting; + this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting; } /** @@ -300,26 +314,35 @@ public final class ConfigurationInternal { } /** + * Sets whether time zone detection supports falling back to telephony detection under + * certain circumstances. + */ + public Builder setTelephonyFallbackSupported(boolean supported) { + mTelephonyFallbackSupported = supported; + return this; + } + + /** * Sets the value of the automatic time zone detection enabled setting for this device. */ - public Builder setAutoDetectionEnabled(boolean enabled) { - mAutoDetectionEnabled = enabled; + public Builder setAutoDetectionEnabledSetting(boolean enabled) { + mAutoDetectionEnabledSetting = enabled; return this; } /** * Sets the value of the location mode setting for this user. */ - public Builder setLocationEnabled(boolean enabled) { - mLocationEnabled = enabled; + public Builder setLocationEnabledSetting(boolean enabled) { + mLocationEnabledSetting = enabled; return this; } /** * Sets the value of the geolocation time zone detection setting for this user. */ - public Builder setGeoDetectionEnabled(boolean enabled) { - mGeoDetectionEnabled = enabled; + public Builder setGeoDetectionEnabledSetting(boolean enabled) { + mGeoDetectionEnabledSetting = enabled; return this; } diff --git a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java new file mode 100644 index 000000000000..62092ec5b4e1 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitor.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timezonedetector; + +import android.annotation.NonNull; + +/** + * The interface for the class that is responsible for detecting device activities relevant to + * time zone detection. This interface exists to decouple parts of the time zone detector from each + * other and to enable easier testing. + * + * @hide + */ +interface DeviceActivityMonitor extends Dumpable { + + /** Adds a listener. */ + void addListener(@NonNull Listener listener); + + /** + * A listener for device activities. See {@link DeviceActivityMonitor#addListener(Listener)}. + */ + interface Listener { + /** A flight has completed. */ + void onFlightComplete(); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java new file mode 100644 index 000000000000..8c9bd3b9d0d7 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/DeviceActivityMonitorImpl.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timezonedetector; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.IndentingPrintWriter; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The real implementation of {@link DeviceActivityMonitor}. + */ +class DeviceActivityMonitorImpl implements DeviceActivityMonitor { + + private static final String LOG_TAG = TimeZoneDetectorService.TAG; + private static final boolean DBG = TimeZoneDetectorService.DBG; + + static DeviceActivityMonitor create(@NonNull Context context, @NonNull Handler handler) { + return new DeviceActivityMonitorImpl(context, handler); + } + + @GuardedBy("this") + @NonNull + private final List<Listener> mListeners = new ArrayList<>(); + + private DeviceActivityMonitorImpl(@NonNull Context context, @NonNull Handler handler) { + // The way this "detects" a flight concluding is by the user explicitly turning off airplane + // mode. Smarter heuristics would be nice. + ContentResolver contentResolver = context.getContentResolver(); + ContentObserver airplaneModeObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean unused) { + try { + int state = Settings.Global.getInt( + contentResolver, Settings.Global.AIRPLANE_MODE_ON); + if (state == 0) { + notifyFlightComplete(); + } + } catch (Settings.SettingNotFoundException e) { + Slog.e(LOG_TAG, "Unable to read airplane mode state", e); + } + } + }; + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), + true /* notifyForDescendants */, + airplaneModeObserver); + } + + @Override + public synchronized void addListener(Listener listener) { + Objects.requireNonNull(listener); + mListeners.add(listener); + } + + private synchronized void notifyFlightComplete() { + if (DBG) { + Slog.d(LOG_TAG, "notifyFlightComplete"); + } + + for (Listener listener : mListeners) { + listener.onFlightComplete(); + } + } + + @Override + public void dump(IndentingPrintWriter pw, String[] args) { + // No-op right now: no state to dump. + } +} diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index ec620b5e2a42..0ec8826cb7eb 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -16,11 +16,13 @@ package com.android.server.timezonedetector; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlarmManager; import android.content.Context; import android.os.Handler; +import android.os.SystemClock; import android.os.SystemProperties; import java.util.Objects; @@ -79,4 +81,9 @@ final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); alarmManager.setTimeZone(zoneId); } + + @Override + public @ElapsedRealtimeLong long elapsedRealtimeMillis() { + return SystemClock.elapsedRealtime(); + } } diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java index 9eb6a458e8e3..f156f8c8e59d 100644 --- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java +++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java @@ -16,16 +16,12 @@ package com.android.server.timezonedetector; -import static libcore.io.IoUtils.closeQuietly; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; -import android.util.proto.ProtoOutputStream; -import java.io.ByteArrayOutputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,22 +46,17 @@ public final class MetricsTimeZoneDetectorState { value = { DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, DETECTION_MODE_TELEPHONY}) @Retention(RetentionPolicy.SOURCE) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) - @interface DetectionMode {}; + public @interface DetectionMode {}; public static final @DetectionMode int DETECTION_MODE_MANUAL = 0; public static final @DetectionMode int DETECTION_MODE_GEO = 1; public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 2; - @NonNull - private final ConfigurationInternal mConfigurationInternal; - @NonNull + @NonNull private final ConfigurationInternal mConfigurationInternal; private final int mDeviceTimeZoneIdOrdinal; - @Nullable - private final MetricsTimeZoneSuggestion mLatestManualSuggestion; - @Nullable - private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion; - @Nullable - private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion; + @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion; + @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion; + @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion; private MetricsTimeZoneDetectorState( @NonNull ConfigurationInternal configurationInternal, @@ -94,16 +85,16 @@ public final class MetricsTimeZoneDetectorState { int deviceTimeZoneIdOrdinal = tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId)); - MetricsTimeZoneSuggestion latestObfuscatedManualSuggestion = + MetricsTimeZoneSuggestion latestCanonicalManualSuggestion = createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion); - MetricsTimeZoneSuggestion latestObfuscatedTelephonySuggestion = + MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion = createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion); - MetricsTimeZoneSuggestion latestObfuscatedGeolocationSuggestion = + MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion); return new MetricsTimeZoneDetectorState( - configurationInternal, deviceTimeZoneIdOrdinal, latestObfuscatedManualSuggestion, - latestObfuscatedTelephonySuggestion, latestObfuscatedGeolocationSuggestion); + configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion, + latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion); } /** Returns true if the device supports telephony time zone detection. */ @@ -116,9 +107,14 @@ public final class MetricsTimeZoneDetectorState { return mConfigurationInternal.isGeoDetectionSupported(); } + /** Returns true if the device supports telephony time zone detection fallback. */ + public boolean isTelephonyTimeZoneFallbackSupported() { + return mConfigurationInternal.isTelephonyFallbackSupported(); + } + /** Returns true if user's location can be used generally. */ - public boolean isUserLocationEnabled() { - return mConfigurationInternal.isLocationEnabled(); + public boolean getUserLocationEnabledSetting() { + return mConfigurationInternal.getLocationEnabledSetting(); } /** Returns the value of the geolocation time zone detection enabled setting. */ @@ -149,36 +145,32 @@ public final class MetricsTimeZoneDetectorState { * Returns the ordinal for the device's currently set time zone ID. * See {@link MetricsTimeZoneDetectorState} for information about ordinals. */ - @NonNull public int getDeviceTimeZoneIdOrdinal() { return mDeviceTimeZoneIdOrdinal; } /** - * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last manual - * suggestion received. + * Returns a canonical form of the last manual suggestion received. */ @Nullable - public byte[] getLatestManualSuggestionProtoBytes() { - return suggestionProtoBytes(mLatestManualSuggestion); + public MetricsTimeZoneSuggestion getLatestManualSuggestion() { + return mLatestManualSuggestion; } /** - * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last, best - * telephony suggestion received. + * Returns a canonical form of the last telephony suggestion received. */ @Nullable - public byte[] getLatestTelephonySuggestionProtoBytes() { - return suggestionProtoBytes(mLatestTelephonySuggestion); + public MetricsTimeZoneSuggestion getLatestTelephonySuggestion() { + return mLatestTelephonySuggestion; } /** - * Returns bytes[] for a {@link MetricsTimeZoneSuggestion} for the last geolocation - * suggestion received. + * Returns a canonical form of last geolocation suggestion received. */ @Nullable - public byte[] getLatestGeolocationSuggestionProtoBytes() { - return suggestionProtoBytes(mLatestGeolocationSuggestion); + public MetricsTimeZoneSuggestion getLatestGeolocationSuggestion() { + return mLatestGeolocationSuggestion; } @Override @@ -214,14 +206,6 @@ public final class MetricsTimeZoneDetectorState { + '}'; } - private static byte[] suggestionProtoBytes( - @Nullable MetricsTimeZoneSuggestion suggestion) { - if (suggestion == null) { - return null; - } - return suggestion.toBytes(); - } - @Nullable private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion( @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator, @@ -265,10 +249,11 @@ public final class MetricsTimeZoneDetectorState { } /** - * A Java class that closely matches the android.app.time.MetricsTimeZoneSuggestion - * proto definition. + * A Java class that represents a generic time zone suggestion, i.e. one that is independent of + * origin-specific information. This closely matches the metrics atoms.proto + * MetricsTimeZoneSuggestion proto definition. */ - private static final class MetricsTimeZoneSuggestion { + public static final class MetricsTimeZoneSuggestion { @Nullable private final int[] mZoneIdOrdinals; @@ -281,42 +266,21 @@ public final class MetricsTimeZoneDetectorState { return new MetricsTimeZoneSuggestion(null); } - public static MetricsTimeZoneSuggestion createCertain( + @NonNull + static MetricsTimeZoneSuggestion createCertain( @NonNull int[] zoneIdOrdinals) { return new MetricsTimeZoneSuggestion(zoneIdOrdinals); } - boolean isCertain() { + public boolean isCertain() { return mZoneIdOrdinals != null; } @Nullable - int[] getZoneIdOrdinals() { + public int[] getZoneIdOrdinals() { return mZoneIdOrdinals; } - byte[] toBytes() { - // We don't get access to the atoms.proto definition for nested proto fields, so we use - // an identically specified proto. - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ProtoOutputStream protoOutputStream = new ProtoOutputStream(byteArrayOutputStream); - int typeProtoValue = isCertain() - ? android.app.time.MetricsTimeZoneSuggestion.CERTAIN - : android.app.time.MetricsTimeZoneSuggestion.UNCERTAIN; - protoOutputStream.write(android.app.time.MetricsTimeZoneSuggestion.TYPE, - typeProtoValue); - if (isCertain()) { - for (int zoneIdOrdinal : getZoneIdOrdinals()) { - protoOutputStream.write( - android.app.time.MetricsTimeZoneSuggestion.TIME_ZONE_ORDINALS, - zoneIdOrdinal); - } - } - protoOutputStream.flush(); - closeQuietly(byteArrayOutputStream); - return byteArrayOutputStream.toByteArray(); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java index 984b9baf0fc7..692b0cc795f5 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -172,12 +172,13 @@ public interface ServiceConfigAccessor { * Enables/disables the state recording mode for tests. The value is reset with {@link * #resetVolatileTestConfig()}. */ - void setRecordProviderStateChanges(boolean enabled); + void setRecordStateChangesForTests(boolean enabled); /** - * Returns {@code true} if providers are expected to record their state changes for tests. + * Returns {@code true} if the controller / providers are expected to record their state changes + * for tests. */ - boolean getRecordProviderStateChanges(); + boolean getRecordStateChangesForTests(); /** * Returns the mode for the primary location time zone provider. diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java index 6e63f59cecb4..02ea43341bef 100644 --- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java @@ -64,6 +64,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, })); /** @@ -149,7 +150,7 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { * See also {@link #resetVolatileTestConfig()}. */ @GuardedBy("this") - private boolean mRecordProviderStateChanges; + private boolean mRecordStateChangesForTests; private ServiceConfigAccessorImpl(@NonNull Context context) { mContext = Objects.requireNonNull(context); @@ -281,8 +282,8 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { // later releases that start to support geo detection on the same hardware. if (!getGeoDetectionSettingEnabledOverride().isPresent() && isGeoTimeZoneDetectionFeatureSupported()) { - final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); - setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); + final boolean geoDetectionEnabledSetting = configuration.isGeoDetectionEnabled(); + setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting); } } } @@ -294,10 +295,11 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { .setTelephonyDetectionFeatureSupported( isTelephonyTimeZoneDetectionFeatureSupported()) .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported()) - .setAutoDetectionEnabled(isAutoDetectionEnabled()) + .setTelephonyFallbackSupported(isTelephonyFallbackSupported()) + .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting()) .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setLocationEnabled(isLocationEnabled(userId)) - .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) + .setLocationEnabledSetting(getLocationEnabledSetting(userId)) + .setGeoDetectionEnabledSetting(getGeoDetectionEnabledSetting(userId)) .build(); } @@ -306,12 +308,12 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { // a ConfigurationChangeListener callback triggering due to ContentObserver's still // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary // for stable behavior during tests. - if (isAutoDetectionEnabled() != enabled) { + if (getAutoDetectionEnabledSetting() != enabled) { Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0); } } - private boolean isLocationEnabled(@UserIdInt int userId) { + private boolean getLocationEnabledSetting(@UserIdInt int userId) { return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId)); } @@ -320,11 +322,11 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } - private boolean isAutoDetectionEnabled() { + private boolean getAutoDetectionEnabledSetting() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } - private boolean isGeoDetectionEnabled(@UserIdInt int userId) { + private boolean getGeoDetectionEnabledSetting(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection // on/off for testers (but only where their other settings would allow them to turn it on // for themselves). @@ -339,9 +341,9 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } - private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { + private void setGeoDetectionEnabledSettingIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 - if (isGeoDetectionEnabled(userId) != enabled) { + if (getGeoDetectionEnabledSetting(userId) != enabled) { Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, enabled ? 1 : 0, userId); } @@ -451,13 +453,13 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { } @Override - public synchronized void setRecordProviderStateChanges(boolean enabled) { - mRecordProviderStateChanges = enabled; + public synchronized void setRecordStateChangesForTests(boolean enabled) { + mRecordStateChangesForTests = enabled; } @Override - public synchronized boolean getRecordProviderStateChanges() { - return mRecordProviderStateChanges; + public synchronized boolean getRecordStateChangesForTests() { + return mRecordStateChangesForTests; } @Override @@ -546,7 +548,14 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { mTestPrimaryLocationTimeZoneProviderMode = null; mTestSecondaryLocationTimeZoneProviderPackageName = null; mTestSecondaryLocationTimeZoneProviderMode = null; - mRecordProviderStateChanges = false; + mRecordStateChangesForTests = false; + } + + private boolean isTelephonyFallbackSupported() { + return mServerFlags.getBoolean( + ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED, + getConfigBoolean( + com.android.internal.R.bool.config_supportTelephonyTimeZoneFallback)); } private boolean getConfigBoolean(int providerEnabledConfigId) { diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index e0c39ad55f47..14784cf0d550 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -83,6 +83,16 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub ServiceConfigAccessorImpl.getInstance(context); TimeZoneDetectorStrategy timeZoneDetectorStrategy = TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); + DeviceActivityMonitor deviceActivityMonitor = + DeviceActivityMonitorImpl.create(context, handler); + + // Wire up the telephony fallback behavior to activity detection. + deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() { + @Override + public void onFlightComplete() { + timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + } + }); // Create and publish the local service for use by internal callers. TimeZoneDetectorInternal internal = @@ -93,6 +103,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub // permissioned) processes. TimeZoneDetectorService service = TimeZoneDetectorService.create( context, handler, serviceConfigAccessor, timeZoneDetectorStrategy); + + // Dump the device activity monitor when the service is dumped. + service.addDumpable(deviceActivityMonitor); + publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service); } } @@ -332,6 +346,15 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub } /** + * Sends a signal to enable telephony fallback. Provided for command-line access for use + * during tests. This is not exposed as a binder API. + */ + void enableTelephonyFallback() { + enforceManageTimeZoneDetectorPermission(); + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + } + + /** * Registers the supplied {@link Dumpable} for dumping. When the service is dumped * {@link Dumpable#dump(IndentingPrintWriter, String[])} will be called on the {@code dumpable}. */ diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index a4a46a3c4c73..2b912ad7ba60 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -15,6 +15,7 @@ */ package com.android.server.timezonedetector; +import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED; import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED; @@ -30,6 +31,7 @@ import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT; import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE; +import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED; import android.app.time.LocationTimeZoneManager; import android.app.time.TimeZoneConfiguration; @@ -76,6 +78,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand { return runSuggestManualTimeZone(); case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE: return runSuggestTelephonyTimeZone(); + case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK: + return runEnableTelephonyFallback(); default: { return handleDefaultCommands(cmd); } @@ -169,6 +173,11 @@ class TimeZoneDetectorShellCommand extends ShellCommand { } } + private int runEnableTelephonyFallback() { + mInterface.enableTelephonyFallback(); + return 1; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -190,6 +199,13 @@ class TimeZoneDetectorShellCommand extends ShellCommand { + "\n"); pw.printf(" %s true|false\n", SHELL_COMMAND_SET_GEO_DETECTION_ENABLED); pw.printf(" Sets the geolocation time zone detection enabled setting.\n"); + pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK); + pw.printf(" Signals that telephony time zone detection fall back can be used if" + + " geolocation detection is supported and enabled. This is a temporary state until" + + " geolocation detection becomes \"certain\". To have an effect this requires that" + + " the telephony fallback feature is supported on the device, see below for" + + " for device_config flags.\n"); + pw.println(); pw.printf(" %s <geolocation suggestion opts>\n", SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE); pw.printf(" %s <manual suggestion opts>\n", @@ -216,6 +232,9 @@ class TimeZoneDetectorShellCommand extends ShellCommand { pw.printf(" %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); pw.printf(" Used to override the device's 'geolocation time zone detection enabled'" + " setting [*].\n"); + pw.printf(" %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED); + pw.printf(" Used to enable / disable support for telephony detection fallback. Also see" + + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK); pw.println(); pw.printf("[*] To be enabled, the user must still have location = on / auto time zone" + " detection = on.\n"); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index ede52ba84d73..6b04adf7de61 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -69,6 +69,20 @@ import android.util.IndentingPrintWriter; * users enter areas without the necessary signals. Ultimately, with no perfect algorithm available, * the user is left to choose which algorithm works best for their circumstances. * + * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during + * international travel, it makes sense to prioritize speed of detection via telephony (when + * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can + * sometimes be slow to get a location fix and can require network connectivity (which cannot be + * assumed when users are travelling) for server-assisted location detection or time zone lookup. + * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms, + * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device + * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in + * response to signals outside of the scope of this class. Telephony fallback allows the use of + * telephony suggestions to help with faster detection but only until geolocation detection + * provides a concrete, "certain" suggestion. After geolocation has made the first certain + * suggestion, telephony fallback is disabled until the next call to {@link + * #enableTelephonyTimeZoneFallback()}. + * * <p>Threading: * * <p>Implementations of this class must be thread-safe as calls calls like {@link @@ -100,6 +114,13 @@ public interface TimeZoneDetectorStrategy extends Dumpable { */ void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); + /** + * Tells the strategy that it can fall back to telephony detection while geolocation detection + * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can + * disable it again. See {@link TimeZoneDetectorStrategy} for details. + */ + void enableTelephonyTimeZoneFallback(); + /** Generates a state snapshot for metrics. */ @NonNull MetricsTimeZoneDetectorState generateMetricsState(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 3b6c1ead3388..92dddacbcff5 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -22,6 +22,7 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_M import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -31,6 +32,7 @@ import android.app.timezonedetector.ManualTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.content.Context; import android.os.Handler; +import android.os.TimestampedValue; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Slog; @@ -38,6 +40,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.time.Duration; import java.util.List; import java.util.Objects; @@ -83,6 +86,13 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * Sets the device's time zone. */ void setDeviceTimeZone(@NonNull String zoneId); + + /** + * Returns the time according to the elapsed realtime clock, the same as {@link + * android.os.SystemClock#elapsedRealtime()}. + */ + @ElapsedRealtimeLong + long elapsedRealtimeMillis(); } private static final String LOG_TAG = TimeZoneDetectorService.TAG; @@ -191,6 +201,21 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat private ConfigurationInternal mCurrentConfigurationInternal; /** + * Whether telephony time zone detection fallback is currently enabled (when device config also + * allows). + * + * <p>This field is only actually used when telephony time zone fallback is supported, but the + * value is maintained even when it isn't supported as it can be turned on at any time via + * server flags. The reference time is the elapsed realtime when the mode last changed to help + * ordering between fallback mode switches and suggestions. + * + * <p>See {@link TimeZoneDetectorStrategy} for more information. + */ + @GuardedBy("this") + @NonNull + private TimestampedValue<Boolean> mTelephonyTimeZoneFallbackEnabled; + + /** * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. */ public static TimeZoneDetectorStrategyImpl create( @@ -205,6 +230,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) { mEnvironment = Objects.requireNonNull(environment); + // Start with telephony fallback enabled. + mTelephonyTimeZoneFallbackEnabled = + new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true); + synchronized (this) { mEnvironment.setConfigurationInternalChangeListener( this::handleConfigurationInternalChanged); @@ -233,6 +262,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // are made in a sensible order and the most recent is always the best one to use. mLatestGeoLocationSuggestion.set(suggestion); + // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion + // will usually disable telephony fallback mode if it is currently enabled. + disableTelephonyFallbackIfNeeded(); + // Now perform auto time zone detection. The new suggestion may be used to modify the // time zone setting. String reason = "New geolocation time zone suggested. suggestion=" + suggestion; @@ -304,6 +337,43 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override + public synchronized void enableTelephonyTimeZoneFallback() { + // Only do any work if fallback is currently not enabled. + if (!mTelephonyTimeZoneFallbackEnabled.getValue()) { + ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; + if (DBG) { + Slog.d(LOG_TAG, "enableTelephonyTimeZoneFallbackMode" + + ": currentUserConfig=" + currentUserConfig); + } + + final boolean fallbackEnabled = true; + mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( + mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); + + // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact. + // If there is currently a certain geolocation suggestion, then the telephony fallback + // value needs to be considered after changing it. + // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen + // above, and the fact that geolocation suggestions should never have a time in the + // future, the following call will be a no-op, and telephony fallback will remain + // enabled. This comment / call is left as a reminder that it is possible for there to + // be a current, "certain" geolocation suggestion when this signal arrives and it is + // intentional that fallback stays enabled in this case. The choice to do this + // is mostly for symmetry WRT the case where fallback is enabled and an old "certain" + // geolocation is received; that would also leave telephony fallback enabled. + // This choice means that telephony fallback will remain enabled until a new "certain" + // geolocation suggestion is received. If, instead, the next geolocation is "uncertain", + // then telephony fallback will occur. + disableTelephonyFallbackIfNeeded(); + + if (currentUserConfig.isTelephonyFallbackSupported()) { + String reason = "enableTelephonyTimeZoneFallbackMode"; + doAutoTimeZoneDetection(currentUserConfig, reason); + } + } + } + + @Override @NonNull public synchronized MetricsTimeZoneDetectorState generateMetricsState() { // Just capture one telephony suggestion: the one that would be used right now if telephony @@ -361,7 +431,32 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat // Use the correct algorithm based on the user's current configuration. If it changes, then // detection will be re-run. if (currentUserConfig.getGeoDetectionEnabledBehavior()) { - doGeolocationTimeZoneDetection(detectionReason); + boolean isGeoDetectionCertain = doGeolocationTimeZoneDetection(detectionReason); + + // When geolocation detection is uncertain of the time zone, telephony detection + // can be used if telephony fallback is enabled and supported. + if (!isGeoDetectionCertain + && mTelephonyTimeZoneFallbackEnabled.getValue() + && currentUserConfig.isTelephonyFallbackSupported()) { + + // This "only look at telephony if geolocation is uncertain" approach is + // deliberate to try to keep the logic simple and keep telephony and geolocation + // detection decoupled: when geolocation detection is in use, it is fully + // trusted and the most recent "certain" geolocation suggestion available will + // be used, even if the information it is based on is quite old. + // There could be newer telephony suggestions available, but telephony + // suggestions tend not to be withdrawn when they should be, and are based on + // combining information like MCC and NITZ signals, which could have been + // received at different times; thus it is hard to say what time the suggestion + // is actually "for" and reason clearly about ordering between telephony and + // geolocation suggestions. + // + // This approach is reliant on the location_time_zone_manager (and the location + // time zone providers it manages) correctly sending "uncertain" suggestions + // when the current location is unknown so that telephony fallback will actually be + // used. + doTelephonyTimeZoneDetection(detectionReason + ", telephony fallback mode"); + } } else { doTelephonyTimeZoneDetection(detectionReason); } @@ -371,21 +466,29 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * Detects the time zone using the latest available geolocation time zone suggestion, if one is * available. The outcome can be that this strategy becomes / remains un-opinionated and nothing * is set. + * + * @return true if geolocation time zone detection was certain of the time zone, false if it is + * uncertain */ @GuardedBy("this") - private void doGeolocationTimeZoneDetection(@NonNull String detectionReason) { + private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) { GeolocationTimeZoneSuggestion latestGeolocationSuggestion = mLatestGeoLocationSuggestion.get(); if (latestGeolocationSuggestion == null) { - return; + return false; } List<String> zoneIds = latestGeolocationSuggestion.getZoneIds(); - if (zoneIds == null || zoneIds.isEmpty()) { - // This means the client has become uncertain about the time zone or it is certain there - // is no known zone. In either case we must leave the existing time zone setting as it - // is. - return; + if (zoneIds == null) { + // This means the originator of the suggestion is uncertain about the time zone. The + // existing time zone setting must be left as it is but detection can go on looking for + // a different answer elsewhere. + return false; + } else if (zoneIds.isEmpty()) { + // This means the originator is certain there is no time zone. The existing time zone + // setting must be left as it is and detection must not go looking for a different + // answer elsewhere. + return true; } // GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are @@ -404,6 +507,34 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat zoneId = zoneIds.get(0); } setDeviceTimeZoneIfRequired(zoneId, detectionReason); + return true; + } + + /** + * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo + * suggestion is a "certain" suggestion that comes after the time when telephony fallback was + * enabled. + */ + @GuardedBy("this") + private void disableTelephonyFallbackIfNeeded() { + GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get(); + boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null; + if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) { + // This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from + // true -> false. See mTelephonyTimeZoneFallbackEnabled javadocs for details. + + // Telephony fallback will be disabled after a "certain" suggestion is processed + // if and only if the location information it is based on is from after telephony + // fallback was enabled. + boolean latestSuggestionIsNewerThanFallbackEnabled = + suggestion.getEffectiveFromElapsedMillis() + > mTelephonyTimeZoneFallbackEnabled.getReferenceTimeMillis(); + if (latestSuggestionIsNewerThanFallbackEnabled) { + final boolean fallbackEnabled = false; + mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( + mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); + } + } } /** @@ -556,6 +687,12 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat + mEnvironment.isDeviceTimeZoneInitialized()); ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone()); + ipw.println("Misc state:"); + ipw.increaseIndent(); // level 2 + ipw.println("mTelephonyTimeZoneFallbackEnabled=" + + formatDebugString(mTelephonyTimeZoneFallbackEnabled)); + ipw.decreaseIndent(); // level 2 + ipw.println("Time zone change log:"); ipw.increaseIndent(); // level 2 mTimeZoneChangesLog.dump(ipw); @@ -603,6 +740,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat return mLatestGeoLocationSuggestion.get(); } + @VisibleForTesting + public synchronized boolean isTelephonyFallbackEnabledForTests() { + return mTelephonyTimeZoneFallbackEnabled.getValue(); + } + /** * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata. */ @@ -652,4 +794,8 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat + '}'; } } + + private static String formatDebugString(TimestampedValue<?> value) { + return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis()); + } } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java deleted file mode 100644 index b9da2ebd3663..000000000000 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.timezonedetector.location; - -import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; -import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; - -import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; -import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; - -import android.annotation.DurationMillisLong; -import android.annotation.ElapsedRealtimeLong; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.service.timezone.TimeZoneProviderEvent; -import android.service.timezone.TimeZoneProviderSuggestion; -import android.util.IndentingPrintWriter; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.timezonedetector.ConfigurationInternal; -import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; -import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; - -import java.time.Duration; -import java.util.Objects; - -/** - * A real implementation of {@link LocationTimeZoneProviderController} that supports a primary and a - * secondary {@link LocationTimeZoneProvider}. - * - * <p>The primary is used until it fails or becomes uncertain. The secondary will then be started. - * The controller will immediately make suggestions based on "certain" {@link - * TimeZoneProviderEvent}s, i.e. events that demonstrate the provider is certain what the time zone - * is. The controller will not make immediate suggestions based on "uncertain" events, giving - * providers time to change their mind. This also gives the secondary provider time to initialize - * when the primary becomes uncertain. - */ -class ControllerImpl extends LocationTimeZoneProviderController { - - @NonNull private final LocationTimeZoneProvider mPrimaryProvider; - - @NonNull private final LocationTimeZoneProvider mSecondaryProvider; - - @GuardedBy("mSharedLock") - // Non-null after initialize() - private ConfigurationInternal mCurrentUserConfiguration; - - @GuardedBy("mSharedLock") - // Non-null after initialize() - private Environment mEnvironment; - - @GuardedBy("mSharedLock") - // Non-null after initialize() - private Callback mCallback; - - /** Indicates both providers have completed initialization. */ - @GuardedBy("mSharedLock") - private boolean mProvidersInitialized; - - /** - * Used for scheduling uncertainty timeouts, i.e after a provider has reported uncertainty. - * This timeout is not provider-specific: it is started when the controller becomes uncertain - * due to events it has received from one or other provider. - */ - @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue; - - /** Contains the last suggestion actually made, if there is one. */ - @GuardedBy("mSharedLock") - @Nullable - private GeolocationTimeZoneSuggestion mLastSuggestion; - - ControllerImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull LocationTimeZoneProvider primaryProvider, - @NonNull LocationTimeZoneProvider secondaryProvider) { - super(threadingDomain); - mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue(); - mPrimaryProvider = Objects.requireNonNull(primaryProvider); - mSecondaryProvider = Objects.requireNonNull(secondaryProvider); - } - - @Override - void initialize(@NonNull Environment environment, @NonNull Callback callback) { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - debugLog("initialize()"); - mEnvironment = Objects.requireNonNull(environment); - mCallback = Objects.requireNonNull(callback); - mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal(); - - LocationTimeZoneProvider.ProviderListener providerListener = - ControllerImpl.this::onProviderStateChange; - mPrimaryProvider.initialize(providerListener); - mSecondaryProvider.initialize(providerListener); - mProvidersInitialized = true; - - alterProvidersStartedStateIfRequired( - null /* oldConfiguration */, mCurrentUserConfiguration); - } - } - - @Override - void onConfigurationInternalChanged() { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - debugLog("onConfigChanged()"); - - ConfigurationInternal oldConfig = mCurrentUserConfiguration; - ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal(); - mCurrentUserConfiguration = newConfig; - - if (!newConfig.equals(oldConfig)) { - if (newConfig.getUserId() != oldConfig.getUserId()) { - // If the user changed, stop the providers if needed. They may be re-started - // for the new user immediately afterwards if their settings allow. - debugLog("User changed. old=" + oldConfig.getUserId() - + ", new=" + newConfig.getUserId() + ": Stopping providers"); - stopProviders(); - - alterProvidersStartedStateIfRequired(null /* oldConfiguration */, newConfig); - } else { - alterProvidersStartedStateIfRequired(oldConfig, newConfig); - } - } - } - } - - @Override - boolean isUncertaintyTimeoutSet() { - return mUncertaintyTimeoutQueue.hasQueued(); - } - - @Override - @DurationMillisLong - long getUncertaintyTimeoutDelayMillis() { - return mUncertaintyTimeoutQueue.getQueuedDelayMillis(); - } - - @Override - void destroy() { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - stopProviders(); - mPrimaryProvider.destroy(); - mSecondaryProvider.destroy(); - } - } - - @GuardedBy("mSharedLock") - private void stopProviders() { - stopProviderIfStarted(mPrimaryProvider); - stopProviderIfStarted(mSecondaryProvider); - - // By definition, if both providers are stopped, the controller is uncertain. - cancelUncertaintyTimeout(); - - // If a previous "certain" suggestion has been made, then a new "uncertain" - // suggestion must now be made to indicate the controller {does not / no longer has} - // an opinion and will not be sending further updates (until at least the providers are - // re-started). - if (mLastSuggestion != null && mLastSuggestion.getZoneIds() != null) { - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), "Providers are stopping"); - makeSuggestion(suggestion); - } - } - - @GuardedBy("mSharedLock") - private void stopProviderIfStarted(@NonNull LocationTimeZoneProvider provider) { - if (provider.getCurrentState().isStarted()) { - stopProvider(provider); - } - } - - @GuardedBy("mSharedLock") - private void stopProvider(@NonNull LocationTimeZoneProvider provider) { - ProviderState providerState = provider.getCurrentState(); - switch (providerState.stateEnum) { - case PROVIDER_STATE_STOPPED: { - debugLog("No need to stop " + provider + ": already stopped"); - break; - } - case PROVIDER_STATE_STARTED_INITIALIZING: - case PROVIDER_STATE_STARTED_CERTAIN: - case PROVIDER_STATE_STARTED_UNCERTAIN: { - debugLog("Stopping " + provider); - provider.stopUpdates(); - break; - } - case PROVIDER_STATE_PERM_FAILED: - case PROVIDER_STATE_DESTROYED: { - debugLog("Unable to stop " + provider + ": it is terminated."); - break; - } - default: { - warnLog("Unknown provider state: " + provider); - break; - } - } - } - - /** - * Sets the providers into the correct started/stopped state for the {@code newConfiguration} - * and, if there is a provider state change, makes any suggestions required to inform the - * downstream time zone detection code. - * - * <p>This is a utility method that exists to avoid duplicated logic for the various cases when - * provider started / stopped state may need to be set or changed, e.g. during initialization - * or when a new configuration has been received. - */ - @GuardedBy("mSharedLock") - private void alterProvidersStartedStateIfRequired( - @Nullable ConfigurationInternal oldConfiguration, - @NonNull ConfigurationInternal newConfiguration) { - - // Provider started / stopped states only need to be changed if geoDetectionEnabled has - // changed. - boolean oldGeoDetectionEnabled = oldConfiguration != null - && oldConfiguration.getGeoDetectionEnabledBehavior(); - boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior(); - if (oldGeoDetectionEnabled == newGeoDetectionEnabled) { - return; - } - - // The check above ensures that the logic below only executes if providers are going from - // {started *} -> {stopped}, or {stopped} -> {started initializing}. If this changes in - // future and there could be {started *} -> {started *} cases, or cases where the provider - // can't be assumed to go straight to the {started initializing} state, then the logic below - // would need to cover extra conditions, for example: - // 1) If the primary is in {started uncertain}, the secondary should be started. - // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty - // timeout started when the primary entered {started uncertain} should be cancelled. - - if (newGeoDetectionEnabled) { - // Try to start the primary provider. - tryStartProvider(mPrimaryProvider, newConfiguration); - - // The secondary should only ever be started if the primary now isn't started (i.e. it - // couldn't become {started initializing} because it is {perm failed}). - ProviderState newPrimaryState = mPrimaryProvider.getCurrentState(); - if (!newPrimaryState.isStarted()) { - // If the primary provider is {perm failed} then the controller must try to start - // the secondary. - tryStartProvider(mSecondaryProvider, newConfiguration); - - ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); - if (!newSecondaryState.isStarted()) { - // If both providers are {perm failed} then the controller immediately - // becomes uncertain. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), - "Providers are failed:" - + " primary=" + mPrimaryProvider.getCurrentState() - + " secondary=" + mPrimaryProvider.getCurrentState()); - makeSuggestion(suggestion); - } - } - } else { - stopProviders(); - } - } - - @GuardedBy("mSharedLock") - private void tryStartProvider(@NonNull LocationTimeZoneProvider provider, - @NonNull ConfigurationInternal configuration) { - ProviderState providerState = provider.getCurrentState(); - switch (providerState.stateEnum) { - case PROVIDER_STATE_STOPPED: { - debugLog("Enabling " + provider); - provider.startUpdates(configuration, - mEnvironment.getProviderInitializationTimeout(), - mEnvironment.getProviderInitializationTimeoutFuzz(), - mEnvironment.getProviderEventFilteringAgeThreshold()); - break; - } - case PROVIDER_STATE_STARTED_INITIALIZING: - case PROVIDER_STATE_STARTED_CERTAIN: - case PROVIDER_STATE_STARTED_UNCERTAIN: { - debugLog("No need to start " + provider + ": already started"); - break; - } - case PROVIDER_STATE_PERM_FAILED: - case PROVIDER_STATE_DESTROYED: { - debugLog("Unable to start " + provider + ": it is terminated"); - break; - } - default: { - throw new IllegalStateException("Unknown provider state:" - + " provider=" + provider); - } - } - } - - void onProviderStateChange(@NonNull ProviderState providerState) { - mThreadingDomain.assertCurrentThread(); - LocationTimeZoneProvider provider = providerState.provider; - assertProviderKnown(provider); - - synchronized (mSharedLock) { - // Ignore provider state changes during initialization. e.g. if the primary provider - // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not - // be ready to take over yet. - if (!mProvidersInitialized) { - warnLog("onProviderStateChange: Ignoring provider state change because both" - + " providers have not yet completed initialization." - + " providerState=" + providerState); - return; - } - - switch (providerState.stateEnum) { - case PROVIDER_STATE_STARTED_INITIALIZING: - case PROVIDER_STATE_STOPPED: - case PROVIDER_STATE_DESTROYED: { - // This should never happen: entering initializing, stopped or destroyed are - // triggered by the controller so and should not trigger a state change - // callback. - warnLog("onProviderStateChange: Unexpected state change for provider," - + " provider=" + provider); - break; - } - case PROVIDER_STATE_STARTED_CERTAIN: - case PROVIDER_STATE_STARTED_UNCERTAIN: { - // These are valid and only happen if an event is received while the provider is - // started. - debugLog("onProviderStateChange: Received notification of a state change while" - + " started, provider=" + provider); - handleProviderStartedStateChange(providerState); - break; - } - case PROVIDER_STATE_PERM_FAILED: { - debugLog("Received notification of permanent failure for" - + " provider=" + provider); - handleProviderFailedStateChange(providerState); - break; - } - default: { - warnLog("onProviderStateChange: Unexpected provider=" + provider); - } - } - } - } - - private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) { - if (provider != mPrimaryProvider && provider != mSecondaryProvider) { - throw new IllegalArgumentException("Unknown provider: " + provider); - } - } - - /** - * Called when a provider has reported that it has failed permanently. - */ - @GuardedBy("mSharedLock") - private void handleProviderFailedStateChange(@NonNull ProviderState providerState) { - LocationTimeZoneProvider failedProvider = providerState.provider; - ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState(); - ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState(); - - // If a provider has failed, the other may need to be started. - if (failedProvider == mPrimaryProvider) { - if (!secondaryCurrentState.isTerminated()) { - // Try to start the secondary. This does nothing if the provider is already - // started, and will leave the provider in {started initializing} if the provider is - // stopped. - tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); - } - } else if (failedProvider == mSecondaryProvider) { - // No-op: The secondary will only be active if the primary is uncertain or is - // terminated. So, there the primary should not need to be started when the secondary - // fails. - if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN - && !primaryCurrentState.isTerminated()) { - warnLog("Secondary provider unexpected reported a failure:" - + " failed provider=" + failedProvider.getName() - + ", primary provider=" + mPrimaryProvider - + ", secondary provider=" + mSecondaryProvider); - } - } - - // If both providers are now terminated, the controller needs to tell the next component in - // the time zone detection process. - if (primaryCurrentState.isTerminated() && secondaryCurrentState.isTerminated()) { - - // If both providers are newly terminated then the controller is uncertain by definition - // and it will never recover so it can send a suggestion immediately. - cancelUncertaintyTimeout(); - - // If both providers are now terminated, then a suggestion must be sent informing the - // time zone detector that there are no further updates coming in future. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - mEnvironment.elapsedRealtimeMillis(), - "Both providers are terminated:" - + " primary=" + primaryCurrentState.provider - + ", secondary=" + secondaryCurrentState.provider); - makeSuggestion(suggestion); - } - } - - /** - * Called when a provider has changed state but just moved from one started state to another - * started state, usually as a result of a new {@link TimeZoneProviderEvent} being received. - * However, there are rare cases where the event can also be null. - */ - @GuardedBy("mSharedLock") - private void handleProviderStartedStateChange(@NonNull ProviderState providerState) { - LocationTimeZoneProvider provider = providerState.provider; - TimeZoneProviderEvent event = providerState.event; - if (event == null) { - // Implicit uncertainty, i.e. where the provider is started, but a problem has been - // detected without having received an event. For example, if the process has detected - // the loss of a binder-based provider, or initialization took too long. This is treated - // the same as explicit uncertainty, i.e. where the provider has explicitly told this - // process it is uncertain. - long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis(); - handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis, - "provider=" + provider + ", implicit uncertainty, event=null"); - return; - } - - if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) { - // This should not happen: the provider should not be in an started state if the user - // does not have geodetection enabled. - warnLog("Provider=" + provider + " is started, but" - + " currentUserConfiguration=" + mCurrentUserConfiguration - + " suggests it shouldn't be."); - } - - switch (event.getType()) { - case EVENT_TYPE_PERMANENT_FAILURE: { - // This shouldn't happen. A provider cannot be started and have this event type. - warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be"); - break; - } - case EVENT_TYPE_UNCERTAIN: { - long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis(); - handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis, - "provider=" + provider + ", explicit uncertainty. event=" + event); - break; - } - case EVENT_TYPE_SUGGESTION: { - handleProviderSuggestion(provider, event); - break; - } - default: { - warnLog("Unknown eventType=" + event.getType()); - break; - } - } - } - - /** - * Called when a provider has become "certain" about the time zone(s). - */ - @GuardedBy("mSharedLock") - private void handleProviderSuggestion( - @NonNull LocationTimeZoneProvider provider, - @NonNull TimeZoneProviderEvent providerEvent) { - - // By definition, the controller is now certain. - cancelUncertaintyTimeout(); - - if (provider == mPrimaryProvider) { - stopProviderIfStarted(mSecondaryProvider); - } - - TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion(); - - // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's - // suggestion (which indicates the time when the provider detected the location used to - // establish the time zone). - // - // An alternative would be to use the current time or the providerEvent creation time, but - // this would hinder the ability for the time_zone_detector to judge which suggestions are - // based on newer information when comparing suggestions between different sources. - long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis(); - GeolocationTimeZoneSuggestion geoSuggestion = - GeolocationTimeZoneSuggestion.createCertainSuggestion( - effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds()); - - String debugInfo = "Event received provider=" + provider - + ", providerEvent=" + providerEvent - + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis(); - geoSuggestion.addDebugInfo(debugInfo); - makeSuggestion(geoSuggestion); - } - - @Override - public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { - synchronized (mSharedLock) { - ipw.println("LocationTimeZoneProviderController:"); - - ipw.increaseIndent(); // level 1 - ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration); - ipw.println("providerInitializationTimeout=" - + mEnvironment.getProviderInitializationTimeout()); - ipw.println("providerInitializationTimeoutFuzz=" - + mEnvironment.getProviderInitializationTimeoutFuzz()); - ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay()); - ipw.println("mLastSuggestion=" + mLastSuggestion); - - ipw.println("Primary Provider:"); - ipw.increaseIndent(); // level 2 - mPrimaryProvider.dump(ipw, args); - ipw.decreaseIndent(); // level 2 - - ipw.println("Secondary Provider:"); - ipw.increaseIndent(); // level 2 - mSecondaryProvider.dump(ipw, args); - ipw.decreaseIndent(); // level 2 - - ipw.decreaseIndent(); // level 1 - } - } - - /** Sends an immediate suggestion, updating mLastSuggestion. */ - @GuardedBy("mSharedLock") - private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion) { - debugLog("makeSuggestion: suggestion=" + suggestion); - mCallback.suggest(suggestion); - mLastSuggestion = suggestion; - } - - /** Clears the uncertainty timeout. */ - @GuardedBy("mSharedLock") - private void cancelUncertaintyTimeout() { - mUncertaintyTimeoutQueue.cancel(); - } - - /** - * Called when a provider has become "uncertain" about the time zone. - * - * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as - * this enables the most flexibility for the controller to start other providers when there are - * multiple ones available. The controller is therefore responsible for deciding when to make a - * "uncertain" suggestion to the downstream time zone detector. - * - * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be - * triggered later if nothing else preempts it. It can be preempted if the provider becomes - * certain (or does anything else that calls {@link - * #makeSuggestion(GeolocationTimeZoneSuggestion)}) within {@link - * Environment#getUncertaintyDelay()}. Preemption causes the scheduled - * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events - * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout - * is not reset each time). - */ - @GuardedBy("mSharedLock") - void handleProviderUncertainty( - @NonNull LocationTimeZoneProvider provider, - @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis, - @NonNull String reason) { - Objects.requireNonNull(provider); - - // Start the uncertainty timeout if needed to ensure the controller will eventually make an - // uncertain suggestion if no success event arrives in time to counteract it. - if (!mUncertaintyTimeoutQueue.hasQueued()) { - debugLog("Starting uncertainty timeout: reason=" + reason); - - Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay(); - mUncertaintyTimeoutQueue.runDelayed( - () -> onProviderUncertaintyTimeout( - provider, uncertaintyStartedElapsedMillis, uncertaintyDelay), - uncertaintyDelay.toMillis()); - } - - if (provider == mPrimaryProvider) { - // (Try to) start the secondary. It could already be started, or enabling might not - // succeed if the provider has previously reported it is perm failed. The uncertainty - // timeout (set above) is used to ensure that an uncertain suggestion will be made if - // the secondary cannot generate a success event in time. - tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); - } - } - - private void onProviderUncertaintyTimeout( - @NonNull LocationTimeZoneProvider provider, - @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis, - @NonNull Duration uncertaintyDelay) { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis(); - - // For the effectiveFromElapsedMillis suggestion property, use the - // uncertaintyStartedElapsedMillis. This is the time when the provider first reported - // uncertainty, i.e. before the uncertainty timeout. - // - // afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when - // the location_time_zone_manager finally confirms that the time zone was uncertain, - // but the suggestion property allows the information to be back-dated, which should - // help when comparing suggestions from different sources. - GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( - uncertaintyStartedElapsedMillis, - "Uncertainty timeout triggered for " + provider.getName() + ":" - + " primary=" + mPrimaryProvider - + ", secondary=" + mSecondaryProvider - + ", uncertaintyStarted=" - + Duration.ofMillis(uncertaintyStartedElapsedMillis) - + ", afterUncertaintyTimeout=" - + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis) - + ", uncertaintyDelay=" + uncertaintyDelay - ); - makeSuggestion(suggestion); - } - } - - @NonNull - private static GeolocationTimeZoneSuggestion createUncertainSuggestion( - @ElapsedRealtimeLong long effectiveFromElapsedMillis, - @NonNull String reason) { - GeolocationTimeZoneSuggestion suggestion = - GeolocationTimeZoneSuggestion.createUncertainSuggestion( - effectiveFromElapsedMillis); - suggestion.addDebugInfo(reason); - return suggestion; - } - - /** - * Clears recorded provider state changes (for use during tests). - */ - void clearRecordedProviderStates() { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - mPrimaryProvider.clearRecordedStates(); - mSecondaryProvider.clearRecordedStates(); - } - } - - /** - * Returns a snapshot of the current controller state for tests. - */ - @NonNull - LocationTimeZoneManagerServiceState getStateForTests() { - mThreadingDomain.assertCurrentThread(); - - synchronized (mSharedLock) { - LocationTimeZoneManagerServiceState.Builder builder = - new LocationTimeZoneManagerServiceState.Builder(); - if (mLastSuggestion != null) { - builder.setLastSuggestion(mLastSuggestion); - } - builder.setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates()) - .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates()); - return builder.build(); - } - } -} diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index ddbeac4e458a..b23f11aa8597 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -147,16 +147,16 @@ public class LocationTimeZoneManagerService extends Binder { /** The shared lock from {@link #mThreadingDomain}. */ @NonNull private final Object mSharedLock; - @NonNull - private final ServiceConfigAccessor mServiceConfigAccessor; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; // Lazily initialized. Can be null if the service has been stopped. @GuardedBy("mSharedLock") - private ControllerImpl mLocationTimeZoneDetectorController; + private LocationTimeZoneProviderController mLocationTimeZoneProviderController; // Lazily initialized. Can be null if the service has been stopped. @GuardedBy("mSharedLock") - private ControllerEnvironmentImpl mEnvironment; + private LocationTimeZoneProviderControllerEnvironmentImpl + mLocationTimeZoneProviderControllerEnvironment; LocationTimeZoneManagerService(@NonNull Context context, @NonNull ServiceConfigAccessor serviceConfigAccessor) { @@ -190,7 +190,7 @@ public class LocationTimeZoneManagerService extends Binder { // Avoid starting the service if it is currently stopped. This is required because // server flags are used by tests to set behavior with the service stopped, and we don't // want the service being restarted after each flag is set. - if (mLocationTimeZoneDetectorController != null) { + if (mLocationTimeZoneProviderController != null) { // Stop and start the service, waiting until completion. stopOnDomainThread(); startOnDomainThread(); @@ -247,8 +247,7 @@ public class LocationTimeZoneManagerService extends Binder { * completion, it cannot be called from the {@code mThreadingDomain} thread. */ void startWithTestProviders(@Nullable String testPrimaryProviderPackageName, - @Nullable String testSecondaryProviderPackageName, - boolean recordProviderStateChanges) { + @Nullable String testSecondaryProviderPackageName, boolean recordStateChanges) { enforceManageTimeZoneDetectorPermission(); if (testPrimaryProviderPackageName == null && testSecondaryProviderPackageName == null) { @@ -263,7 +262,7 @@ public class LocationTimeZoneManagerService extends Binder { testPrimaryProviderPackageName); mServiceConfigAccessor.setTestSecondaryLocationTimeZoneProviderPackageName( testSecondaryProviderPackageName); - mServiceConfigAccessor.setRecordProviderStateChanges(recordProviderStateChanges); + mServiceConfigAccessor.setRecordStateChangesForTests(recordStateChanges); startOnDomainThread(); } }, BLOCKING_OP_WAIT_DURATION_MILLIS); @@ -278,19 +277,32 @@ public class LocationTimeZoneManagerService extends Binder { return; } - if (mLocationTimeZoneDetectorController == null) { + if (mLocationTimeZoneProviderController == null) { LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider(); LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider(); - - ControllerImpl controller = - new ControllerImpl(mThreadingDomain, primary, secondary); - ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( - mThreadingDomain, mServiceConfigAccessor, controller); - ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); + LocationTimeZoneProviderController.MetricsLogger metricsLogger = + new LocationTimeZoneProviderController.MetricsLogger() { + @Override + public void onStateChange( + @LocationTimeZoneProviderController.State String state) { + // TODO b/200279201 - wire this up to metrics code + // No-op. + } + }; + + boolean recordStateChanges = mServiceConfigAccessor.getRecordStateChangesForTests(); + LocationTimeZoneProviderController controller = + new LocationTimeZoneProviderController(mThreadingDomain, metricsLogger, + primary, secondary, recordStateChanges); + LocationTimeZoneProviderControllerEnvironmentImpl environment = + new LocationTimeZoneProviderControllerEnvironmentImpl( + mThreadingDomain, mServiceConfigAccessor, controller); + LocationTimeZoneProviderControllerCallbackImpl callback = + new LocationTimeZoneProviderControllerCallbackImpl(mThreadingDomain); controller.initialize(environment, callback); - mEnvironment = environment; - mLocationTimeZoneDetectorController = controller; + mLocationTimeZoneProviderControllerEnvironment = environment; + mLocationTimeZoneProviderController = controller; } } } @@ -312,11 +324,11 @@ public class LocationTimeZoneManagerService extends Binder { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController != null) { - mLocationTimeZoneDetectorController.destroy(); - mLocationTimeZoneDetectorController = null; - mEnvironment.destroy(); - mEnvironment = null; + if (mLocationTimeZoneProviderController != null) { + mLocationTimeZoneProviderController.destroy(); + mLocationTimeZoneProviderController = null; + mLocationTimeZoneProviderControllerEnvironment.destroy(); + mLocationTimeZoneProviderControllerEnvironment = null; // Clear test state so it won't be used the next time the service is started. mServiceConfigAccessor.resetVolatileTestConfig(); @@ -338,8 +350,8 @@ public class LocationTimeZoneManagerService extends Binder { mThreadingDomain.postAndWait(() -> { synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController != null) { - mLocationTimeZoneDetectorController.clearRecordedProviderStates(); + if (mLocationTimeZoneProviderController != null) { + mLocationTimeZoneProviderController.clearRecordedStates(); } } }, BLOCKING_OP_WAIT_DURATION_MILLIS); @@ -357,10 +369,10 @@ public class LocationTimeZoneManagerService extends Binder { return mThreadingDomain.postAndWait( () -> { synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController == null) { + if (mLocationTimeZoneProviderController == null) { return null; } - return mLocationTimeZoneDetectorController.getStateForTests(); + return mLocationTimeZoneProviderController.getStateForTests(); } }, BLOCKING_OP_WAIT_DURATION_MILLIS); @@ -390,10 +402,10 @@ public class LocationTimeZoneManagerService extends Binder { mSecondaryProviderConfig.dump(ipw, args); ipw.decreaseIndent(); - if (mLocationTimeZoneDetectorController == null) { + if (mLocationTimeZoneProviderController == null) { ipw.println("{Stopped}"); } else { - mLocationTimeZoneDetectorController.dump(ipw, args); + mLocationTimeZoneProviderController.dump(ipw, args); } ipw.decreaseIndent(); } @@ -447,10 +459,9 @@ public class LocationTimeZoneManagerService extends Binder { ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex); return new BinderLocationTimeZoneProvider( providerMetricsLogger, mThreadingDomain, mName, proxy, - mServiceConfigAccessor.getRecordProviderStateChanges()); + mServiceConfigAccessor.getRecordStateChangesForTests()); } - @GuardedBy("mSharedLock") @Override public void dump(IndentingPrintWriter ipw, String[] args) { ipw.printf("getMode()=%s\n", getMode()); diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java index 113926a265f5..1f752f45fd51 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import java.util.ArrayList; import java.util.Collections; @@ -30,22 +31,35 @@ import java.util.Objects; /** A snapshot of the location time zone manager service's state for tests. */ final class LocationTimeZoneManagerServiceState { + private final @State String mControllerState; @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion; + @NonNull private final List<@State String> mControllerStates; @NonNull private final List<ProviderState> mPrimaryProviderStates; @NonNull private final List<ProviderState> mSecondaryProviderStates; LocationTimeZoneManagerServiceState(@NonNull Builder builder) { + mControllerState = builder.mControllerState; mLastSuggestion = builder.mLastSuggestion; + mControllerStates = Objects.requireNonNull(builder.mControllerStates); mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates); mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates); } + public @State String getControllerState() { + return mControllerState; + } + @Nullable public GeolocationTimeZoneSuggestion getLastSuggestion() { return mLastSuggestion; } @NonNull + public List<@State String> getControllerStates() { + return mControllerStates; + } + + @NonNull public List<ProviderState> getPrimaryProviderStates() { return Collections.unmodifiableList(mPrimaryProviderStates); } @@ -58,7 +72,9 @@ final class LocationTimeZoneManagerServiceState { @Override public String toString() { return "LocationTimeZoneManagerServiceState{" - + "mLastSuggestion=" + mLastSuggestion + + "mControllerState=" + mControllerState + + ", mLastSuggestion=" + mLastSuggestion + + ", mControllerStates=" + mControllerStates + ", mPrimaryProviderStates=" + mPrimaryProviderStates + ", mSecondaryProviderStates=" + mSecondaryProviderStates + '}'; @@ -66,17 +82,31 @@ final class LocationTimeZoneManagerServiceState { static final class Builder { + private @State String mControllerState; private GeolocationTimeZoneSuggestion mLastSuggestion; + private List<@State String> mControllerStates; private List<ProviderState> mPrimaryProviderStates; private List<ProviderState> mSecondaryProviderStates; @NonNull + public Builder setControllerState(@State String stateEnum) { + mControllerState = stateEnum; + return this; + } + + @NonNull Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) { mLastSuggestion = Objects.requireNonNull(lastSuggestion); return this; } @NonNull + public Builder setStateChanges(@NonNull List<@State String> states) { + mControllerStates = new ArrayList<>(states); + return this; + } + + @NonNull Builder setPrimaryProviderStateChanges(@NonNull List<ProviderState> primaryProviderStates) { mPrimaryProviderStates = new ArrayList<>(primaryProviderStates); return this; diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 6c9e174b0fc5..60bbea77b636 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -40,6 +40,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,6 +63,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -245,6 +254,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { outputStream.end(lastSuggestionToken); } + writeControllerStates(outputStream, state.getControllerStates()); writeProviderStates(outputStream, state.getPrimaryProviderStates(), "primary_provider_states", LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES); @@ -256,6 +266,37 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { return 0; } + private static void writeControllerStates(DualDumpOutputStream outputStream, + List<@State String> states) { + for (@State String state : states) { + outputStream.write("controller_states", + LocationTimeZoneManagerServiceStateProto.CONTROLLER_STATES, + convertControllerStateToProtoEnum(state)); + } + } + + private static int convertControllerStateToProtoEnum(@State String state) { + switch (state) { + case STATE_PROVIDERS_INITIALIZING: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_PROVIDERS_INITIALIZING; + case STATE_STOPPED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_STOPPED; + case STATE_INITIALIZING: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_INITIALIZING; + case STATE_UNCERTAIN: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_UNCERTAIN; + case STATE_CERTAIN: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_CERTAIN; + case STATE_FAILED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_FAILED; + case STATE_DESTROYED: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_DESTROYED; + case STATE_UNKNOWN: + default: + return LocationTimeZoneManagerProto.CONTROLLER_STATE_UNKNOWN; + } + } + private static void writeProviderStates(DualDumpOutputStream outputStream, List<LocationTimeZoneProvider.ProviderState> providerStates, String fieldName, long fieldId) { diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index 4dff02e8ab6f..5d7730ab9aac 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -16,31 +16,64 @@ package com.android.server.timezonedetector.location; +import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; +import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; +import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; + +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; + import android.annotation.DurationMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; -import android.os.Handler; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.service.timezone.TimeZoneProviderEvent; +import android.service.timezone.TimeZoneProviderSuggestion; +import android.util.IndentingPrintWriter; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; -import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; +import com.android.server.timezonedetector.ReferenceWithHistory; +import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.time.Duration; +import java.util.ArrayList; import java.util.Objects; /** - * An base class for the component responsible handling events from {@link - * LocationTimeZoneProvider}s and synthesizing time zone ID suggestions for sending to the time zone - * detector. This interface primarily exists to extract testable detection logic, i.e. with - * a minimal number of threading considerations or dependencies on Android infrastructure. + * The component responsible handling events from {@link LocationTimeZoneProvider}s and synthesizing + * time zone ID suggestions for sending to the time zone detector. + * + * <p>This class primarily exists to extract unit-testable logic from the surrounding service class, + * i.e. with a minimal number of threading considerations or direct dependencies on Android + * infrastructure. + * + * <p>This class supports a primary and a secondary {@link LocationTimeZoneProvider}. The primary is + * used until it fails or becomes uncertain. The secondary will then be started. The controller will + * immediately make suggestions based on "certain" {@link TimeZoneProviderEvent}s, i.e. events that + * demonstrate the provider is certain what the time zone is. The controller will not make immediate + * suggestions based on "uncertain" events, giving providers time to change their mind. This also + * gives the secondary provider time to initialize when the primary becomes uncertain. * * <p>The controller interacts with the following components: * <ul> - * <li>The surrounding service, which calls {@link #initialize(Environment, Callback)} and - * {@link #onConfigurationInternalChanged()}.</li> - * <li>The {@link Environment} through which obtains information it needs.</li> + * <li>The surrounding service, which calls {@link #initialize(Environment, Callback)}. + * <li>The {@link Environment} through which it obtains information it needs.</li> * <li>The {@link Callback} through which it makes time zone suggestions.</li> * <li>Any {@link LocationTimeZoneProvider} instances it owns, which communicate via the * {@link LocationTimeZoneProvider.ProviderListener#onProviderStateChange(ProviderState)} @@ -49,8 +82,9 @@ import java.util.Objects; * * <p>All incoming calls except for {@link * LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be - * made on the {@link Handler} thread of the {@link ThreadingDomain} passed to {@link - * #LocationTimeZoneProviderController(ThreadingDomain)}. + * made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link + * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider, + * LocationTimeZoneProvider)}. * * <p>Provider / controller integration notes: * @@ -59,43 +93,718 @@ import java.util.Objects; * different from the certainty that there are no time zone IDs for the current location. A provider * can be certain about there being no time zone IDs for a location for good reason, e.g. for * disputed areas and oceans. Distinguishing uncertainty allows the controller to try other - * providers (or give up), where as certainty means it should not. + * providers (or give up), whereas certainty means it should not. * * <p>A provider can fail permanently. A permanent failure will stop the provider until next * boot. */ -abstract class LocationTimeZoneProviderController implements Dumpable { +class LocationTimeZoneProviderController implements Dumpable { + + // String is used for easier logging / interpretation in bug reports Vs int. + @StringDef(prefix = "STATE_", + value = { STATE_UNKNOWN, STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, + STATE_INITIALIZING, STATE_UNCERTAIN, STATE_CERTAIN, STATE_FAILED, + STATE_DESTROYED }) + @Retention(RetentionPolicy.SOURCE) + @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) + @interface State {} + + /** The state used for an uninitialized controller. */ + static final @State String STATE_UNKNOWN = "UNKNOWN"; + + /** + * A state used while the location time zone providers are initializing. Enables detection + * / avoidance of unwanted fail-over behavior before both providers are initialized. + */ + static final @State String STATE_PROVIDERS_INITIALIZING = "PROVIDERS_INITIALIZING"; + /** An inactive state: Detection is disabled. */ + static final @State String STATE_STOPPED = "STOPPED"; + /** An active state: No suggestion has yet been made. */ + static final @State String STATE_INITIALIZING = "INITIALIZING"; + /** An active state: The last suggestion was "uncertain". */ + static final @State String STATE_UNCERTAIN = "UNCERTAIN"; + /** An active state: The last suggestion was "certain". */ + static final @State String STATE_CERTAIN = "CERTAIN"; + /** An inactive state: The location time zone providers have failed. */ + static final @State String STATE_FAILED = "FAILED"; + /** An inactive state: The controller is destroyed. */ + static final @State String STATE_DESTROYED = "DESTROYED"; + + @NonNull private final ThreadingDomain mThreadingDomain; + @NonNull private final Object mSharedLock; + /** + * Used for scheduling uncertainty timeouts, i.e. after a provider has reported uncertainty. + * This timeout is not provider-specific: it is started when the controller becomes uncertain + * due to events it has received from one or other provider. + */ + @NonNull private final SingleRunnableQueue mUncertaintyTimeoutQueue; + + @NonNull private final MetricsLogger mMetricsLogger; + @NonNull private final LocationTimeZoneProvider mPrimaryProvider; + @NonNull private final LocationTimeZoneProvider mSecondaryProvider; + + @GuardedBy("mSharedLock") + // Non-null after initialize() + private ConfigurationInternal mCurrentUserConfiguration; + + @GuardedBy("mSharedLock") + // Non-null after initialize() + private Environment mEnvironment; - @NonNull protected final ThreadingDomain mThreadingDomain; - @NonNull protected final Object mSharedLock; + @GuardedBy("mSharedLock") + // Non-null after initialize() + private Callback mCallback; + + /** Usually {@code false} but can be set to {@code true} to record state changes for testing. */ + private final boolean mRecordStateChanges; + + @GuardedBy("mSharedLock") + @NonNull + private final ArrayList<@State String> mRecordedStates = new ArrayList<>(0); + + /** + * The current state. This is primarily for metrics / reporting of how long the controller + * spends active / inactive during a period. There is overlap with the provider states, but + * providers operate independently of each other, so this can help to understand how long the + * geo detection system overall was certain or uncertain when multiple providers might have been + * enabled concurrently. + */ + @GuardedBy("mSharedLock") + private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10); - LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain) { + /** Contains the last suggestion actually made, if there is one. */ + @GuardedBy("mSharedLock") + @Nullable + private GeolocationTimeZoneSuggestion mLastSuggestion; + + LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain, + @NonNull MetricsLogger metricsLogger, + @NonNull LocationTimeZoneProvider primaryProvider, + @NonNull LocationTimeZoneProvider secondaryProvider, + boolean recordStateChanges) { mThreadingDomain = Objects.requireNonNull(threadingDomain); mSharedLock = threadingDomain.getLockObject(); + mUncertaintyTimeoutQueue = threadingDomain.createSingleRunnableQueue(); + mMetricsLogger = Objects.requireNonNull(metricsLogger); + mPrimaryProvider = Objects.requireNonNull(primaryProvider); + mSecondaryProvider = Objects.requireNonNull(secondaryProvider); + mRecordStateChanges = recordStateChanges; + + synchronized (mSharedLock) { + mState.set(STATE_UNKNOWN); + } } /** * Called to initialize the controller during boot. Called once only. * {@link LocationTimeZoneProvider#initialize} must be called by this method. */ - abstract void initialize(@NonNull Environment environment, @NonNull Callback callback); + void initialize(@NonNull Environment environment, @NonNull Callback callback) { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + debugLog("initialize()"); + mEnvironment = Objects.requireNonNull(environment); + mCallback = Objects.requireNonNull(callback); + mCurrentUserConfiguration = environment.getCurrentUserConfigurationInternal(); + + LocationTimeZoneProvider.ProviderListener providerListener = + LocationTimeZoneProviderController.this::onProviderStateChange; + setState(STATE_PROVIDERS_INITIALIZING); + mPrimaryProvider.initialize(providerListener); + mSecondaryProvider.initialize(providerListener); + setState(STATE_STOPPED); + + alterProvidersStartedStateIfRequired( + null /* oldConfiguration */, mCurrentUserConfiguration); + } + } /** * Called when the content of the {@link ConfigurationInternal} may have changed. The receiver * should call {@link Environment#getCurrentUserConfigurationInternal()} to get the current * user's config. This call must be made on the {@link ThreadingDomain} handler thread. */ - abstract void onConfigurationInternalChanged(); + void onConfigurationInternalChanged() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + debugLog("onConfigChanged()"); + + ConfigurationInternal oldConfig = mCurrentUserConfiguration; + ConfigurationInternal newConfig = mEnvironment.getCurrentUserConfigurationInternal(); + mCurrentUserConfiguration = newConfig; + + if (!newConfig.equals(oldConfig)) { + if (newConfig.getUserId() != oldConfig.getUserId()) { + // If the user changed, stop the providers if needed. They may be re-started + // for the new user immediately afterwards if their settings allow. + debugLog("User changed. old=" + oldConfig.getUserId() + + ", new=" + newConfig.getUserId() + ": Stopping providers"); + stopProviders(); + + alterProvidersStartedStateIfRequired(null /* oldConfiguration */, newConfig); + } else { + alterProvidersStartedStateIfRequired(oldConfig, newConfig); + } + } + } + } @VisibleForTesting - abstract boolean isUncertaintyTimeoutSet(); + boolean isUncertaintyTimeoutSet() { + return mUncertaintyTimeoutQueue.hasQueued(); + } @VisibleForTesting @DurationMillisLong - abstract long getUncertaintyTimeoutDelayMillis(); + long getUncertaintyTimeoutDelayMillis() { + return mUncertaintyTimeoutQueue.getQueuedDelayMillis(); + } /** Called if the geolocation time zone detection is being reconfigured. */ - abstract void destroy(); + void destroy() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + stopProviders(); + + // Enter destroyed state. + mPrimaryProvider.destroy(); + mSecondaryProvider.destroy(); + setState(STATE_DESTROYED); + } + } + + /** + * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated + * with state changes. + */ + @GuardedBy("mSharedLock") + private void setState(@State String state) { + if (!Objects.equals(mState.get(), state)) { + mState.set(state); + if (mRecordStateChanges) { + mRecordedStates.add(state); + } + mMetricsLogger.onStateChange(state); + } + } + + @GuardedBy("mSharedLock") + private void stopProviders() { + stopProviderIfStarted(mPrimaryProvider); + stopProviderIfStarted(mSecondaryProvider); + + // By definition, if both providers are stopped, the controller is uncertain. + cancelUncertaintyTimeout(); + + // If a previous "certain" suggestion has been made, then a new "uncertain" + // suggestion must now be made to indicate the controller {does not / no longer has} + // an opinion and will not be sending further updates (until at least the providers are + // re-started). + if (Objects.equals(mState.get(), STATE_CERTAIN)) { + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + mEnvironment.elapsedRealtimeMillis(), "Providers are stopping"); + makeSuggestion(suggestion, STATE_UNCERTAIN); + } + setState(STATE_STOPPED); + } + + @GuardedBy("mSharedLock") + private void stopProviderIfStarted(@NonNull LocationTimeZoneProvider provider) { + if (provider.getCurrentState().isStarted()) { + stopProvider(provider); + } + } + + @GuardedBy("mSharedLock") + private void stopProvider(@NonNull LocationTimeZoneProvider provider) { + ProviderState providerState = provider.getCurrentState(); + switch (providerState.stateEnum) { + case PROVIDER_STATE_STOPPED: { + debugLog("No need to stop " + provider + ": already stopped"); + break; + } + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + debugLog("Stopping " + provider); + provider.stopUpdates(); + break; + } + case PROVIDER_STATE_PERM_FAILED: + case PROVIDER_STATE_DESTROYED: { + debugLog("Unable to stop " + provider + ": it is terminated."); + break; + } + default: { + warnLog("Unknown provider state: " + provider); + break; + } + } + } + + /** + * Sets the providers into the correct started/stopped state for the {@code newConfiguration} + * and, if there is a provider state change, makes any suggestions required to inform the + * downstream time zone detection code. + * + * <p>This is a utility method that exists to avoid duplicated logic for the various cases when + * provider started / stopped state may need to be set or changed, e.g. during initialization + * or when a new configuration has been received. + */ + @GuardedBy("mSharedLock") + private void alterProvidersStartedStateIfRequired( + @Nullable ConfigurationInternal oldConfiguration, + @NonNull ConfigurationInternal newConfiguration) { + + // Provider started / stopped states only need to be changed if geoDetectionEnabled has + // changed. + boolean oldGeoDetectionEnabled = oldConfiguration != null + && oldConfiguration.getGeoDetectionEnabledBehavior(); + boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior(); + if (oldGeoDetectionEnabled == newGeoDetectionEnabled) { + return; + } + + // The check above ensures that the logic below only executes if providers are going from + // {started *} -> {stopped}, or {stopped} -> {started initializing}. If this changes in + // future and there could be {started *} -> {started *} cases, or cases where the provider + // can't be assumed to go straight to the {started initializing} state, then the logic below + // would need to cover extra conditions, for example: + // 1) If the primary is in {started uncertain}, the secondary should be started. + // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty + // timeout started when the primary entered {started uncertain} should be cancelled. + + if (newGeoDetectionEnabled) { + setState(STATE_INITIALIZING); + + // Try to start the primary provider. + tryStartProvider(mPrimaryProvider, newConfiguration); + + // The secondary should only ever be started if the primary now isn't started (i.e. it + // couldn't become {started initializing} because it is {perm failed}). + ProviderState newPrimaryState = mPrimaryProvider.getCurrentState(); + if (!newPrimaryState.isStarted()) { + // If the primary provider is {perm failed} then the controller must try to start + // the secondary. + tryStartProvider(mSecondaryProvider, newConfiguration); + + ProviderState newSecondaryState = mSecondaryProvider.getCurrentState(); + if (!newSecondaryState.isStarted()) { + // If both providers are {perm failed} then the controller immediately + // reports uncertain. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + mEnvironment.elapsedRealtimeMillis(), + "Providers are failed:" + + " primary=" + mPrimaryProvider.getCurrentState() + + " secondary=" + mPrimaryProvider.getCurrentState()); + makeSuggestion(suggestion, STATE_FAILED); + } + } + } else { + stopProviders(); + } + } + + @GuardedBy("mSharedLock") + private void tryStartProvider(@NonNull LocationTimeZoneProvider provider, + @NonNull ConfigurationInternal configuration) { + ProviderState providerState = provider.getCurrentState(); + switch (providerState.stateEnum) { + case PROVIDER_STATE_STOPPED: { + debugLog("Enabling " + provider); + provider.startUpdates(configuration, + mEnvironment.getProviderInitializationTimeout(), + mEnvironment.getProviderInitializationTimeoutFuzz(), + mEnvironment.getProviderEventFilteringAgeThreshold()); + break; + } + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + debugLog("No need to start " + provider + ": already started"); + break; + } + case PROVIDER_STATE_PERM_FAILED: + case PROVIDER_STATE_DESTROYED: { + debugLog("Unable to start " + provider + ": it is terminated"); + break; + } + default: { + throw new IllegalStateException("Unknown provider state:" + + " provider=" + provider); + } + } + } + + void onProviderStateChange(@NonNull ProviderState providerState) { + mThreadingDomain.assertCurrentThread(); + LocationTimeZoneProvider provider = providerState.provider; + assertProviderKnown(provider); + + synchronized (mSharedLock) { + // Ignore provider state changes during initialization. e.g. if the primary provider + // moves to PROVIDER_STATE_PERM_FAILED during initialization, the secondary will not + // be ready to take over yet. + if (Objects.equals(mState.get(), STATE_PROVIDERS_INITIALIZING)) { + warnLog("onProviderStateChange: Ignoring provider state change because both" + + " providers have not yet completed initialization." + + " providerState=" + providerState); + return; + } + + switch (providerState.stateEnum) { + case PROVIDER_STATE_STARTED_INITIALIZING: + case PROVIDER_STATE_STOPPED: + case PROVIDER_STATE_DESTROYED: { + // This should never happen: entering initializing, stopped or destroyed are + // triggered by the controller so and should not trigger a state change + // callback. + warnLog("onProviderStateChange: Unexpected state change for provider," + + " provider=" + provider); + break; + } + case PROVIDER_STATE_STARTED_CERTAIN: + case PROVIDER_STATE_STARTED_UNCERTAIN: { + // These are valid and only happen if an event is received while the provider is + // started. + debugLog("onProviderStateChange: Received notification of a state change while" + + " started, provider=" + provider); + handleProviderStartedStateChange(providerState); + break; + } + case PROVIDER_STATE_PERM_FAILED: { + debugLog("Received notification of permanent failure for" + + " provider=" + provider); + handleProviderFailedStateChange(providerState); + break; + } + default: { + warnLog("onProviderStateChange: Unexpected provider=" + provider); + } + } + } + } + + private void assertProviderKnown(@NonNull LocationTimeZoneProvider provider) { + if (provider != mPrimaryProvider && provider != mSecondaryProvider) { + throw new IllegalArgumentException("Unknown provider: " + provider); + } + } + + /** + * Called when a provider has reported that it has failed permanently. + */ + @GuardedBy("mSharedLock") + private void handleProviderFailedStateChange(@NonNull ProviderState providerState) { + LocationTimeZoneProvider failedProvider = providerState.provider; + ProviderState primaryCurrentState = mPrimaryProvider.getCurrentState(); + ProviderState secondaryCurrentState = mSecondaryProvider.getCurrentState(); + + // If a provider has failed, the other may need to be started. + if (failedProvider == mPrimaryProvider) { + if (!secondaryCurrentState.isTerminated()) { + // Try to start the secondary. This does nothing if the provider is already + // started, and will leave the provider in {started initializing} if the provider is + // stopped. + tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); + } + } else if (failedProvider == mSecondaryProvider) { + // No-op: The secondary will only be active if the primary is uncertain or is + // terminated. So, there the primary should not need to be started when the secondary + // fails. + if (primaryCurrentState.stateEnum != PROVIDER_STATE_STARTED_UNCERTAIN + && !primaryCurrentState.isTerminated()) { + warnLog("Secondary provider unexpected reported a failure:" + + " failed provider=" + failedProvider.getName() + + ", primary provider=" + mPrimaryProvider + + ", secondary provider=" + mSecondaryProvider); + } + } + + // If both providers are now terminated, the controller needs to tell the next component in + // the time zone detection process. + if (primaryCurrentState.isTerminated() && secondaryCurrentState.isTerminated()) { + + // If both providers are newly terminated then the controller is uncertain by definition + // and it will never recover so it can send a suggestion immediately. + cancelUncertaintyTimeout(); + + // If both providers are now terminated, then a suggestion must be sent informing the + // time zone detector that there are no further updates coming in the future. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + mEnvironment.elapsedRealtimeMillis(), + "Both providers are terminated:" + + " primary=" + primaryCurrentState.provider + + ", secondary=" + secondaryCurrentState.provider); + makeSuggestion(suggestion, STATE_FAILED); + } + } + + /** + * Called when a provider has changed state but just moved from one started state to another + * started state, usually as a result of a new {@link TimeZoneProviderEvent} being received. + * However, there are rare cases where the event can also be null. + */ + @GuardedBy("mSharedLock") + private void handleProviderStartedStateChange(@NonNull ProviderState providerState) { + LocationTimeZoneProvider provider = providerState.provider; + TimeZoneProviderEvent event = providerState.event; + if (event == null) { + // Implicit uncertainty, i.e. where the provider is started, but a problem has been + // detected without having received an event. For example, if the process has detected + // the loss of a binder-based provider, or initialization took too long. This is treated + // the same as explicit uncertainty, i.e. where the provider has explicitly told this + // process it is uncertain. + long uncertaintyStartedElapsedMillis = mEnvironment.elapsedRealtimeMillis(); + handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis, + "provider=" + provider + ", implicit uncertainty, event=null"); + return; + } + + if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) { + // This should not happen: the provider should not be in an started state if the user + // does not have geodetection enabled. + warnLog("Provider=" + provider + " is started, but" + + " currentUserConfiguration=" + mCurrentUserConfiguration + + " suggests it shouldn't be."); + } + + switch (event.getType()) { + case EVENT_TYPE_PERMANENT_FAILURE: { + // This shouldn't happen. A provider cannot be started and have this event type. + warnLog("Provider=" + provider + " is started, but event suggests it shouldn't be"); + break; + } + case EVENT_TYPE_UNCERTAIN: { + long uncertaintyStartedElapsedMillis = event.getCreationElapsedMillis(); + handleProviderUncertainty(provider, uncertaintyStartedElapsedMillis, + "provider=" + provider + ", explicit uncertainty. event=" + event); + break; + } + case EVENT_TYPE_SUGGESTION: { + handleProviderSuggestion(provider, event); + break; + } + default: { + warnLog("Unknown eventType=" + event.getType()); + break; + } + } + } + + /** + * Called when a provider has become "certain" about the time zone(s). + */ + @GuardedBy("mSharedLock") + private void handleProviderSuggestion( + @NonNull LocationTimeZoneProvider provider, + @NonNull TimeZoneProviderEvent providerEvent) { + + // By definition, the controller is now certain. + cancelUncertaintyTimeout(); + + if (provider == mPrimaryProvider) { + stopProviderIfStarted(mSecondaryProvider); + } + + TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion(); + + // For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's + // suggestion (which indicates the time when the provider detected the location used to + // establish the time zone). + // + // An alternative would be to use the current time or the providerEvent creation time, but + // this would hinder the ability for the time_zone_detector to judge which suggestions are + // based on newer information when comparing suggestions between different sources. + long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis(); + GeolocationTimeZoneSuggestion geoSuggestion = + GeolocationTimeZoneSuggestion.createCertainSuggestion( + effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds()); + + String debugInfo = "Event received provider=" + provider + + ", providerEvent=" + providerEvent + + ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis(); + geoSuggestion.addDebugInfo(debugInfo); + makeSuggestion(geoSuggestion, STATE_CERTAIN); + } + + @Override + public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) { + synchronized (mSharedLock) { + ipw.println("LocationTimeZoneProviderController:"); + + ipw.increaseIndent(); // level 1 + ipw.println("mCurrentUserConfiguration=" + mCurrentUserConfiguration); + ipw.println("providerInitializationTimeout=" + + mEnvironment.getProviderInitializationTimeout()); + ipw.println("providerInitializationTimeoutFuzz=" + + mEnvironment.getProviderInitializationTimeoutFuzz()); + ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay()); + ipw.println("mState=" + mState.get()); + ipw.println("mLastSuggestion=" + mLastSuggestion); + + ipw.println("State history:"); + ipw.increaseIndent(); // level 2 + mState.dump(ipw); + ipw.decreaseIndent(); // level 2 + + ipw.println("Primary Provider:"); + ipw.increaseIndent(); // level 2 + mPrimaryProvider.dump(ipw, args); + ipw.decreaseIndent(); // level 2 + + ipw.println("Secondary Provider:"); + ipw.increaseIndent(); // level 2 + mSecondaryProvider.dump(ipw, args); + ipw.decreaseIndent(); // level 2 + + ipw.decreaseIndent(); // level 1 + } + } + + /** + * Sends an immediate suggestion and enters a new state if needed. This method updates + * mLastSuggestion and changes mStateEnum / reports the new state for metrics. + */ + @GuardedBy("mSharedLock") + private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion, + @State String newState) { + debugLog("makeSuggestion: suggestion=" + suggestion); + mCallback.suggest(suggestion); + mLastSuggestion = suggestion; + setState(newState); + } + + /** Clears the uncertainty timeout. */ + @GuardedBy("mSharedLock") + private void cancelUncertaintyTimeout() { + mUncertaintyTimeoutQueue.cancel(); + } + + /** + * Called when a provider has become "uncertain" about the time zone. + * + * <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as + * this enables the most flexibility for the controller to start other providers when there are + * multiple ones available. The controller is therefore responsible for deciding when to make a + * "uncertain" suggestion to the downstream time zone detector. + * + * <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be + * triggered later if nothing else preempts it. It can be preempted if the provider becomes + * certain (or does anything else that calls {@link + * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link + * Environment#getUncertaintyDelay()}. Preemption causes the scheduled + * "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events + * within the uncertainty delay period, those events are effectively ignored (i.e. the timeout + * is not reset each time). + */ + @GuardedBy("mSharedLock") + void handleProviderUncertainty( + @NonNull LocationTimeZoneProvider provider, + @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis, + @NonNull String reason) { + Objects.requireNonNull(provider); + + // Start the uncertainty timeout if needed to ensure the controller will eventually make an + // uncertain suggestion if no success event arrives in time to counteract it. + if (!mUncertaintyTimeoutQueue.hasQueued()) { + debugLog("Starting uncertainty timeout: reason=" + reason); + + Duration uncertaintyDelay = mEnvironment.getUncertaintyDelay(); + mUncertaintyTimeoutQueue.runDelayed( + () -> onProviderUncertaintyTimeout( + provider, uncertaintyStartedElapsedMillis, uncertaintyDelay), + uncertaintyDelay.toMillis()); + } + + if (provider == mPrimaryProvider) { + // (Try to) start the secondary. It could already be started, or enabling might not + // succeed if the provider has previously reported it is perm failed. The uncertainty + // timeout (set above) is used to ensure that an uncertain suggestion will be made if + // the secondary cannot generate a success event in time. + tryStartProvider(mSecondaryProvider, mCurrentUserConfiguration); + } + } + + private void onProviderUncertaintyTimeout( + @NonNull LocationTimeZoneProvider provider, + @ElapsedRealtimeLong long uncertaintyStartedElapsedMillis, + @NonNull Duration uncertaintyDelay) { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis(); + + // For the effectiveFromElapsedMillis suggestion property, use the + // uncertaintyStartedElapsedMillis. This is the time when the provider first reported + // uncertainty, i.e. before the uncertainty timeout. + // + // afterUncertaintyTimeoutElapsedMillis could be used instead, which is the time when + // the location_time_zone_manager finally confirms that the time zone was uncertain, + // but the suggestion property allows the information to be back-dated, which should + // help when comparing suggestions from different sources. + GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion( + uncertaintyStartedElapsedMillis, + "Uncertainty timeout triggered for " + provider.getName() + ":" + + " primary=" + mPrimaryProvider + + ", secondary=" + mSecondaryProvider + + ", uncertaintyStarted=" + + Duration.ofMillis(uncertaintyStartedElapsedMillis) + + ", afterUncertaintyTimeout=" + + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis) + + ", uncertaintyDelay=" + uncertaintyDelay + ); + makeSuggestion(suggestion, STATE_UNCERTAIN); + } + } + + @NonNull + private static GeolocationTimeZoneSuggestion createUncertainSuggestion( + @ElapsedRealtimeLong long effectiveFromElapsedMillis, + @NonNull String reason) { + GeolocationTimeZoneSuggestion suggestion = + GeolocationTimeZoneSuggestion.createUncertainSuggestion( + effectiveFromElapsedMillis); + suggestion.addDebugInfo(reason); + return suggestion; + } + + /** + * Clears recorded controller and provider state changes (for use during tests). + */ + void clearRecordedStates() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + mRecordedStates.clear(); + mPrimaryProvider.clearRecordedStates(); + mSecondaryProvider.clearRecordedStates(); + } + } + + /** + * Returns a snapshot of the current controller state for tests. + */ + @NonNull + LocationTimeZoneManagerServiceState getStateForTests() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + LocationTimeZoneManagerServiceState.Builder builder = + new LocationTimeZoneManagerServiceState.Builder(); + if (mLastSuggestion != null) { + builder.setLastSuggestion(mLastSuggestion); + } + builder.setControllerState(mState.get()) + .setStateChanges(mRecordedStates) + .setPrimaryProviderStateChanges(mPrimaryProvider.getRecordedStates()) + .setSecondaryProviderStateChanges(mSecondaryProvider.getRecordedStates()); + return builder.build(); + } + } /** * Used by {@link LocationTimeZoneProviderController} to obtain information from the surrounding @@ -167,4 +876,12 @@ abstract class LocationTimeZoneProviderController implements Dumpable { */ abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion); } + + /** + * Used by {@link LocationTimeZoneProviderController} to record events for metrics / telemetry. + */ + interface MetricsLogger { + /** Called when the controller's state changes. */ + void onStateChange(@State String stateEnum); + } } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java index 46eaad075b54..0c751aaa62c7 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java @@ -24,11 +24,12 @@ import com.android.server.timezonedetector.TimeZoneDetectorInternal; /** * The real implementation of {@link LocationTimeZoneProviderController.Callback} used by - * {@link ControllerImpl} to interact with other server components. + * {@link LocationTimeZoneProviderController} to interact with other server components. */ -class ControllerCallbackImpl extends LocationTimeZoneProviderController.Callback { +class LocationTimeZoneProviderControllerCallbackImpl + extends LocationTimeZoneProviderController.Callback { - ControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) { + LocationTimeZoneProviderControllerCallbackImpl(@NonNull ThreadingDomain threadingDomain) { super(threadingDomain); } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java index 33cdc5f66def..e7d16c85b1c6 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java @@ -29,14 +29,15 @@ import java.util.Objects; /** * The real implementation of {@link LocationTimeZoneProviderController.Environment} used by - * {@link ControllerImpl} to interact with other server components. + * {@link LocationTimeZoneProviderController} to interact with other server components. */ -class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment { +class LocationTimeZoneProviderControllerEnvironmentImpl + extends LocationTimeZoneProviderController.Environment { @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener; - ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, + LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull LocationTimeZoneProviderController controller) { super(threadingDomain); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e69acc3071fb..59b6a08ef150 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -3012,32 +3012,47 @@ public final class TvInputManagerService extends SystemService { public void addHardwareInput(int deviceId, TvInputInfo inputInfo) { ensureHardwarePermission(); ensureValidInput(inputInfo); - synchronized (mLock) { - mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo); - addHardwareInputLocked(inputInfo); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo); + addHardwareInputLocked(inputInfo); + } + } finally { + Binder.restoreCallingIdentity(identity); } } public void addHdmiInput(int id, TvInputInfo inputInfo) { ensureHardwarePermission(); ensureValidInput(inputInfo); - synchronized (mLock) { - mTvInputHardwareManager.addHdmiInput(id, inputInfo); - addHardwareInputLocked(inputInfo); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + mTvInputHardwareManager.addHdmiInput(id, inputInfo); + addHardwareInputLocked(inputInfo); + } + } finally { + Binder.restoreCallingIdentity(identity); } } public void removeHardwareInput(String inputId) { ensureHardwarePermission(); - synchronized (mLock) { - ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; - if (removed) { - buildTvInputListLocked(mUserId, null); - mTvInputHardwareManager.removeHardwareInput(inputId); - } else { - Slog.e(TAG, "failed to remove input " + inputId); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); + boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; + if (removed) { + buildTvInputListLocked(mUserId, null); + mTvInputHardwareManager.removeHardwareInput(inputId); + } else { + Slog.e(TAG, "failed to remove input " + inputId); + } } + } finally { + Binder.restoreCallingIdentity(identity); } } } diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java index dfb07523d830..c7b6421cd0c9 100644 --- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java @@ -28,6 +28,8 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.media.tv.BroadcastInfoRequest; +import android.media.tv.BroadcastInfoResponse; import android.media.tv.interactive.ITvIAppClient; import android.media.tv.interactive.ITvIAppManager; import android.media.tv.interactive.ITvIAppManagerCallback; @@ -47,6 +49,7 @@ import android.os.UserManager; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; +import android.view.InputChannel; import android.view.Surface; import com.android.internal.annotations.GuardedBy; @@ -611,14 +614,14 @@ public class TvIAppManagerService extends SystemService { if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) { // Only current user and its running profiles can create sessions. // Let the client get onConnectionFailed callback for this case. - sendSessionTokenToClientLocked(client, iAppServiceId, null, seq); + sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq); return; } UserState userState = getOrCreateUserStateLocked(resolvedUserId); TvIAppState iAppState = userState.mIAppMap.get(iAppServiceId); if (iAppState == null) { Slogf.w(TAG, "Failed to find state for iAppServiceId=" + iAppServiceId); - sendSessionTokenToClientLocked(client, iAppServiceId, null, seq); + sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq); return; } ServiceState serviceState = @@ -631,7 +634,7 @@ public class TvIAppManagerService extends SystemService { } // Send a null token immediately while reconnecting. if (serviceState.mReconnecting) { - sendSessionTokenToClientLocked(client, iAppServiceId, null, seq); + sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq); return; } @@ -744,6 +747,29 @@ public class TvIAppManagerService extends SystemService { } @Override + public void notifyBroadcastInfoResponse(IBinder sessionToken, + BroadcastInfoResponse response, int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId, + "notifyBroadcastInfoResponse"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + SessionState sessionState = getSessionStateLocked(sessionToken, callingUid, + resolvedUserId); + getSessionLocked(sessionState).notifyBroadcastInfoResponse(response); + } catch (RemoteException | SessionNotFoundException e) { + Slogf.e(TAG, "error in notifyBroadcastInfoResponse", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void registerCallback(final ITvIAppManagerCallback callback, int userId) { int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); @@ -780,9 +806,9 @@ public class TvIAppManagerService extends SystemService { @GuardedBy("mLock") private void sendSessionTokenToClientLocked(ITvIAppClient client, String iAppServiceId, - IBinder sessionToken, int seq) { + IBinder sessionToken, InputChannel channel, int seq) { try { - client.onSessionCreated(iAppServiceId, sessionToken, seq); + client.onSessionCreated(iAppServiceId, sessionToken, channel, seq); } catch (RemoteException e) { Slogf.e(TAG, "error in onSessionCreated", e); } @@ -797,20 +823,23 @@ public class TvIAppManagerService extends SystemService { Slogf.d(TAG, "createSessionInternalLocked(iAppServiceId=" + sessionState.mIAppServiceId + ")"); } + InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString()); // Set up a callback to send the session token. - ITvIAppSessionCallback callback = new SessionCallback(sessionState); + ITvIAppSessionCallback callback = new SessionCallback(sessionState, channels); boolean created = true; // Create a session. When failed, send a null token immediately. try { - service.createSession(callback, sessionState.mIAppServiceId, sessionState.mType); + service.createSession( + channels[1], callback, sessionState.mIAppServiceId, sessionState.mType); } catch (RemoteException e) { Slogf.e(TAG, "error in createSession", e); sendSessionTokenToClientLocked(sessionState.mClient, sessionState.mIAppServiceId, null, - sessionState.mSeq); + null, sessionState.mSeq); created = false; } + channels[1].dispose(); return created; } @@ -883,7 +912,7 @@ public class TvIAppManagerService extends SystemService { for (SessionState sessionState : sessionsToAbort) { removeSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId); sendSessionTokenToClientLocked(sessionState.mClient, - sessionState.mIAppServiceId, null, sessionState.mSeq); + sessionState.mIAppServiceId, null, null, sessionState.mSeq); } updateServiceConnectionLocked(serviceState.mComponent, userId); } @@ -1136,9 +1165,11 @@ public class TvIAppManagerService extends SystemService { private final class SessionCallback extends ITvIAppSessionCallback.Stub { private final SessionState mSessionState; + private final InputChannel[] mInputChannels; - SessionCallback(SessionState sessionState) { + SessionCallback(SessionState sessionState, InputChannel[] channels) { mSessionState = sessionState; + mInputChannels = channels; } @Override @@ -1154,12 +1185,14 @@ public class TvIAppManagerService extends SystemService { mSessionState.mClient, mSessionState.mIAppServiceId, mSessionState.mSessionToken, + mInputChannels[0], mSessionState.mSeq); } else { removeSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId); sendSessionTokenToClientLocked(mSessionState.mClient, - mSessionState.mIAppServiceId, null, mSessionState.mSeq); + mSessionState.mIAppServiceId, null, null, mSessionState.mSeq); } + mInputChannels[0].dispose(); } } @@ -1182,6 +1215,24 @@ public class TvIAppManagerService extends SystemService { } } + @Override + public void onBroadcastInfoRequest(BroadcastInfoRequest request) { + synchronized (mLock) { + if (DEBUG) { + Slogf.d(TAG, "onBroadcastInfoRequest (requestId=" + + request.getRequestId() + ")"); + } + if (mSessionState.mSession == null || mSessionState.mClient == null) { + return; + } + try { + mSessionState.mClient.onBroadcastInfoRequest(request, mSessionState.mSeq); + } catch (RemoteException e) { + Slogf.e(TAG, "error in onBroadcastInfoRequest", e); + } + } + } + @GuardedBy("mLock") private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) { try { diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 1c46ac8c4aa1..be13168815d9 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -87,9 +87,10 @@ import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import com.android.server.vcn.util.LogUtils; import com.android.server.vcn.util.MtuUtils; import com.android.server.vcn.util.OneWayBoolean; @@ -201,7 +202,7 @@ public class VcnGatewayConnection extends StateMachine { private interface EventInfo {} /** - * Sent when there are changes to the underlying network (per the UnderlyingNetworkTracker). + * Sent when there are changes to the underlying network (per the UnderlyingNetworkController). * * <p>May indicate an entirely new underlying network, OR a change in network properties. * @@ -522,11 +523,14 @@ public class VcnGatewayConnection extends StateMachine { @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; - @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull private final UnderlyingNetworkController mUnderlyingNetworkController; @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; - @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; + + @NonNull + private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback; + private final boolean mIsMobileDataEnabled; @NonNull private final IpSecManager mIpSecManager; @@ -674,17 +678,17 @@ public class VcnGatewayConnection extends StateMachine { mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); - mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); + mUnderlyingNetworkControllerCallback = new VcnUnderlyingNetworkControllerCallback(); mWakeLock = mDeps.newWakeLock(mVcnContext.getContext(), PowerManager.PARTIAL_WAKE_LOCK, TAG); - mUnderlyingNetworkTracker = - mDeps.newUnderlyingNetworkTracker( + mUnderlyingNetworkController = + mDeps.newUnderlyingNetworkController( mVcnContext, subscriptionGroup, mLastSnapshot, - mUnderlyingNetworkTrackerCallback); + mUnderlyingNetworkControllerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); addState(mDisconnectedState); @@ -748,7 +752,7 @@ public class VcnGatewayConnection extends StateMachine { cancelRetryTimeoutAlarm(); cancelSafeModeAlarm(); - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); mGatewayStatusCallback.onQuit(); } @@ -764,12 +768,13 @@ public class VcnGatewayConnection extends StateMachine { mVcnContext.ensureRunningOnLooperThread(); mLastSnapshot = snapshot; - mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); + mUnderlyingNetworkController.updateSubscriptionSnapshot(mLastSnapshot); sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); } - private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { + private class VcnUnderlyingNetworkControllerCallback + implements UnderlyingNetworkControllerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { @@ -2264,7 +2269,7 @@ public class VcnGatewayConnection extends StateMachine { + (mNetworkAgent == null ? null : mNetworkAgent.getNetwork())); pw.println(); - mUnderlyingNetworkTracker.dump(pw); + mUnderlyingNetworkController.dump(pw); pw.println(); pw.decreaseIndent(); @@ -2276,8 +2281,8 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) - UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { - return mUnderlyingNetworkTrackerCallback; + UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() { + return mUnderlyingNetworkControllerCallback; } @VisibleForTesting(visibility = Visibility.PRIVATE) @@ -2356,17 +2361,14 @@ public class VcnGatewayConnection extends StateMachine { /** External dependencies used by VcnGatewayConnection, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { - /** Builds a new UnderlyingNetworkTracker. */ - public UnderlyingNetworkTracker newUnderlyingNetworkTracker( + /** Builds a new UnderlyingNetworkController. */ + public UnderlyingNetworkController newUnderlyingNetworkController( VcnContext vcnContext, ParcelUuid subscriptionGroup, TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkTrackerCallback callback) { - return new UnderlyingNetworkTracker( - vcnContext, - subscriptionGroup, - snapshot, - callback); + UnderlyingNetworkControllerCallback callback) { + return new UnderlyingNetworkController( + vcnContext, subscriptionGroup, snapshot, callback); } /** Builds a new IkeSession. */ diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java new file mode 100644 index 000000000000..bea8ae932a9d --- /dev/null +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.vcn.routeselection; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + +import static com.android.server.VcnManagementService.LOCAL_LOG; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.NetworkCapabilities; +import android.net.vcn.VcnManager; +import android.os.ParcelUuid; +import android.os.PersistableBundle; +import android.telephony.SubscriptionManager; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; + +import java.util.Set; + +/** @hide */ +class NetworkPriorityClassifier { + @NonNull private static final String TAG = NetworkPriorityClassifier.class.getSimpleName(); + /** + * Minimum signal strength for a WiFi network to be eligible for switching to + * + * <p>A network that satisfies this is eligible to become the selected underlying network with + * no additional conditions + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70; + /** + * Minimum signal strength to continue using a WiFi network + * + * <p>A network that satisfies the conditions may ONLY continue to be used if it is already + * selected as the underlying network. A WiFi network satisfying this condition, but NOT the + * prospective-network RSSI threshold CANNOT be switched to. + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; + /** Priority for any cellular network for which the subscription is listed as opportunistic */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; + /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_WIFI_IN_USE = 1; + /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_WIFI_PROSPECTIVE = 2; + /** Priority for any standard macro cellular network */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_MACRO_CELLULAR = 3; + /** Priority for any other networks (including unvalidated, etc) */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int PRIORITY_ANY = Integer.MAX_VALUE; + + private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>(); + + static { + PRIORITY_TO_STRING_MAP.put( + PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); + PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); + } + + /** + * Gives networks a priority class, based on the following priorities: + * + * <ol> + * <li>Opportunistic cellular + * <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT + * <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT + * <li>Macro cellular + * <li>Any others + * </ol> + */ + static int calculatePriorityClass( + UnderlyingNetworkRecord networkRecord, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + final NetworkCapabilities caps = networkRecord.networkCapabilities; + + // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED + + if (networkRecord.isBlocked) { + logWtf("Network blocked for System Server: " + networkRecord.network); + return PRIORITY_ANY; + } + + if (caps.hasTransport(TRANSPORT_CELLULAR) + && isOpportunistic(snapshot, caps.getSubscriptionIds())) { + // If this carrier is the active data provider, ensure that opportunistic is only + // ever prioritized if it is also the active data subscription. This ensures that + // if an opportunistic subscription is still in the process of being switched to, + // or switched away from, the VCN does not attempt to continue using it against the + // decision made at the telephony layer. Failure to do so may result in the modem + // switching back and forth. + // + // Allow the following two cases: + // 1. Active subId is NOT in the group that this VCN is supporting + // 2. This opportunistic subscription is for the active subId + if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) + .contains(SubscriptionManager.getActiveDataSubscriptionId()) + || caps.getSubscriptionIds() + .contains(SubscriptionManager.getActiveDataSubscriptionId())) { + return PRIORITY_OPPORTUNISTIC_CELLULAR; + } + } + + if (caps.hasTransport(TRANSPORT_WIFI)) { + if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) + && currentlySelected != null + && networkRecord.network.equals(currentlySelected.network)) { + return PRIORITY_WIFI_IN_USE; + } + + if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { + return PRIORITY_WIFI_PROSPECTIVE; + } + } + + // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might + // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be + // the case if the Default Data SubId does not support certain services (eg voice + // calling) + if (caps.hasTransport(TRANSPORT_CELLULAR) + && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { + return PRIORITY_MACRO_CELLULAR; + } + + return PRIORITY_ANY; + } + + static boolean isOpportunistic( + @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) { + if (snapshot == null) { + logWtf("Got null snapshot"); + return false; + } + for (int subId : subIds) { + if (snapshot.isOpportunistic(subId)) { + return true; + } + } + return false; + } + + static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { + if (carrierConfig != null) { + return carrierConfig.getInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, + WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT); + } + return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; + } + + static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { + if (carrierConfig != null) { + return carrierConfig.getInt( + VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, + WIFI_EXIT_RSSI_THRESHOLD_DEFAULT); + } + return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; + } + + static String priorityClassToString(int priorityClass) { + return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown"); + } + + private static void logWtf(String msg) { + Slog.wtf(TAG, msg); + LOCAL_LOG.log(TAG + " WTF: " + msg); + } +} diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java index 7ddd1355a2d6..071c7a683cbf 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.server.vcn; +package com.android.server.vcn.routeselection; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; import static com.android.server.VcnManagementService.LOCAL_LOG; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiEntryRssiThreshold; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.getWifiExitRssiThreshold; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.isOpportunistic; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,27 +32,23 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; -import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; -import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnContext; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -61,68 +58,18 @@ import java.util.TreeSet; /** * Tracks a set of Networks underpinning a VcnGatewayConnection. * - * <p>A single UnderlyingNetworkTracker is built to serve a SINGLE VCN Gateway Connection, and MUST - * be torn down with the VcnGatewayConnection in order to ensure underlying networks are allowed to - * be reaped. + * <p>A single UnderlyingNetworkController is built to serve a SINGLE VCN Gateway Connection, and + * MUST be torn down with the VcnGatewayConnection in order to ensure underlying networks are + * allowed to be reaped. * * @hide */ -public class UnderlyingNetworkTracker { - @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName(); - - /** - * Minimum signal strength for a WiFi network to be eligible for switching to - * - * <p>A network that satisfies this is eligible to become the selected underlying network with - * no additional conditions - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT = -70; - - /** - * Minimum signal strength to continue using a WiFi network - * - * <p>A network that satisfies the conditions may ONLY continue to be used if it is already - * selected as the underlying network. A WiFi network satisfying this condition, but NOT the - * prospective-network RSSI threshold CANNOT be switched to. - */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74; - - /** Priority for any cellular network for which the subscription is listed as opportunistic */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0; - - /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_IN_USE = 1; - - /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_WIFI_PROSPECTIVE = 2; - - /** Priority for any standard macro cellular network */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_MACRO_CELLULAR = 3; - - /** Priority for any other networks (including unvalidated, etc) */ - @VisibleForTesting(visibility = Visibility.PRIVATE) - static final int PRIORITY_ANY = Integer.MAX_VALUE; - - private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>(); - - static { - PRIORITY_TO_STRING_MAP.put( - PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR"); - PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY"); - } +public class UnderlyingNetworkController { + @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName(); @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; - @NonNull private final UnderlyingNetworkTrackerCallback mCb; + @NonNull private final UnderlyingNetworkControllerCallback mCb; @NonNull private final Dependencies mDeps; @NonNull private final Handler mHandler; @NonNull private final ConnectivityManager mConnectivityManager; @@ -142,11 +89,11 @@ public class UnderlyingNetworkTracker { @Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; - public UnderlyingNetworkTracker( + public UnderlyingNetworkController( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull UnderlyingNetworkTrackerCallback cb) { + @NonNull UnderlyingNetworkControllerCallback cb) { this( vcnContext, subscriptionGroup, @@ -155,11 +102,11 @@ public class UnderlyingNetworkTracker { new Dependencies()); } - private UnderlyingNetworkTracker( + private UnderlyingNetworkController( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull TelephonySubscriptionSnapshot snapshot, - @NonNull UnderlyingNetworkTrackerCallback cb, + @NonNull UnderlyingNetworkControllerCallback cb, @NonNull Dependencies deps) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); @@ -271,8 +218,8 @@ public class UnderlyingNetworkTracker { * subscription group, while the VCN networks are excluded by virtue of not having subIds set on * the VCN-exposed networks. * - * <p>If the VCN that this UnderlyingNetworkTracker belongs to is in test-mode, this will return - * a NetworkRequest that only matches Test Networks. + * <p>If the VCN that this UnderlyingNetworkController belongs to is in test-mode, this will + * return a NetworkRequest that only matches Test Networks. */ private NetworkRequest getRouteSelectionRequest() { if (mVcnContext.isInTestMode()) { @@ -373,9 +320,9 @@ public class UnderlyingNetworkTracker { } /** - * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. + * Update this UnderlyingNetworkController's TelephonySubscriptionSnapshot. * - * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to + * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkController to * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. */ @@ -410,7 +357,7 @@ public class UnderlyingNetworkTracker { private void reevaluateNetworks() { if (mIsQuitting || mRouteSelectionCallback == null) { - return; // UnderlyingNetworkTracker has quit. + return; // UnderlyingNetworkController has quit. } TreeSet<UnderlyingNetworkRecord> sorted = @@ -424,22 +371,6 @@ public class UnderlyingNetworkTracker { mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord); } - private static boolean isOpportunistic( - @NonNull TelephonySubscriptionSnapshot snapshot, Set<Integer> subIds) { - if (snapshot == null) { - logWtf("Got null snapshot"); - return false; - } - - for (int subId : subIds) { - if (snapshot.isOpportunistic(subId)) { - return true; - } - } - - return false; - } - /** * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped. * @@ -544,230 +475,6 @@ public class UnderlyingNetworkTracker { } } - private static int getWifiEntryRssiThreshold(@Nullable PersistableBundle carrierConfig) { - if (carrierConfig != null) { - return carrierConfig.getInt( - VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, - WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT); - } - - return WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; - } - - private static int getWifiExitRssiThreshold(@Nullable PersistableBundle carrierConfig) { - if (carrierConfig != null) { - return carrierConfig.getInt( - VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, - WIFI_EXIT_RSSI_THRESHOLD_DEFAULT); - } - - return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; - } - - /** A record of a single underlying network, caching relevant fields. */ - public static class UnderlyingNetworkRecord { - @NonNull public final Network network; - @NonNull public final NetworkCapabilities networkCapabilities; - @NonNull public final LinkProperties linkProperties; - public final boolean isBlocked; - - @VisibleForTesting(visibility = Visibility.PRIVATE) - UnderlyingNetworkRecord( - @NonNull Network network, - @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, - boolean isBlocked) { - this.network = network; - this.networkCapabilities = networkCapabilities; - this.linkProperties = linkProperties; - this.isBlocked = isBlocked; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof UnderlyingNetworkRecord)) return false; - final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; - - return network.equals(that.network) - && networkCapabilities.equals(that.networkCapabilities) - && linkProperties.equals(that.linkProperties) - && isBlocked == that.isBlocked; - } - - @Override - public int hashCode() { - return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); - } - - /** - * Gives networks a priority class, based on the following priorities: - * - * <ol> - * <li>Opportunistic cellular - * <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT - * <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT - * <li>Macro cellular - * <li>Any others - * </ol> - */ - private int calculatePriorityClass( - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - final NetworkCapabilities caps = networkCapabilities; - - // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED - - if (isBlocked) { - logWtf("Network blocked for System Server: " + network); - return PRIORITY_ANY; - } - - if (caps.hasTransport(TRANSPORT_CELLULAR) - && isOpportunistic(snapshot, caps.getSubscriptionIds())) { - // If this carrier is the active data provider, ensure that opportunistic is only - // ever prioritized if it is also the active data subscription. This ensures that - // if an opportunistic subscription is still in the process of being switched to, - // or switched away from, the VCN does not attempt to continue using it against the - // decision made at the telephony layer. Failure to do so may result in the modem - // switching back and forth. - // - // Allow the following two cases: - // 1. Active subId is NOT in the group that this VCN is supporting - // 2. This opportunistic subscription is for the active subId - if (!snapshot.getAllSubIdsInGroup(subscriptionGroup) - .contains(SubscriptionManager.getActiveDataSubscriptionId()) - || caps.getSubscriptionIds() - .contains(SubscriptionManager.getActiveDataSubscriptionId())) { - return PRIORITY_OPPORTUNISTIC_CELLULAR; - } - } - - if (caps.hasTransport(TRANSPORT_WIFI)) { - if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig) - && currentlySelected != null - && network.equals(currentlySelected.network)) { - return PRIORITY_WIFI_IN_USE; - } - - if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) { - return PRIORITY_WIFI_PROSPECTIVE; - } - } - - // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might - // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be - // the case if the Default Data SubId does not support certain services (eg voice - // calling) - if (caps.hasTransport(TRANSPORT_CELLULAR) - && !isOpportunistic(snapshot, caps.getSubscriptionIds())) { - return PRIORITY_MACRO_CELLULAR; - } - - return PRIORITY_ANY; - } - - private static Comparator<UnderlyingNetworkRecord> getComparator( - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - return (left, right) -> { - return Integer.compare( - left.calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig), - right.calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig)); - }; - } - - /** Dumps the state of this record for logging and debugging purposes. */ - private void dump( - IndentingPrintWriter pw, - ParcelUuid subscriptionGroup, - TelephonySubscriptionSnapshot snapshot, - UnderlyingNetworkRecord currentlySelected, - PersistableBundle carrierConfig) { - pw.println("UnderlyingNetworkRecord:"); - pw.increaseIndent(); - - final int priorityClass = - calculatePriorityClass( - subscriptionGroup, snapshot, currentlySelected, carrierConfig); - pw.println( - "Priority class: " + PRIORITY_TO_STRING_MAP.get(priorityClass) + " (" - + priorityClass + ")"); - pw.println("mNetwork: " + network); - pw.println("mNetworkCapabilities: " + networkCapabilities); - pw.println("mLinkProperties: " + linkProperties); - - pw.decreaseIndent(); - } - - /** Builder to incrementally construct an UnderlyingNetworkRecord. */ - private static class Builder { - @NonNull private final Network mNetwork; - - @Nullable private NetworkCapabilities mNetworkCapabilities; - @Nullable private LinkProperties mLinkProperties; - boolean mIsBlocked; - boolean mWasIsBlockedSet; - - @Nullable private UnderlyingNetworkRecord mCached; - - private Builder(@NonNull Network network) { - mNetwork = network; - } - - @NonNull - private Network getNetwork() { - return mNetwork; - } - - private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { - mNetworkCapabilities = networkCapabilities; - mCached = null; - } - - @Nullable - private NetworkCapabilities getNetworkCapabilities() { - return mNetworkCapabilities; - } - - private void setLinkProperties(@NonNull LinkProperties linkProperties) { - mLinkProperties = linkProperties; - mCached = null; - } - - private void setIsBlocked(boolean isBlocked) { - mIsBlocked = isBlocked; - mWasIsBlockedSet = true; - mCached = null; - } - - private boolean isValid() { - return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; - } - - private UnderlyingNetworkRecord build() { - if (!isValid()) { - throw new IllegalArgumentException( - "Called build before UnderlyingNetworkRecord was valid"); - } - - if (mCached == null) { - mCached = - new UnderlyingNetworkRecord( - mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); - } - - return mCached; - } - } - } - private static void logWtf(String msg) { Slog.wtf(TAG, msg); LOCAL_LOG.log(TAG + " WTF: " + msg); @@ -780,7 +487,7 @@ public class UnderlyingNetworkTracker { /** Dumps the state of this record for logging and debugging purposes. */ public void dump(IndentingPrintWriter pw) { - pw.println("UnderlyingNetworkTracker:"); + pw.println("UnderlyingNetworkController:"); pw.increaseIndent(); pw.println("Carrier WiFi Entry Threshold: " + getWifiEntryRssiThreshold(mCarrierConfig)); @@ -811,7 +518,7 @@ public class UnderlyingNetworkTracker { } /** Callbacks for being notified of the changes in, or to the selected underlying network. */ - public interface UnderlyingNetworkTrackerCallback { + public interface UnderlyingNetworkControllerCallback { /** * Fired when a new underlying network is selected, or properties have changed. * diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java new file mode 100644 index 000000000000..65c69dedcb28 --- /dev/null +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn.routeselection; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.os.ParcelUuid; +import android.os.PersistableBundle; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; + +import java.util.Comparator; +import java.util.Objects; + +/** + * A record of a single underlying network, caching relevant fields. + * + * @hide + */ +public class UnderlyingNetworkRecord { + @NonNull public final Network network; + @NonNull public final NetworkCapabilities networkCapabilities; + @NonNull public final LinkProperties linkProperties; + public final boolean isBlocked; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public UnderlyingNetworkRecord( + @NonNull Network network, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull LinkProperties linkProperties, + boolean isBlocked) { + this.network = network; + this.networkCapabilities = networkCapabilities; + this.linkProperties = linkProperties; + this.isBlocked = isBlocked; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof UnderlyingNetworkRecord)) return false; + final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o; + + return network.equals(that.network) + && networkCapabilities.equals(that.networkCapabilities) + && linkProperties.equals(that.linkProperties) + && isBlocked == that.isBlocked; + } + + @Override + public int hashCode() { + return Objects.hash(network, networkCapabilities, linkProperties, isBlocked); + } + + static Comparator<UnderlyingNetworkRecord> getComparator( + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + return (left, right) -> { + return Integer.compare( + NetworkPriorityClassifier.calculatePriorityClass( + left, subscriptionGroup, snapshot, currentlySelected, carrierConfig), + NetworkPriorityClassifier.calculatePriorityClass( + right, subscriptionGroup, snapshot, currentlySelected, carrierConfig)); + }; + } + + /** Dumps the state of this record for logging and debugging purposes. */ + void dump( + IndentingPrintWriter pw, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + UnderlyingNetworkRecord currentlySelected, + PersistableBundle carrierConfig) { + pw.println("UnderlyingNetworkRecord:"); + pw.increaseIndent(); + + final int priorityClass = + NetworkPriorityClassifier.calculatePriorityClass( + this, subscriptionGroup, snapshot, currentlySelected, carrierConfig); + pw.println( + "Priority class: " + + NetworkPriorityClassifier.priorityClassToString(priorityClass) + + " (" + + priorityClass + + ")"); + pw.println("mNetwork: " + network); + pw.println("mNetworkCapabilities: " + networkCapabilities); + pw.println("mLinkProperties: " + linkProperties); + + pw.decreaseIndent(); + } + + /** Builder to incrementally construct an UnderlyingNetworkRecord. */ + static class Builder { + @NonNull private final Network mNetwork; + + @Nullable private NetworkCapabilities mNetworkCapabilities; + @Nullable private LinkProperties mLinkProperties; + boolean mIsBlocked; + boolean mWasIsBlockedSet; + + @Nullable private UnderlyingNetworkRecord mCached; + + Builder(@NonNull Network network) { + mNetwork = network; + } + + @NonNull + Network getNetwork() { + return mNetwork; + } + + void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) { + mNetworkCapabilities = networkCapabilities; + mCached = null; + } + + @Nullable + NetworkCapabilities getNetworkCapabilities() { + return mNetworkCapabilities; + } + + void setLinkProperties(@NonNull LinkProperties linkProperties) { + mLinkProperties = linkProperties; + mCached = null; + } + + void setIsBlocked(boolean isBlocked) { + mIsBlocked = isBlocked; + mWasIsBlockedSet = true; + mCached = null; + } + + boolean isValid() { + return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet; + } + + UnderlyingNetworkRecord build() { + if (!isValid()) { + throw new IllegalArgumentException( + "Called build before UnderlyingNetworkRecord was valid"); + } + + if (mCached == null) { + mCached = + new UnderlyingNetworkRecord( + mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked); + } + + return mCached; + } + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index bc21f1bd2ed3..f82f99d6a253 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -16,6 +16,14 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.USAGE_ALARM; +import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; +import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK; +import static android.os.VibrationAttributes.USAGE_NOTIFICATION; +import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION; +import static android.os.VibrationAttributes.USAGE_RINGTONE; +import static android.os.VibrationAttributes.USAGE_TOUCH; + import android.annotation.Nullable; import android.app.ActivityManager; import android.app.IUidObserver; @@ -90,6 +98,8 @@ final class VibrationSettings { @GuardedBy("mLock") private int mHapticFeedbackIntensity; @GuardedBy("mLock") + private int mHardwareFeedbackIntensity; + @GuardedBy("mLock") private int mNotificationIntensity; @GuardedBy("mLock") private int mRingIntensity; @@ -176,7 +186,7 @@ final class VibrationSettings { registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES)); registerSettingsObserver(Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING)); - registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.APPLY_RAMPING_RINGER)); + registerSettingsObserver(Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER)); registerSettingsObserver(Settings.Global.getUriFor(Settings.Global.ZEN_MODE)); registerSettingsObserver( Settings.System.getUriFor(Settings.System.HAPTIC_FEEDBACK_INTENSITY)); @@ -232,17 +242,20 @@ final class VibrationSettings { * @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_* */ public int getDefaultIntensity(int usageHint) { - if (isAlarm(usageHint)) { + if (usageHint == USAGE_ALARM) { return Vibrator.VIBRATION_INTENSITY_HIGH; } synchronized (mLock) { if (mVibrator != null) { - if (isRingtone(usageHint)) { - return mVibrator.getDefaultRingVibrationIntensity(); - } else if (isNotification(usageHint)) { - return mVibrator.getDefaultNotificationVibrationIntensity(); - } else if (isHapticFeedback(usageHint)) { - return mVibrator.getDefaultHapticFeedbackIntensity(); + switch (usageHint) { + case USAGE_RINGTONE: + return mVibrator.getDefaultRingVibrationIntensity(); + case USAGE_NOTIFICATION: + return mVibrator.getDefaultNotificationVibrationIntensity(); + case USAGE_TOUCH: + case USAGE_HARDWARE_FEEDBACK: + case USAGE_PHYSICAL_EMULATION: + return mVibrator.getDefaultHapticFeedbackIntensity(); } } } @@ -257,16 +270,20 @@ final class VibrationSettings { */ public int getCurrentIntensity(int usageHint) { synchronized (mLock) { - if (isRingtone(usageHint)) { - return mRingIntensity; - } else if (isNotification(usageHint)) { - return mNotificationIntensity; - } else if (isHapticFeedback(usageHint)) { - return mHapticFeedbackIntensity; - } else if (isAlarm(usageHint)) { - return Vibrator.VIBRATION_INTENSITY_HIGH; - } else { - return Vibrator.VIBRATION_INTENSITY_MEDIUM; + switch (usageHint) { + case USAGE_RINGTONE: + return mRingIntensity; + case USAGE_NOTIFICATION: + return mNotificationIntensity; + case USAGE_TOUCH: + return mHapticFeedbackIntensity; + case USAGE_HARDWARE_FEEDBACK: + case USAGE_PHYSICAL_EMULATION: + return mHardwareFeedbackIntensity; + case USAGE_ALARM: + return Vibrator.VIBRATION_INTENSITY_HIGH; + default: + return Vibrator.VIBRATION_INTENSITY_MEDIUM; } } } @@ -289,7 +306,7 @@ final class VibrationSettings { * for ringtone usage only. All other usages are allowed independently of ringer mode. */ public boolean shouldVibrateForRingerMode(int usageHint) { - if (!isRingtone(usageHint)) { + if (usageHint != USAGE_RINGTONE) { return true; } synchronized (mLock) { @@ -324,8 +341,10 @@ final class VibrationSettings { * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate. */ public boolean shouldVibrateForPowerMode(int usageHint) { - return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint) - || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST; + synchronized (mLock) { + return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM + || usageHint == USAGE_COMMUNICATION_REQUEST; + } } /** Return {@code true} if input devices should vibrate instead of this device. */ @@ -338,22 +357,6 @@ final class VibrationSettings { return mZenMode != Settings.Global.ZEN_MODE_OFF; } - private static boolean isNotification(int usageHint) { - return usageHint == VibrationAttributes.USAGE_NOTIFICATION; - } - - private static boolean isRingtone(int usageHint) { - return usageHint == VibrationAttributes.USAGE_RINGTONE; - } - - private static boolean isHapticFeedback(int usageHint) { - return usageHint == VibrationAttributes.USAGE_TOUCH; - } - - private static boolean isAlarm(int usageHint) { - return usageHint == VibrationAttributes.USAGE_ALARM; - } - private static boolean isClassAlarm(int usageHint) { return (usageHint & VibrationAttributes.USAGE_CLASS_MASK) == VibrationAttributes.USAGE_CLASS_ALARM; @@ -363,20 +366,37 @@ final class VibrationSettings { public void updateSettings() { synchronized (mLock) { mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0; - mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0; + mApplyRampingRinger = getSystemSetting(Settings.System.APPLY_RAMPING_RINGER, 0) != 0; mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + getDefaultIntensity(USAGE_TOUCH)); + mHardwareFeedbackIntensity = getSystemSetting( + Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, + getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity)); mNotificationIntensity = getSystemSetting( Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + getDefaultIntensity(USAGE_NOTIFICATION)); mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + getDefaultIntensity(USAGE_RINGTONE)); mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0; mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); } notifyListeners(); } + /** + * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY} + * when the value was not set by the user. + * + * <p>This should adapt the behavior preceding the introduction of this new setting key, which + * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled. + */ + private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) { + if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) { + return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK); + } + return hapticFeedbackIntensity; + } + @Override public String toString() { return "VibrationSettings{" @@ -389,18 +409,20 @@ final class VibrationSettings { + ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude() + ", mRampStepDuration=" + mRampStepDuration + ", mRampDownDuration=" + mRampDownDuration + + ", mHardwareHapticFeedbackIntensity=" + + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)) + ", mHapticFeedbackIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)) + + intensityToString(getCurrentIntensity(USAGE_TOUCH)) + ", mHapticFeedbackDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)) + + intensityToString(getDefaultIntensity(USAGE_TOUCH)) + ", mNotificationIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION)) + ", mNotificationDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)) + + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION)) + ", mRingIntensity=" - + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)) + + intensityToString(getCurrentIntensity(USAGE_RINGTONE)) + ", mRingDefaultIntensity=" - + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)) + + intensityToString(getDefaultIntensity(USAGE_RINGTONE)) + '}'; } @@ -410,15 +432,15 @@ final class VibrationSettings { proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, mHapticFeedbackIntensity); proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); + getDefaultIntensity(USAGE_TOUCH)); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY, mNotificationIntensity); proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); + getDefaultIntensity(USAGE_NOTIFICATION)); proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY, mRingIntensity); proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY, - getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + getDefaultIntensity(USAGE_RINGTONE)); } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 5d40c23610dd..97172015f9f0 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -341,7 +341,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!isEffectValid(effect)) { return false; } - attrs = fixupVibrationAttributes(attrs); + attrs = fixupVibrationAttributes(attrs, effect); synchronized (mLock) { SparseArray<PrebakedSegment> effects = fixupAlwaysOnEffectsLocked(effect); if (effects == null) { @@ -385,7 +385,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!isEffectValid(effect)) { return null; } - attrs = fixupVibrationAttributes(attrs); + attrs = fixupVibrationAttributes(attrs, effect); Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs, uid, opPkg, reason); fillVibrationFallbacks(vib, effect); @@ -895,21 +895,32 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * Return new {@link VibrationAttributes} that only applies flags that this user has permissions * to use. */ - private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs) { + @NonNull + private VibrationAttributes fixupVibrationAttributes(@Nullable VibrationAttributes attrs, + CombinedVibration effect) { if (attrs == null) { attrs = DEFAULT_ATTRIBUTES; } + int usage = attrs.getUsage(); + if ((usage == VibrationAttributes.USAGE_UNKNOWN) && effect.isHapticFeedbackCandidate()) { + usage = VibrationAttributes.USAGE_TOUCH; + } + int flags = attrs.getFlags(); if (attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) { if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - final int flags = attrs.getFlags() - & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; - attrs = new VibrationAttributes.Builder(attrs) - .setFlags(flags, attrs.getFlags()).build(); + // Remove bypass policy flag from attributes if the app does not have permissions. + flags &= ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; } } - return attrs; + if ((usage == attrs.getUsage()) && (flags == attrs.getFlags())) { + return attrs; + } + return new VibrationAttributes.Builder(attrs) + .setUsage(usage) + .setFlags(flags, attrs.getFlags()) + .build(); } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ea22d92c1e20..ffc70da54b7d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1376,9 +1376,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */ private boolean shouldStartChangeTransition( @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) { - if (mWmService.mDisableTransitionAnimation - || mDisplayContent == null || newParent == null || oldParent == null - || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) { + if (newParent == null || oldParent == null || !canStartChangeTransition()) { return false; } @@ -6224,15 +6222,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public boolean inputDispatchingTimedOut(String reason, int windowPid) { ActivityRecord anrActivity; WindowProcessController anrApp; - boolean windowFromSameProcessAsActivity; + boolean blameActivityProcess; synchronized (mAtmService.mGlobalLock) { anrActivity = getWaitingHistoryRecordLocked(); anrApp = app; - windowFromSameProcessAsActivity = - !hasProcess() || app.getPid() == windowPid || windowPid == INVALID_PID; + blameActivityProcess = hasProcess() + && (app.getPid() == windowPid || windowPid == INVALID_PID); } - if (windowFromSameProcessAsActivity) { + if (blameActivityProcess) { return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner, anrActivity.shortComponentName, anrActivity.info.applicationInfo, shortComponentName, app, false, reason); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 73a783eba602..bb7434d5092f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2784,8 +2784,8 @@ class ActivityStarter { // If it exist, we need to reparent target root task from TDA to launch root task. final TaskDisplayArea tda = mTargetRootTask.getDisplayArea(); final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(), - mTargetRootTask.getActivityType(), null /** options */, - mSourceRootTask, 0 /** launchFlags */); + mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask, + mLaunchFlags); // If target root task is created by organizer, let organizer handle reparent itself. if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null && launchRootTask != mTargetRootTask) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 7fa98618ecb9..e38e9c1a4357 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -126,35 +126,6 @@ public abstract class ActivityTaskManagerInternal { } /** - * Sleep tokens cause the activity manager to put the top activity to sleep. - * They are used by components such as dreams that may hide and block interaction - * with underlying activities. - * The Acquirer provides an interface that encapsulates the underlying work, so the user does - * not need to handle the token by him/herself. - */ - public interface SleepTokenAcquirer { - - /** - * Acquires a sleep token. - * @param displayId The display to apply to. - */ - void acquire(int displayId); - - /** - * Releases the sleep token. - * @param displayId The display to apply to. - */ - void release(int displayId); - } - - /** - * Creates a sleep token acquirer for the specified display with the specified tag. - * - * @param tag A string identifying the purpose (eg. "Dream"). - */ - public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag); - - /** * Returns home activity for the specified user. * * @param userId ID of the user or {@link android.os.UserHandle#USER_ALL} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0a85ba15fd40..e4ed04de50f5 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4544,17 +4544,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { reason); } - final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer { + /** + * Sleep tokens cause the activity manager to put the top activity to sleep. + * They are used by components such as dreams that may hide and block interaction + * with underlying activities. + */ + final class SleepTokenAcquirer { private final String mTag; private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens = new SparseArray<>(); - SleepTokenAcquirerImpl(@NonNull String tag) { + SleepTokenAcquirer(@NonNull String tag) { mTag = tag; } - @Override - public void acquire(int displayId) { + /** + * Acquires a sleep token. + * @param displayId The display to apply to. + */ + void acquire(int displayId) { synchronized (mGlobalLock) { if (!mSleepTokens.contains(displayId)) { mSleepTokens.append(displayId, @@ -4564,8 +4572,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @Override - public void release(int displayId) { + /** + * Releases the sleep token. + * @param displayId The display to apply to. + */ + void release(int displayId) { synchronized (mGlobalLock) { final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId); if (token != null) { @@ -5255,12 +5266,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final class LocalService extends ActivityTaskManagerInternal { @Override - public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) { - Objects.requireNonNull(tag); - return new SleepTokenAcquirerImpl(tag); - } - - @Override public ComponentName getHomeActivityForUser(int userId) { synchronized (mGlobalLock) { final ActivityRecord homeActivity = diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 878822522d08..721907c21904 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -176,6 +176,9 @@ public class AppTransitionController { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO"); + // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause. + mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow, + true /* traverseTopToBottom */); // TODO(new-app-transition): Remove code using appTransition.getAppTransition() final AppTransition appTransition = mDisplayContent.mAppTransition; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 42c81248da34..68eff9a5450e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -34,6 +34,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.os.Build.VERSION_CODES.N; +import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.util.RotationUtils.deltaRotation; @@ -43,6 +44,8 @@ import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; +import static android.view.Display.STATE_UNKNOWN; +import static android.view.Display.isSuspendedState; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; @@ -61,6 +64,7 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; @@ -218,6 +222,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -316,11 +321,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private Rect mLastMirroredDisplayAreaBounds = null; - /** - * The last state of the display. - */ - private int mLastDisplayState; - // Contains all IME window containers. Note that the z-ordering of the IME windows will depend // on the IME target. We mainly have this container grouping so we can keep track of all the IME // window containers together and move them in-sync if/when needed. We use a subclass of @@ -664,7 +664,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** All tokens used to put activities on this root task to sleep (including mOffToken) */ final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>(); /** The token acquirer to put root tasks on the display to sleep */ - private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer; + private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer; private boolean mSleeping; @@ -696,6 +696,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // well and thus won't change the top resumed / focused record boolean mDontMoveToTop; + /** + * The policy controller of the windows that can be displayed on the virtual display. + * + * @see DisplayWindowPolicyController + */ + @Nullable + DisplayWindowPolicyController mDisplayWindowPolicyController; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -1631,11 +1639,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // to cover the activity configuration change. return false; } - if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) { - // Currently it is unknown that when will IME window be ready. Reject the case to - // avoid flickering by showing IME in inconsistent orientation. - return false; - } if (checkOpening) { if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { // Apply normal rotation animation in case of the activity set different requested @@ -2739,6 +2742,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (newDisplayInfo != null) { mDisplayInfo.copyFrom(newDisplayInfo); } + + mDisplayWindowPolicyController = + displayManagerInternal.getDisplayWindowPolicyController(mDisplayId); } updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight, @@ -2821,8 +2827,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mBaseDisplayDensity = baseDensity; if (mMaxUiWidth > 0 && mBaseDisplayWidth > mMaxUiWidth) { - mBaseDisplayHeight = (mMaxUiWidth * mBaseDisplayHeight) / mBaseDisplayWidth; + final float ratio = mMaxUiWidth / (float) mBaseDisplayWidth; + mBaseDisplayHeight = (int) (mBaseDisplayHeight * ratio); mBaseDisplayWidth = mMaxUiWidth; + if (!mIsDensityForced) { + // Update the density proportionally so the size of the UI elements won't change + // from the user's perspective. + mBaseDisplayDensity = (int) (mBaseDisplayDensity * ratio); + } if (DEBUG_DISPLAY) { Slog.v(TAG_WM, "Applying config restraints:" + mBaseDisplayWidth + "x" @@ -2879,6 +2891,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** If the given width and height equal to initial size, the setting will be cleared. */ void setForcedSize(int width, int height) { + // Can't force size higher than the maximal allowed + if (mMaxUiWidth > 0 && width > mMaxUiWidth) { + final float ratio = mMaxUiWidth / (float) width; + height = (int) (height * ratio); + width = mMaxUiWidth; + } + mIsSizeForced = mInitialDisplayWidth != width || mInitialDisplayHeight != height; if (mIsSizeForced) { // Set some sort of reasonable bounds on the size of the display that we will try @@ -3420,6 +3439,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); + if (mDisplayWindowPolicyController != null) { + pw.println(); + mDisplayWindowPolicyController.dump(prefix, pw); + } } @Override @@ -4272,7 +4295,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean subtle) { final WindowManagerPolicy policy = mWmService.mPolicy; forAllWindows(w -> { - if (w.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(w) + if (w.mActivityRecord == null && w.canBeHiddenByKeyguard() && w.wouldBeVisibleIfPolicyIgnored() && !w.isVisible()) { w.startAnimation(policy.createHiddenByKeyguardExit( onWallpaper, goingToShade, subtle)); @@ -4643,12 +4666,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.requestTraversal(); } + @Override boolean okToDisplay() { - return okToDisplay(false); - } - - boolean okToDisplay(boolean ignoreFrozen) { - return okToDisplay(ignoreFrozen, false /* ignoreScreenOn */); + return okToDisplay(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) { @@ -4660,18 +4680,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayInfo.state == Display.STATE_ON; } - boolean okToAnimate() { - return okToAnimate(false); - } - - boolean okToAnimate(boolean ignoreFrozen) { - return okToAnimate(ignoreFrozen, false /* ignoreScreenOn */); - } - + @Override boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { return okToDisplay(ignoreFrozen, ignoreScreenOn) && (mDisplayId != DEFAULT_DISPLAY - || mWmService.mPolicy.okToAnimate(ignoreScreenOn)); + || mWmService.mPolicy.okToAnimate(ignoreScreenOn)) + && getDisplayPolicy().isScreenOnFully(); } static final class TaskForResizePointSearchResult implements Predicate<Task> { @@ -4801,7 +4815,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch. return dc.mImeLayeringTarget != null && (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated() - || dc.mImeLayeringTarget.getTask() == null); + || dc.mImeLayeringTarget.getTask() == null) + // Make sure that the IME window won't be skipped to report that it has + // completed the orientation change. + && !dc.mWmService.mDisplayFrozen; } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -4922,6 +4939,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp reconfigureDisplayLocked(); onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); + // Attach the SystemUiContext to this DisplayContent the get latest configuration. + // Note that the SystemUiContext will be removed automatically if this DisplayContent + // is detached. + mWmService.mWindowContextListenerController.registerWindowContainerListener( + getDisplayUiContext().getWindowContextToken(), this, SYSTEM_UID, + INVALID_WINDOW_TYPE, null /* options */); } } @@ -5456,29 +5479,44 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mMetricsLogger; } + void acquireScreenOffToken(boolean acquire) { + if (acquire) { + mOffTokenAcquirer.acquire(mDisplayId); + } else { + mOffTokenAcquirer.release(mDisplayId); + } + } + void onDisplayChanged() { mDisplay.getRealSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); + final int lastDisplayState = mDisplayInfo.state; updateDisplayInfo(); // The window policy is responsible for stopping activities on the default display. final int displayId = mDisplay.getDisplayId(); + final int displayState = mDisplayInfo.state; if (displayId != DEFAULT_DISPLAY) { - final int displayState = mDisplay.getState(); if (displayState == Display.STATE_OFF) { - mOffTokenAcquirer.acquire(mDisplayId); + acquireScreenOffToken(true /* acquire */); } else if (displayState == Display.STATE_ON) { - mOffTokenAcquirer.release(mDisplayId); + acquireScreenOffToken(false /* acquire */); } ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, "Display %d state is now (%d), so update layer mirroring?", mDisplayId, displayState); - if (mLastDisplayState != displayState) { + if (lastDisplayState != displayState) { // If state is on due to surface being added, then start layer mirroring. // If state is off due to surface being removed, then stop layer mirroring. updateMirroring(); } - mLastDisplayState = displayState; + } + // Dispatch pending Configuration to WindowContext if the associated display changes to + // un-suspended state from suspended. + if (isSuspendedState(lastDisplayState) + && !isSuspendedState(displayState) && displayState != STATE_UNKNOWN) { + mWmService.mWindowContextListenerController + .dispatchPendingConfigurationIfNeeded(mDisplayId); } mWmService.requestTraversal(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 09de6b3ca4fa..cbb9d5db8db3 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -426,7 +426,7 @@ public class DisplayPolicy { : service.mContext.createDisplayContext(displayContent.getDisplay()); mUiContext = displayContent.isDefaultDisplay ? service.mAtmService.mUiContext : service.mAtmService.mSystemThread - .createSystemUiContext(displayContent.getDisplayId()); + .getSystemUiContext(displayContent.getDisplayId()); mDisplayContent = displayContent; mLock = service.getWindowManagerLock(); @@ -752,6 +752,10 @@ public class DisplayPolicy { public void setAwake(boolean awake) { mAwake = awake; + // The screen off token for non-default display is controlled by DisplayContent. + if (mDisplayContent.isDefaultDisplay) { + mDisplayContent.acquireScreenOffToken(!awake); + } } public boolean isAwake() { @@ -1738,7 +1742,7 @@ public class DisplayPolicy { * @param imeTarget The current IME target window. */ private void applyKeyguardPolicy(WindowState win, WindowState imeTarget) { - if (mService.mPolicy.canBeHiddenByKeyguardLw(win)) { + if (win.canBeHiddenByKeyguard()) { if (shouldBeHiddenByKeyguard(win, imeTarget)) { win.hide(false /* doAnimation */, true /* requestAnim */); } else { @@ -1767,7 +1771,7 @@ public class DisplayPolicy { // Show IME over the keyguard if the target allows it. final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible() && win.mIsImWindow && (imeTarget.canShowWhenLocked() - || !mService.mPolicy.canBeHiddenByKeyguardLw(imeTarget)); + || !imeTarget.canBeHiddenByKeyguard()); if (showImeOverKeyguard) { return false; } @@ -1887,7 +1891,8 @@ public class DisplayPolicy { // user's package info (see ContextImpl.createDisplayContext) final LoadedApk pi = ActivityThread.currentActivityThread().getPackageInfo( uiContext.getPackageName(), null, 0, userId); - mCurrentUserResources = ResourcesManager.getInstance().getResources(null, + mCurrentUserResources = ResourcesManager.getInstance().getResources( + uiContext.getWindowContextToken(), pi.getResDir(), null /* splitResDirs */, pi.getOverlayDirs(), diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index badb1f5a0a12..963f3265757d 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -139,9 +139,6 @@ class EnsureActivitiesVisibleHelper { setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); } } - if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) { - mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows(); - } } private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 83fd3fa3bbfe..4225f214eebd 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -50,7 +50,6 @@ import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS; import static java.lang.Integer.MAX_VALUE; import android.annotation.Nullable; -import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.IBinder; @@ -262,9 +261,6 @@ final class InputMonitor { && !mDisableWallpaperTouchEvents; inputWindowHandle.setHasWallpaper(hasWallpaper); - final Rect frame = w.getFrame(); - inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom); - // Surface insets are hardcoded to be the same in all directions // and we could probably deprecate the "left/right/top/bottom" concept. // we avoid reintroducing this concept by just choosing one of them here. @@ -274,11 +270,19 @@ final class InputMonitor { // what is on screen to what is actually being touched in the UI. inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f); - final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags); - inputWindowHandle.setTouchableRegion(mTmpRegion); + // Update layout params flags to force the window to be not touch modal. We do this to + // restrict the window's touchable region to the task even if it request touches outside its + // window bounds. An example is a dialog in primary split should get touches outside its + // window within the primary task but should not get any touches going to the secondary + // task. + int flags = w.mAttrs.flags; + if (w.mAttrs.isModal()) { + flags = flags | FLAG_NOT_TOUCH_MODAL; + } inputWindowHandle.setLayoutParamsFlags(flags); - boolean useSurfaceCrop = false; + boolean useSurfaceBoundsAsTouchRegion = false; + SurfaceControl touchableRegionCrop = null; final Task task = w.getTask(); if (task != null) { // TODO(b/165794636): Remove the special case for freeform window once drag resizing is @@ -290,20 +294,22 @@ final class InputMonitor { // we need to make sure that these changes in crop are reflected in the input // windows, and so ensure this flag is set so that the input crop always reflects // the surface hierarchy. - // TODO(b/168252846): we have some issues with modal-windows, so we need to cross - // that bridge now that we organize full-screen Tasks. - inputWindowHandle.setTouchableRegionCrop(null /* Use this surfaces crop */); - inputWindowHandle.setReplaceTouchableRegionWithCrop(true); - useSurfaceCrop = true; + useSurfaceBoundsAsTouchRegion = true; + + if (w.mAttrs.isModal()) { + TaskFragment parent = w.getTaskFragment(); + touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null; + } } else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) { - inputWindowHandle.setTouchableRegionCrop(task.getRootTask().getSurfaceControl()); - inputWindowHandle.setReplaceTouchableRegionWithCrop(false); - useSurfaceCrop = true; + touchableRegionCrop = task.getRootTask().getSurfaceControl(); } } - if (!useSurfaceCrop) { - inputWindowHandle.setReplaceTouchableRegionWithCrop(false); - inputWindowHandle.setTouchableRegionCrop(null); + inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion); + inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop); + + if (!useSurfaceBoundsAsTouchRegion) { + w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs); + inputWindowHandle.setTouchableRegion(mTmpRegion); } } diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index fee9884bf333..a843909dd26c 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -75,14 +75,14 @@ class KeyguardController { private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>(); private final ActivityTaskManagerService mService; private RootWindowContainer mRootWindowContainer; - private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer; + private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer; KeyguardController(ActivityTaskManagerService service, ActivityTaskSupervisor taskSupervisor) { mService = service; mTaskSupervisor = taskSupervisor; - mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG); + mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG); } void setWindowManager(WindowManagerService windowManager) { @@ -513,10 +513,10 @@ class KeyguardController { private boolean mRequestDismissKeyguard; private final ActivityTaskManagerService mService; - private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer; + private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer; KeyguardDisplayState(ActivityTaskManagerService service, int displayId, - ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) { + ActivityTaskManagerService.SleepTokenAcquirer acquirer) { mService = service; mDisplayId = displayId; mSleepTokenAcquirer = acquirer; diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java index 7c35a2163d6d..49d30cd27c3e 100644 --- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java @@ -108,7 +108,7 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter { final WindowManagerPolicy policy = service.mPolicy; service.mRoot.forAllWindows(nonAppWindow -> { // Animation on the IME window is controlled via Insets. - if (nonAppWindow.mActivityRecord == null && policy.canBeHiddenByKeyguardLw(nonAppWindow) + if (nonAppWindow.mActivityRecord == null && nonAppWindow.canBeHiddenByKeyguard() && nonAppWindow.wouldBeVisibleIfPolicyIgnored() && !nonAppWindow.isVisible() && nonAppWindow != service.mRoot.getCurrentInputMethodWindow()) { final NonAppWindowAnimationAdapter nonAppAdapter = new NonAppWindowAnimationAdapter( diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ccee45800a00..117b22af4bdf 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -225,7 +225,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off"; /** The token acquirer to put root tasks on the displays to sleep */ - final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer; + final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer; /** * The modes which affect which tasks are returned when calling @@ -470,7 +470,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService = service.mAtmService; mTaskSupervisor = mService.mTaskSupervisor; mTaskSupervisor.mRootWindowContainer = this; - mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG); + mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG); } boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) { @@ -2496,7 +2496,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // starts. Instead, we expect home activities to be launched when the system is ready // (ActivityManagerService#systemReady). if (mService.isBooted() || mService.isBooting()) { - startSystemDecorations(display.mDisplayContent); + startSystemDecorations(display); } // Drop any cached DisplayInfos associated with this display id - the values are now // out of date given this display added event. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index bfb1a8ec3975..b328e4de74f6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2152,10 +2152,7 @@ class Task extends TaskFragment { } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { - if (mWmService.mDisableTransitionAnimation - || !isVisible() - || getSurfaceControl() == null - || !isLeafTask()) { + if (!isLeafTask() || !canStartChangeTransition()) { return false; } // Only do an animation into and out-of freeform mode for now. Other mode @@ -3395,6 +3392,9 @@ class Task extends TaskFragment { info.resizeMode = top != null ? top.mResizeMode : mResizeMode; info.topActivityType = top.getActivityType(); info.isResizeable = isResizeable(); + info.minWidth = mMinWidth; + info.minHeight = mMinHeight; + info.defaultMinSize = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp; info.positionInParent = getRelativePosition(); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 600545e41e47..796a90a2cea2 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -808,6 +808,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Place root home tasks to the bottom. layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer); layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer); + // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and + // make app pair split only have single root then we can just attach the + // divider to the single root task in shell. + layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1); adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer); t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER); } @@ -905,18 +909,24 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { mBackgroundColor = colorInt; Color color = Color.valueOf(colorInt); mColorLayerCounter++; - getPendingTransaction() - .setColor(mSurfaceControl, new float[]{color.red(), color.green(), color.blue()}); - scheduleAnimation(); + // Only apply the background color if the TDA is actually attached and has a valid surface + // to set the background color on. We still want to keep track of the background color state + // even if we are not showing it for when/if the TDA is reattached and gets a valid surface + if (mSurfaceControl != null) { + getPendingTransaction() + .setColor(mSurfaceControl, + new float[]{color.red(), color.green(), color.blue()}); + scheduleAnimation(); + } } void clearBackgroundColor() { mColorLayerCounter--; // Only clear the color layer if we have received the same amounts of clear as set - // requests. - if (mColorLayerCounter == 0) { + // requests and TDA has a non null surface control (i.e. is attached) + if (mColorLayerCounter == 0 && mSurfaceControl != null) { getPendingTransaction().unsetColor(mSurfaceControl); scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 929f2212348a..e497b539c999 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2121,13 +2121,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */ private boolean shouldStartChangeTransition(Rect startBounds) { - if (mWmService.mDisableTransitionAnimation - || mDisplayContent == null - || mTaskFragmentOrganizer == null - || getSurfaceControl() == null - // The change transition will be covered by display. - || mDisplayContent.inTransition() - || !isVisible()) { + if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) { return false; } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 6d83fb6d12c9..29c27f9f3af6 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -31,10 +31,8 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; import android.view.RemoteAnimationDefinition; -import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; -import android.window.TaskFragmentAppearedInfo; import android.window.TaskFragmentInfo; import com.android.internal.protolog.common.ProtoLog; @@ -135,11 +133,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) { ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName()); final TaskFragmentInfo info = tf.getTaskFragmentInfo(); - final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(), - "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared"); try { - organizer.onTaskFragmentAppeared( - new TaskFragmentAppearedInfo(info, outSurfaceControl)); + organizer.onTaskFragmentAppeared(info); mLastSentTaskFragmentInfos.put(tf, info); tf.mTaskFragmentAppearedSent = true; } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f175eec15064..7349594483a8 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -244,11 +244,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } mParticipants.add(wc); if (info.mShowWallpaper) { - // Collect the wallpaper so it is part of the sync set. - final WindowContainer wallpaper = + // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. + final WindowState wallpaper = wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper(); if (wallpaper != null) { - collect(wallpaper); + collect(wallpaper.mToken); } } } @@ -495,25 +495,35 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); return; } - int displayId = DEFAULT_DISPLAY; - for (WindowContainer container : mParticipants) { - if (container.mDisplayContent == null) continue; - displayId = container.mDisplayContent.getDisplayId(); + boolean hasWallpaper = false; + DisplayContent dc = null; + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final WindowContainer<?> wc = mParticipants.valueAt(i); + if (dc == null && wc.mDisplayContent != null) { + dc = wc.mDisplayContent; + } + if (!hasWallpaper && isWallpaper(wc)) { + hasWallpaper = true; + } } + if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay(); if (mState == STATE_ABORT) { mController.abort(this); - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId) - .getPendingTransaction().merge(transaction); + dc.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; return; } + // Ensure that wallpaper visibility is updated with the latest wallpaper target. + if (hasWallpaper) { + dc.mWallpaperController.adjustWallpaperWindows(); + } mState = STATE_PLAYING; mController.moveToPlaying(this); - if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked(displayId)) { + if (dc.isKeyguardLocked()) { mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; } @@ -523,9 +533,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe info.setAnimationOptions(mOverrideOptions); // TODO(b/188669821): Move to animation impl in shell. - handleLegacyRecentsStartBehavior(displayId, info); + handleLegacyRecentsStartBehavior(dc, info); - handleNonAppWindowsInTransition(displayId, mType, mFlags); + handleNonAppWindowsInTransition(dc, mType, mFlags); reportStartReasonsToLogger(); @@ -627,14 +637,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** @see RecentsAnimationController#attachNavigationBarToApp */ - private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) { + private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) { if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) { return; } - final DisplayContent dc = - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); - if (dc == null) return; - mRecentsDisplayId = displayId; + mRecentsDisplayId = dc.mDisplayId; // Recents has an input-consumer to grab input from the "live tile" app. Set that up here final InputConsumerImpl recentsAnimationInputConsumer = @@ -679,7 +686,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Find the top-most non-home, closing app. for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change c = info.getChanges().get(i); - if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId + if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) { continue; @@ -710,7 +717,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe t.setLayer(navSurfaceControl, Integer.MAX_VALUE); } if (mController.mStatusBar != null) { - mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false); + mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false); } } @@ -760,13 +767,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } - private void handleNonAppWindowsInTransition(int displayId, + private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc, @TransitionType int transit, @TransitionFlags int flags) { - final DisplayContent dc = - mController.mAtm.mRootWindowContainer.getDisplayContent(displayId); - if (dc == null) { - return; - } if ((transit == TRANSIT_KEYGUARD_GOING_AWAY || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 08b1a2f39953..e24be378d29a 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -611,8 +611,9 @@ class WallpaperController { private void updateWallpaperTokens(boolean visible) { for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx); - token.updateWallpaperWindows(visible); - token.getDisplayContent().assignWindowLayers(false); + if (token.updateWallpaperWindows(visible)) { + token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 3a639f50603f..fe405e5b3af8 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -104,18 +104,21 @@ class WallpaperWindowToken extends WindowToken { } } - void updateWallpaperWindows(boolean visible) { + /** Returns {@code true} if visibility is changed. */ + boolean updateWallpaperWindows(boolean visible) { + boolean changed = false; if (isVisible() != visible) { ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b", token, visible); setVisibility(visible); + changed = true; } - final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; if (mTransitionController.isShellTransitionsEnabled()) { - return; + return changed; } - final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget(); + final WindowState wallpaperTarget = + mDisplayContent.mWallpaperController.getWallpaperTarget(); if (visible && wallpaperTarget != null) { final RecentsAnimationController recentsAnimationController = @@ -137,6 +140,7 @@ class WallpaperWindowToken extends WindowToken { } setVisible(visible); + return changed; } private void setVisible(boolean visible) { @@ -155,10 +159,12 @@ class WallpaperWindowToken extends WindowToken { * transition. In that situation, make sure to call {@link #commitVisibility} when done. */ void setVisibility(boolean visible) { - // Before setting mVisibleRequested so we can track changes. - mTransitionController.collect(this); + if (mVisibleRequested != visible) { + // Before setting mVisibleRequested so we can track changes. + mTransitionController.collect(this); - setVisibleRequested(visible); + setVisibleRequested(visible); + } // If in a transition, defer commits for activities that are going invisible if (!visible && (mTransitionController.inTransition() diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 98655066ccfc..7e84dbbec311 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2543,6 +2543,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceFreezer.unfreeze(getPendingTransaction()); } + /** Whether we can start change transition with this window and current display status. */ + boolean canStartChangeTransition() { + return !mWmService.mDisableTransitionAnimation && mDisplayContent != null + && getSurfaceControl() != null && !mDisplayContent.inTransition() + && isVisible() && isVisibleRequested() && okToAnimate(); + } + /** * Initializes a change transition. See {@link SurfaceFreezer} for more information. * @@ -2891,12 +2898,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } boolean okToAnimate() { - return okToAnimate(false /* ignoreFrozen */); - } - - boolean okToAnimate(boolean ignoreFrozen) { - final DisplayContent dc = getDisplayContent(); - return dc != null && dc.okToAnimate(ignoreFrozen); + return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index bc530416c8cd..cc527136eb51 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -17,7 +17,9 @@ package com.android.server.wm; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Display.isSuspendedState; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; @@ -45,7 +47,7 @@ import java.util.Objects; * * <ul> * <li>When a {@link WindowContext} is created, it registers the listener via - * {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)} + * {@link WindowManagerService#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)} * automatically.</li> * <li>When the {@link WindowContext} adds the first window to the screen via * {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)}, @@ -53,7 +55,7 @@ import java.util.Objects; * to corresponding {@link WindowToken} via this controller.</li> * <li>When the {@link WindowContext} is GCed, it unregisters the previously * registered listener via - * {@link WindowManagerService#unregisterWindowContextListener(IBinder)}. + * {@link WindowManagerService#detachWindowContextFromWindowContainer(IBinder)}. * {@link WindowManagerService} is also responsible for removing the * {@link WindowContext} created {@link WindowToken}.</li> * </ul> @@ -68,7 +70,7 @@ class WindowContextListenerController { /** * Registers the listener to a {@code container} which is associated with - * a {@code clientToken}, which is a {@link android.app.WindowContext} representation. If the + * a {@code clientToken}, which is a {@link android.window.WindowContext} representation. If the * listener associated with {@code clientToken} hasn't been initialized yet, create one * {@link WindowContextListenerImpl}. Otherwise, the listener associated with * {@code clientToken} switches to listen to the {@code container}. @@ -80,7 +82,7 @@ class WindowContextListenerController { * @param options a bundle used to pass window-related options. */ void registerWindowContainerListener(@NonNull IBinder clientToken, - @NonNull WindowContainer container, int ownerUid, @WindowType int type, + @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { WindowContextListenerImpl listener = mListeners.get(clientToken); if (listener == null) { @@ -103,6 +105,16 @@ class WindowContextListenerController { listener.unregister(); } + void dispatchPendingConfigurationIfNeeded(int displayId) { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final WindowContextListenerImpl listener = mListeners.valueAt(i); + if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId + && listener.mHasPendingConfiguration) { + listener.reportConfigToWindowTokenClient(); + } + } + } + /** * Verifies if the caller is allowed to do the operation to the listener specified by * {@code clientToken}. @@ -138,7 +150,7 @@ class WindowContextListenerController { return listener != null ? listener.mOptions : null; } - @Nullable WindowContainer getContainer(IBinder clientToken) { + @Nullable WindowContainer<?> getContainer(IBinder clientToken) { final WindowContextListenerImpl listener = mListeners.get(clientToken); return listener != null ? listener.mContainer : null; } @@ -163,7 +175,7 @@ class WindowContextListenerController { class WindowContextListenerImpl implements WindowContainerListener { @NonNull private final IBinder mClientToken; private final int mOwnerUid; - @NonNull private WindowContainer mContainer; + @NonNull private WindowContainer<?> mContainer; /** * The options from {@link Context#createWindowContext(int, Bundle)}. * <p>It can be used for choosing the {@link DisplayArea} where the window context @@ -177,7 +189,9 @@ class WindowContextListenerController { private int mLastReportedDisplay = INVALID_DISPLAY; private Configuration mLastReportedConfig; - private WindowContextListenerImpl(IBinder clientToken, WindowContainer container, + private boolean mHasPendingConfiguration; + + private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { mClientToken = clientToken; mContainer = Objects.requireNonNull(container); @@ -197,11 +211,11 @@ class WindowContextListenerController { /** TEST ONLY: returns the {@link WindowContainer} of the listener */ @VisibleForTesting - WindowContainer getWindowContainer() { + WindowContainer<?> getWindowContainer() { return mContainer; } - private void updateContainer(@NonNull WindowContainer newContainer) { + private void updateContainer(@NonNull WindowContainer<?> newContainer) { Objects.requireNonNull(newContainer); if (mContainer.equals(newContainer)) { @@ -246,12 +260,20 @@ class WindowContextListenerController { if (mDeathRecipient == null) { throw new IllegalStateException("Invalid client token: " + mClientToken); } - - if (mLastReportedConfig == null) { - mLastReportedConfig = new Configuration(); + // If the display of window context associated window container is suspended, don't + // report the configuration update. Note that we still dispatch the configuration update + // to WindowProviderService to make it compatible with Service#onConfigurationChanged. + // Service always receives #onConfigurationChanged callback regardless of display state. + if (!isWindowProviderService(mOptions) + && isSuspendedState(mContainer.getDisplayContent().getDisplayInfo().state)) { + mHasPendingConfiguration = true; + return; } final Configuration config = mContainer.getConfiguration(); final int displayId = mContainer.getDisplayContent().getDisplayId(); + if (mLastReportedConfig == null) { + mLastReportedConfig = new Configuration(); + } if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { // No changes since last reported time. return; @@ -266,6 +288,7 @@ class WindowContextListenerController { } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); } + mHasPendingConfiguration = false; } @Override @@ -283,7 +306,7 @@ class WindowContextListenerController { // If we cannot obtain the DisplayContent, the DisplayContent may also be removed. // We should proceed the removal process. if (dc != null) { - final DisplayArea da = dc.findAreaForToken(windowToken); + final DisplayArea<?> da = dc.findAreaForToken(windowToken); updateContainer(da); return; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4720d7c096fd..23f9c58b9af9 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2700,6 +2700,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId, Bundle options) { + if (clientToken == null) { + throw new IllegalArgumentException("clientToken must not be null!"); + } final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, "attachWindowContextToDisplayArea", false /* printLog */); final int callingUid = Binder.getCallingUid(); @@ -2790,6 +2793,39 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public Configuration attachToDisplayContent(IBinder clientToken, int displayId) { + if (clientToken == null) { + throw new IllegalArgumentException("clientToken must not be null!"); + } + final int callingUid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + // We use "getDisplayContent" instead of "getDisplayContentOrCreate" because + // this method may be called in DisplayPolicy's constructor and may cause + // infinite loop. In this scenario, we early return here and switch to do the + // registration in DisplayContent#onParentChanged at DisplayContent initialization. + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + if (Binder.getCallingPid() != myPid()) { + throw new WindowManager.InvalidDisplayException("attachToDisplayContent: " + + "trying to attach to a non-existing display:" + displayId); + } + // Early return if this method is invoked from system process. + // See above comments for more detail. + return null; + } + + mWindowContextListenerController.registerWindowContainerListener(clientToken, dc, + callingUid, INVALID_WINDOW_TYPE, null /* options */); + return dc.getConfiguration(); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + /** Returns {@code true} if this binder is a registered window token. */ @Override public boolean isWindowToken(IBinder binder) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 81b241eddb84..bae5465c4fa2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -48,7 +48,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; import static android.view.WindowManager.LayoutParams.FLAG_SCALED; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; @@ -1338,8 +1337,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height()); } - @Override - public WindowManager.LayoutParams getAttrs() { + WindowManager.LayoutParams getAttrs() { return mAttrs; } @@ -2687,10 +2685,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - int getSurfaceTouchableRegion(Region region, int flags) { - final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; + void getSurfaceTouchableRegion(Region region, WindowManager.LayoutParams attrs) { + final boolean modal = attrs.isModal(); if (modal) { - flags |= FLAG_NOT_TOUCH_MODAL; if (mActivityRecord != null) { // Limit the outer touch to the activity root task region. updateRegionForModalActivityWindow(region); @@ -2722,8 +2719,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mInvGlobalScale != 1.f) { region.scale(mInvGlobalScale); } - - return flags; } /** @@ -3414,7 +3409,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } // Exclude toast because legacy apps may show toast window by themselves, so the misused // apps won't always be considered as foreground state. - if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) { + // Exclude private presentations as they can only be shown on private virtual displays and + // shouldn't be the cause of an app be considered foreground. + if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST + && mAttrs.type != TYPE_PRIVATE_PRESENTATION) { mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown); } } @@ -3559,10 +3557,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.} */ void getEffectiveTouchableRegion(Region outRegion) { - final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0; final DisplayContent dc = getDisplayContent(); - if (modal && dc != null) { + if (mAttrs.isModal() && dc != null) { outRegion.set(dc.getBounds()); cropRegionToRootTaskBoundsIfNeeded(outRegion); subtractTouchExcludeRegionIfNeeded(outRegion); @@ -3835,6 +3832,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return (mAttrs.insetsFlags.behavior & BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) != 0; } + boolean canBeHiddenByKeyguard() { + // Keyguard visibility of window from activities are determined over activity visibility. + if (mActivityRecord != null) { + return false; + } + switch (mAttrs.type) { + case TYPE_NOTIFICATION_SHADE: + case TYPE_STATUS_BAR: + case TYPE_NAVIGATION_BAR: + case TYPE_WALLPAPER: + return false; + default: + // Hide only windows below the keyguard host window. + return mPolicy.getWindowLayerLw(this) + < mPolicy.getWindowLayerFromTypeLw(TYPE_NOTIFICATION_SHADE); + } + } + private int getRootTaskId() { final Task rootTask = getRootTask(); if (rootTask == null) { @@ -4710,6 +4725,48 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + private boolean shouldFinishAnimatingExit() { + // Exit animation might be applied soon. + if (inTransition()) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s", + this); + return false; + } + if (!mDisplayContent.okToAnimate()) { + return true; + } + // Exit animation is running. + if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s", + this); + return false; + } + // If the wallpaper is currently behind this app window, we need to change both of + // them inside of a transaction to avoid artifacts. + if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, + "shouldWaitAnimatingExit: isWallpaperTarget: %s", this); + return false; + } + return true; + } + + /** + * If this is window is stuck in the animatingExit status, resume clean up procedure blocked + * by the exit animation. + */ + void cleanupAnimatingExitWindow() { + // TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation + // and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall + // be called, but there seems to be a case that #onExitAnimationDone is not triggered, so + // a windows stuck in the animatingExit status. + if (mAnimatingExit && shouldFinishAnimatingExit()) { + ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s", + this); + onExitAnimationDone(); + } + } + void onExitAnimationDone() { if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 6204824d70a9..ca834c56e0f0 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -48,12 +48,12 @@ import java.io.PrintWriter; class WindowTracing { /** - * Maximum buffer size, currently defined as 512 KB + * Maximum buffer size, currently defined as 5 MB * Size was experimentally defined to fit between 100 to 150 elements. */ - private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024; - private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024; - private static final int BUFFER_CAPACITY_ALL = 4096 * 1024; + private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB + private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB + private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB static final String WINSCOPE_EXT = ".winscope"; private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT; private static final String TAG = "WindowTracing"; diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 0a55003e77fc..2ccef9a61ffb 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -29,6 +29,8 @@ #include <android/hardware/gnss/2.1/IGnssMeasurement.h> #include <android/hardware/gnss/BnGnss.h> #include <android/hardware/gnss/BnGnssCallback.h> +#include <android/hardware/gnss/BnGnssGeofence.h> +#include <android/hardware/gnss/BnGnssGeofenceCallback.h> #include <android/hardware/gnss/BnGnssMeasurementCallback.h> #include <android/hardware/gnss/BnGnssPowerIndicationCallback.h> #include <android/hardware/gnss/BnGnssPsdsCallback.h> @@ -54,13 +56,13 @@ #include "gnss/GnssBatching.h" #include "gnss/GnssConfiguration.h" #include "gnss/GnssMeasurement.h" +#include "gnss/GnssNavigationMessage.h" #include "gnss/Utils.h" #include "hardware_legacy/power.h" #include "jni.h" #include "utils/Log.h" #include "utils/misc.h" -static jclass class_gnssNavigationMessage; static jclass class_gnssPowerStats; static jmethodID method_reportLocation; @@ -83,7 +85,6 @@ static jmethodID method_reportGeofenceAddStatus; static jmethodID method_reportGeofenceRemoveStatus; static jmethodID method_reportGeofencePauseStatus; static jmethodID method_reportGeofenceResumeStatus; -static jmethodID method_reportNavigationMessages; static jmethodID method_reportGnssServiceDied; static jmethodID method_reportGnssPowerStats; static jmethodID method_setSubHalMeasurementCorrectionsCapabilities; @@ -113,7 +114,6 @@ static jmethodID method_correctionPlaneAltDeg; static jmethodID method_correctionPlaneAzimDeg; static jmethodID method_reportNfwNotification; static jmethodID method_isInEmergencySession; -static jmethodID method_gnssNavigationMessageCtor; static jmethodID method_gnssPowerStatsCtor; static jmethodID method_setSubHalPowerIndicationCapabilities; @@ -191,6 +191,9 @@ using android::hardware::gnss::IGnssPowerIndicationCallback; using android::hardware::gnss::PsdsType; using IGnssAidl = android::hardware::gnss::IGnss; using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback; +using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching; +using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence; +using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback; using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds; using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback; using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration; @@ -216,6 +219,8 @@ sp<IGnss_V1_1> gnssHal_V1_1 = nullptr; sp<IGnss_V2_0> gnssHal_V2_0 = nullptr; sp<IGnss_V2_1> gnssHal_V2_1 = nullptr; sp<IGnssAidl> gnssHalAidl = nullptr; +sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr; +sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr; sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; sp<IGnssXtra> gnssXtraIface = nullptr; sp<IAGnssRil_V1_0> agnssRilIface = nullptr; @@ -226,7 +231,6 @@ sp<IAGnss_V2_0> agnssIface_V2_0 = nullptr; sp<IGnssDebug_V1_0> gnssDebugIface = nullptr; sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr; sp<IGnssNi> gnssNiIface = nullptr; -sp<IGnssNavigationMessage> gnssNavigationMessageIface = nullptr; sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr; sp<IMeasurementCorrections_V1_0> gnssCorrectionsIface_V1_0 = nullptr; sp<IMeasurementCorrections_V1_1> gnssCorrectionsIface_V1_1 = nullptr; @@ -235,6 +239,7 @@ sp<IGnssAntennaInfo> gnssAntennaInfoIface = nullptr; std::unique_ptr<GnssConfigurationInterface> gnssConfigurationIface = nullptr; std::unique_ptr<android::gnss::GnssMeasurementInterface> gnssMeasurementIface = nullptr; +std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr; std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr; #define WAKE_LOCK_NAME "GPS" @@ -709,35 +714,25 @@ Return<void> GnssXtraCallback::downloadRequestCb() { return Void(); } -/* - * GnssGeofenceCallback class implements the callback methods for the - * IGnssGeofence interface. - */ -struct GnssGeofenceCallback : public IGnssGeofenceCallback { - // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow. - Return<void> gnssGeofenceTransitionCb( - int32_t geofenceId, - const GnssLocation_V1_0& location, - GeofenceTransition transition, - hardware::gnss::V1_0::GnssUtcTime timestamp) override; - Return<void> - gnssGeofenceStatusCb( - GeofenceAvailability status, - const GnssLocation_V1_0& location) override; - Return<void> gnssGeofenceAddCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofencePauseCb(int32_t geofenceId, - GeofenceStatus status) override; - Return<void> gnssGeofenceResumeCb(int32_t geofenceId, - GeofenceStatus status) override; +/** Util class for GnssGeofenceCallback methods. */ +struct GnssGeofenceCallbackUtil { + template <class T> + static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition, + int64_t timestampMillis); + template <class T> + static void gnssGeofenceStatusCb(int availability, const T& lastLocation); + static void gnssGeofenceAddCb(int geofenceId, int status); + static void gnssGeofenceRemoveCb(int geofenceId, int status); + static void gnssGeofencePauseCb(int geofenceId, int status); + static void gnssGeofenceResumeCb(int geofenceId, int status); + +private: + GnssGeofenceCallbackUtil() = delete; }; -Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( - int32_t geofenceId, const GnssLocation_V1_0& location, - GeofenceTransition transition, - hardware::gnss::V1_0::GnssUtcTime timestamp) { +template <class T> +void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location, + int transition, int64_t timestamp) { JNIEnv* env = getJniEnv(); jobject jLocation = translateGnssLocation(env, location); @@ -751,27 +746,22 @@ Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( checkAndClearExceptionFromCallback(env, __FUNCTION__); env->DeleteLocalRef(jLocation); - return Void(); } -Return<void> -GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability status, - const GnssLocation_V1_0& location) { +template <class T> +void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) { JNIEnv* env = getJniEnv(); - jobject jLocation = translateGnssLocation(env, location); + jobject jLocation = translateGnssLocation(env, lastLocation); - env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status, - jLocation); + env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation); checkAndClearExceptionFromCallback(env, __FUNCTION__); env->DeleteLocalRef(jLocation); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status); } @@ -780,13 +770,11 @@ Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status); } @@ -794,13 +782,11 @@ Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, method_reportGeofenceRemoveStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status); } @@ -808,13 +794,11 @@ Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, method_reportGeofencePauseStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } -Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, - GeofenceStatus status) { +void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) { JNIEnv* env = getJniEnv(); - if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) { + if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) { ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status); } @@ -822,50 +806,104 @@ Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, method_reportGeofenceResumeStatus, geofenceId, status); checkAndClearExceptionFromCallback(env, __FUNCTION__); - return Void(); } /* - * GnssNavigationMessageCallback interface implements the callback methods - * required by the IGnssNavigationMessage interface. + * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL + * interface. */ -struct GnssNavigationMessageCallback : public IGnssNavigationMessageCallback { - /* - * Methods from ::android::hardware::gps::V1_0::IGnssNavigationMessageCallback - * follow. - */ - Return<void> gnssNavigationMessageCb( - const IGnssNavigationMessageCallback::GnssNavigationMessage& message) override; +struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback { + Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location, + int transition, int64_t timestampMillis) override; + Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override; + Status gnssGeofenceAddCb(int geofenceId, int status) override; + Status gnssGeofenceRemoveCb(int geofenceId, int status) override; + Status gnssGeofencePauseCb(int geofenceId, int status) override; + Status gnssGeofenceResumeCb(int geofenceId, int status) override; }; -Return<void> GnssNavigationMessageCallback::gnssNavigationMessageCb( - const IGnssNavigationMessageCallback::GnssNavigationMessage& message) { - JNIEnv* env = getJniEnv(); +Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId, + const GnssLocationAidl& location, + int transition, int64_t timestampMillis) { + GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition, + timestampMillis); + return Status::ok(); +} - size_t dataLength = message.data.size(); +Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability, + const GnssLocationAidl& lastLocation) { + GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation); + return Status::ok(); +} - std::vector<uint8_t> navigationData = message.data; - uint8_t* data = &(navigationData[0]); - if (dataLength == 0 || data == nullptr) { - ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, - dataLength); - return Void(); - } +Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status); + return Status::ok(); +} - JavaObject object(env, class_gnssNavigationMessage, method_gnssNavigationMessageCtor); - SET(Type, static_cast<int32_t>(message.type)); - SET(Svid, static_cast<int32_t>(message.svid)); - SET(MessageId, static_cast<int32_t>(message.messageId)); - SET(SubmessageId, static_cast<int32_t>(message.submessageId)); - object.callSetter("setData", data, dataLength); - SET(Status, static_cast<int32_t>(message.status)); +Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status); + return Status::ok(); +} - jobject navigationMessage = object.get(); - env->CallVoidMethod(mCallbacksObj, - method_reportNavigationMessages, - navigationMessage); - checkAndClearExceptionFromCallback(env, __FUNCTION__); - env->DeleteLocalRef(navigationMessage); +Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status); + return Status::ok(); +} + +Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) { + GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status); + return Status::ok(); +} + +/* + * GnssGeofenceCallback class implements the callback methods for the + * IGnssGeofence HIDL interface. + */ +struct GnssGeofenceCallback : public IGnssGeofenceCallback { + // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow. + Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location, + GeofenceTransition transition, + hardware::gnss::V1_0::GnssUtcTime timestamp) override; + Return<void> gnssGeofenceStatusCb(GeofenceAvailability status, + const GnssLocation_V1_0& location) override; + Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override; + Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override; +}; + +Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb( + int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition, + hardware::gnss::V1_0::GnssUtcTime timestamp) { + GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition, + (int64_t)timestamp); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability, + const GnssLocation_V1_0& location) { + GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status); + return Void(); +} + +Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) { + GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status); return Void(); } @@ -1180,10 +1218,6 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc "(II)V"); method_reportGeofencePauseStatus = env->GetMethodID(clazz, "reportGeofencePauseStatus", "(II)V"); - method_reportNavigationMessages = env->GetMethodID( - clazz, - "reportNavigationMessage", - "(Landroid/location/GnssNavigationMessage;)V"); method_reportGnssServiceDied = env->GetMethodID(clazz, "reportGnssServiceDied", "()V"); method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification", "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V"); @@ -1253,14 +1287,11 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc class_gnssPowerStats = (jclass)env->NewGlobalRef(gnssPowerStatsClass); method_gnssPowerStatsCtor = env->GetMethodID(class_gnssPowerStats, "<init>", "(IJDDDDDD[D)V"); - jclass gnssNavigationMessageClass = env->FindClass("android/location/GnssNavigationMessage"); - class_gnssNavigationMessage = (jclass) env->NewGlobalRef(gnssNavigationMessageClass); - method_gnssNavigationMessageCtor = env->GetMethodID(class_gnssNavigationMessage, "<init>", "()V"); - gnss::GnssAntennaInfo_class_init_once(env, clazz); gnss::GnssBatching_class_init_once(env, clazz); gnss::GnssConfiguration_class_init_once(env); gnss::GnssMeasurement_class_init_once(env, clazz); + gnss::GnssNavigationMessage_class_init_once(env, clazz); gnss::Utils_class_init_once(env); } @@ -1279,11 +1310,13 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject android_location_gnss_hal_GnssNative_set_gps_service_handle(); } - if (gnssHal == nullptr) { + if (gnssHal == nullptr && gnssHalAidl == nullptr) { ALOGE("Unable to get GPS service\n"); return; } + // TODO: linkToDeath for AIDL HAL + gnssHalDeathRecipient = new GnssDeathRecipient(); hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0); if (!linked.isOk()) { @@ -1303,7 +1336,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } else { ALOGD("Unable to get a handle to PSDS AIDL interface."); } - } else { + } else if (gnssHal != nullptr) { auto gnssXtra = gnssHal->getExtensionXtra(); if (!gnssXtra.isOk()) { ALOGD("Unable to get a handle to Xtra"); @@ -1320,7 +1353,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject agnssRilIface_V2_0 = agnssRil_V2_0; agnssRilIface = agnssRilIface_V2_0; } - } else { + } else if (gnssHal != nullptr) { auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil(); if (!agnssRil_V1_0.isOk()) { ALOGD("Unable to get a handle to AGnssRil"); @@ -1336,7 +1369,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } else { agnssIface_V2_0 = agnss_V2_0; } - } else { + } else if (gnssHal != nullptr) { auto agnss_V1_0 = gnssHal->getExtensionAGnss(); if (!agnss_V1_0.isOk()) { ALOGD("Unable to get a handle to AGnss"); @@ -1345,11 +1378,21 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage(); - if (!gnssNavigationMessage.isOk()) { - ALOGD("Unable to get a handle to GnssNavigationMessage"); - } else { - gnssNavigationMessageIface = gnssNavigationMessage; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + sp<hardware::gnss::IGnssNavigationMessageInterface> gnssNavigationMessage; + auto status = gnssHalAidl->getExtensionGnssNavigationMessage(&gnssNavigationMessage); + if (checkAidlStatus(status, + "Unable to get a handle to GnssNavigationMessage AIDL interface.")) { + gnssNavigationMessageIface = + std::make_unique<gnss::GnssNavigationMessageAidl>(gnssNavigationMessage); + } + } else if (gnssHal != nullptr) { + auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage(); + if (checkHidlReturn(gnssNavigationMessage, + "Unable to get a handle to GnssNavigationMessage interface.")) { + gnssNavigationMessageIface = + std::make_unique<gnss::GnssNavigationMessageHidl>(gnssNavigationMessage); + } } // Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means, @@ -1387,12 +1430,12 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement); } } - if (gnssMeasurementIface == nullptr) { - auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement(); - if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) { - gnssMeasurementIface = - std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement); - } + if (gnssHal != nullptr && gnssMeasurementIface == nullptr) { + auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement(); + if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) { + gnssMeasurementIface = + std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement); + } } if (gnssHal_V2_1 != nullptr) { @@ -1434,7 +1477,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssDebugIface = gnssDebugIface_V2_0; } } - if (gnssDebugIface == nullptr) { + if (gnssHal != nullptr && gnssDebugIface == nullptr) { auto gnssDebug = gnssHal->getExtensionGnssDebug(); if (!gnssDebug.isOk()) { ALOGD("Unable to get a handle to GnssDebug"); @@ -1443,11 +1486,13 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssNi = gnssHal->getExtensionGnssNi(); - if (!gnssNi.isOk()) { - ALOGD("Unable to get a handle to GnssNi"); - } else { - gnssNiIface = gnssNi; + if (gnssHal != nullptr) { + auto gnssNi = gnssHal->getExtensionGnssNi(); + if (!gnssNi.isOk()) { + ALOGD("Unable to get a handle to GnssNi"); + } else { + gnssNiIface = gnssNi; + } } if (gnssHalAidl != nullptr) { @@ -1488,11 +1533,17 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing(); - if (!gnssGeofencing.isOk()) { - ALOGD("Unable to get a handle to GnssGeofencing"); - } else { - gnssGeofencingIface = gnssGeofencing; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + sp<IGnssGeofenceAidl> gnssGeofenceAidl; + auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl); + if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) { + gnssGeofenceAidlIface = gnssGeofenceAidl; + } + } else if (gnssHal != nullptr) { + auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing(); + if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) { + gnssGeofencingIface = gnssGeofencing; + } } if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { @@ -1507,7 +1558,7 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0); } } - if (gnssBatchingIface == nullptr) { + if (gnssHal != nullptr && gnssBatchingIface == nullptr) { auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching(); if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) { gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0); @@ -1568,7 +1619,7 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl /* * Fail if the main interface fails to initialize */ - if (gnssHal == nullptr) { + if (gnssHal == nullptr && gnssHalAidl == nullptr) { ALOGE("Unable to initialize GNSS HAL."); return JNI_FALSE; } @@ -1583,7 +1634,7 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl result = gnssHal_V2_0->setCallback_2_0(gnssCbIface); } else if (gnssHal_V1_1 != nullptr) { result = gnssHal_V1_1->setCallback_1_1(gnssCbIface); - } else { + } else if (gnssHal != nullptr) { result = gnssHal->setCallback(gnssCbIface); } @@ -1630,10 +1681,18 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } // Set IGnssGeofencing.hal callback. - sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback(); - if (gnssGeofencingIface != nullptr) { + if (gnssGeofenceAidlIface != nullptr) { + sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl(); + auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl); + if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) { + gnssGeofenceAidlIface = nullptr; + } + } else if (gnssGeofencingIface != nullptr) { + sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback(); auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface); - checkHidlReturn(status, "IGnssGeofencing setCallback() failed."); + if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) { + gnssGeofencingIface = nullptr; + } } else { ALOGI("Unable to initialize IGnssGeofencing interface."); } @@ -1693,12 +1752,15 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) { - if (gnssHal == nullptr) { - return; + if (gnssHalAidl != nullptr) { + auto status = gnssHalAidl->close(); + checkAidlStatus(status, "IGnssAidl close() failed."); } - auto result = gnssHal->cleanup(); - checkHidlReturn(result, "IGnss cleanup() failed."); + if (gnssHal != nullptr) { + auto result = gnssHal->cleanup(); + checkHidlReturn(result, "IGnss cleanup() failed."); + } } static jboolean android_location_gnss_hal_GnssNative_set_position_mode( @@ -2177,57 +2239,85 @@ static void android_location_GnssNetworkConnectivityHandler_update_network_state static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */, jclass) { - return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE; + if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; } static jboolean android_location_gnss_hal_GnssNative_add_geofence( JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude, jdouble radius, jint last_transition, jint monitor_transition, jint notification_responsiveness, jint unknown_timer) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = + gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius, + last_transition, monitor_transition, + notification_responsiveness, unknown_timer); + return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed."); } - auto result = gnssGeofencingIface->addGeofence( - geofenceId, latitude, longitude, radius, - static_cast<IGnssGeofenceCallback::GeofenceTransition>(last_transition), - monitor_transition, notification_responsiveness, unknown_timer); - return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed."); + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface + ->addGeofence(geofenceId, latitude, longitude, radius, + static_cast<IGnssGeofenceCallback::GeofenceTransition>( + last_transition), + monitor_transition, notification_responsiveness, + unknown_timer); + return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed."); + } + + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass, jint geofenceId) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId); + return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed."); + } + + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->removeGeofence(geofenceId); + return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed."); } - auto result = gnssGeofencingIface->removeGeofence(geofenceId); - return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed."); + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass, jint geofenceId) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId); + return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed."); } - auto result = gnssGeofencingIface->pauseGeofence(geofenceId); - return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed."); + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->pauseGeofence(geofenceId); + return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed."); + } + + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass, jint geofenceId, jint monitor_transition) { - if (gnssGeofencingIface == nullptr) { - ALOGE("%s: IGnssGeofencing interface not available.", __func__); - return JNI_FALSE; + if (gnssGeofenceAidlIface != nullptr) { + auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition); + return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed."); + } + + if (gnssGeofencingIface != nullptr) { + auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition); + return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed."); } - auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition); - return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed."); + ALOGE("%s: IGnssGeofencing interface not available.", __func__); + return JNI_FALSE; } static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env, @@ -2489,20 +2579,8 @@ static jboolean android_location_gnss_hal_GnssNative_start_navigation_message_co return JNI_FALSE; } - sp<IGnssNavigationMessageCallback> gnssNavigationMessageCbIface = - new GnssNavigationMessageCallback(); - auto result = gnssNavigationMessageIface->setCallback(gnssNavigationMessageCbIface); - if (!checkHidlReturn(result, "IGnssNavigationMessage setCallback() failed.")) { - return JNI_FALSE; - } - - IGnssNavigationMessage::GnssNavigationMessageStatus initRet = result; - if (initRet != IGnssNavigationMessage::GnssNavigationMessageStatus::SUCCESS) { - ALOGE("An error has been found in %s: %d", __FUNCTION__, static_cast<int32_t>(initRet)); - return JNI_FALSE; - } - - return JNI_TRUE; + return gnssNavigationMessageIface->setCallback( + std::make_unique<gnss::GnssNavigationMessageCallback>()); } static jboolean android_location_gnss_hal_GnssNative_stop_navigation_message_collection(JNIEnv* env, @@ -2511,9 +2589,7 @@ static jboolean android_location_gnss_hal_GnssNative_stop_navigation_message_col ALOGE("%s: IGnssNavigationMessage interface not available.", __func__); return JNI_FALSE; } - - auto result = gnssNavigationMessageIface->close(); - return checkHidlReturn(result, "IGnssNavigationMessage close() failed."); + return gnssNavigationMessageIface->close(); } static jboolean android_location_GnssConfiguration_set_emergency_supl_pdn(JNIEnv*, diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp index 090166a0bc1d..6c6b30405ab3 100644 --- a/services/core/jni/gnss/Android.bp +++ b/services/core/jni/gnss/Android.bp @@ -29,6 +29,8 @@ cc_library_shared { "GnssConfiguration.cpp", "GnssMeasurement.cpp", "GnssMeasurementCallback.cpp", + "GnssNavigationMessage.cpp", + "GnssNavigationMessageCallback.cpp", "Utils.cpp", ], } diff --git a/services/core/jni/gnss/GnssNavigationMessage.cpp b/services/core/jni/gnss/GnssNavigationMessage.cpp new file mode 100644 index 000000000000..75aee74380a7 --- /dev/null +++ b/services/core/jni/gnss/GnssNavigationMessage.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Define LOG_TAG before <log/log.h> to overwrite the default value. +#define LOG_TAG "GnssNavigationMessageJni" + +#include "GnssNavigationMessage.h" + +#include "Utils.h" + +namespace android::gnss { + +using hardware::gnss::IGnssNavigationMessageInterface; +using IGnssNavigationMessageHidl = hardware::gnss::V1_0::IGnssNavigationMessage; + +// Implementation of GnssNavigationMessage (AIDL HAL) + +GnssNavigationMessageAidl::GnssNavigationMessageAidl( + const sp<IGnssNavigationMessageInterface>& iGnssNavigationMessage) + : mIGnssNavigationMessage(iGnssNavigationMessage) { + assert(mIGnssNavigationMessage != nullptr); +} + +jboolean GnssNavigationMessageAidl::setCallback( + const std::unique_ptr<GnssNavigationMessageCallback>& callback) { + auto status = mIGnssNavigationMessage->setCallback(callback->getAidl()); + return checkAidlStatus(status, "IGnssNavigationMessageAidl setCallback() failed."); +} + +jboolean GnssNavigationMessageAidl::close() { + auto status = mIGnssNavigationMessage->close(); + return checkAidlStatus(status, "IGnssNavigationMessageAidl close() failed"); +} + +// Implementation of GnssNavigationMessageHidl + +GnssNavigationMessageHidl::GnssNavigationMessageHidl( + const sp<IGnssNavigationMessageHidl>& iGnssNavigationMessage) + : mIGnssNavigationMessageHidl(iGnssNavigationMessage) { + assert(mIGnssNavigationMessageHidl != nullptr); +} + +jboolean GnssNavigationMessageHidl::setCallback( + const std::unique_ptr<GnssNavigationMessageCallback>& callback) { + auto result = mIGnssNavigationMessageHidl->setCallback(callback->getHidl()); + + IGnssNavigationMessageHidl::GnssNavigationMessageStatus initRet = result; + if (initRet != IGnssNavigationMessageHidl::GnssNavigationMessageStatus::SUCCESS) { + ALOGE("An error has been found in %s: %d", __FUNCTION__, static_cast<int32_t>(initRet)); + return JNI_FALSE; + } + return JNI_TRUE; +} + +jboolean GnssNavigationMessageHidl::close() { + auto result = mIGnssNavigationMessageHidl->close(); + return checkHidlReturn(result, "IGnssNavigationMessage close() failed."); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/GnssNavigationMessage.h b/services/core/jni/gnss/GnssNavigationMessage.h new file mode 100644 index 000000000000..e3a1e4a49ab1 --- /dev/null +++ b/services/core/jni/gnss/GnssNavigationMessage.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H +#define _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IGnssNavigationMessage.h> +#include <android/hardware/gnss/BnGnssNavigationMessageInterface.h> +#include <log/log.h> + +#include "GnssNavigationMessageCallback.h" +#include "jni.h" + +namespace android::gnss { + +class GnssNavigationMessageInterface { +public: + virtual ~GnssNavigationMessageInterface() {} + virtual jboolean setCallback( + const std::unique_ptr<GnssNavigationMessageCallback>& callback) = 0; + virtual jboolean close() = 0; +}; + +class GnssNavigationMessageAidl : public GnssNavigationMessageInterface { +public: + GnssNavigationMessageAidl(const sp<android::hardware::gnss::IGnssNavigationMessageInterface>& + iGnssNavigationMessage); + jboolean setCallback(const std::unique_ptr<GnssNavigationMessageCallback>& callback) override; + jboolean close() override; + +private: + const sp<android::hardware::gnss::IGnssNavigationMessageInterface> mIGnssNavigationMessage; +}; + +class GnssNavigationMessageHidl : public GnssNavigationMessageInterface { +public: + GnssNavigationMessageHidl(const sp<android::hardware::gnss::V1_0::IGnssNavigationMessage>& + iGnssNavigationMessage); + jboolean setCallback(const std::unique_ptr<GnssNavigationMessageCallback>& callback) override; + jboolean close() override; + +private: + const sp<android::hardware::gnss::V1_0::IGnssNavigationMessage> mIGnssNavigationMessageHidl; +}; + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGE_H diff --git a/services/core/jni/gnss/GnssNavigationMessageCallback.cpp b/services/core/jni/gnss/GnssNavigationMessageCallback.cpp new file mode 100644 index 000000000000..1779c95d12f3 --- /dev/null +++ b/services/core/jni/gnss/GnssNavigationMessageCallback.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GnssNavMsgCbJni" + +#include "GnssNavigationMessageCallback.h" + +namespace android::gnss { + +namespace { + +jclass class_gnssNavigationMessage; +jmethodID method_reportNavigationMessages; +jmethodID method_gnssNavigationMessageCtor; + +} // anonymous namespace + +using binder::Status; +using hardware::Return; +using hardware::Void; + +using GnssNavigationMessageAidl = + android::hardware::gnss::IGnssNavigationMessageCallback::GnssNavigationMessage; +using GnssNavigationMessageHidl = + android::hardware::gnss::V1_0::IGnssNavigationMessageCallback::GnssNavigationMessage; + +void GnssNavigationMessage_class_init_once(JNIEnv* env, jclass clazz) { + method_reportNavigationMessages = + env->GetMethodID(clazz, "reportNavigationMessage", + "(Landroid/location/GnssNavigationMessage;)V"); + + jclass gnssNavigationMessageClass = env->FindClass("android/location/GnssNavigationMessage"); + class_gnssNavigationMessage = (jclass)env->NewGlobalRef(gnssNavigationMessageClass); + method_gnssNavigationMessageCtor = + env->GetMethodID(class_gnssNavigationMessage, "<init>", "()V"); +} + +Status GnssNavigationMessageCallbackAidl::gnssNavigationMessageCb( + const GnssNavigationMessageAidl& message) { + GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(message); + return Status::ok(); +} + +Return<void> GnssNavigationMessageCallbackHidl::gnssNavigationMessageCb( + const GnssNavigationMessageHidl& message) { + GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(message); + return Void(); +} + +} // namespace android::gnss diff --git a/services/core/jni/gnss/GnssNavigationMessageCallback.h b/services/core/jni/gnss/GnssNavigationMessageCallback.h new file mode 100644 index 000000000000..fe76fc75bd39 --- /dev/null +++ b/services/core/jni/gnss/GnssNavigationMessageCallback.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H +#define _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H + +#pragma once + +#ifndef LOG_TAG +#error LOG_TAG must be defined before including this file. +#endif + +#include <android/hardware/gnss/1.0/IGnssNavigationMessage.h> +#include <android/hardware/gnss/BnGnssNavigationMessageCallback.h> +#include <log/log.h> + +#include <vector> + +#include "Utils.h" +#include "jni.h" + +namespace android::gnss { + +namespace { + +extern jclass class_gnssNavigationMessage; +extern jmethodID method_reportNavigationMessages; +extern jmethodID method_gnssNavigationMessageCtor; + +} // anonymous namespace + +void GnssNavigationMessage_class_init_once(JNIEnv* env, jclass clazz); + +class GnssNavigationMessageCallbackAidl : public hardware::gnss::BnGnssNavigationMessageCallback { +public: + GnssNavigationMessageCallbackAidl() {} + android::binder::Status gnssNavigationMessageCb( + const hardware::gnss::IGnssNavigationMessageCallback::GnssNavigationMessage& message) + override; +}; + +class GnssNavigationMessageCallbackHidl + : public hardware::gnss::V1_0::IGnssNavigationMessageCallback { +public: + GnssNavigationMessageCallbackHidl() {} + + hardware::Return<void> gnssNavigationMessageCb( + const hardware::gnss::V1_0::IGnssNavigationMessageCallback::GnssNavigationMessage& + message) override; +}; + +class GnssNavigationMessageCallback { +public: + GnssNavigationMessageCallback() {} + sp<GnssNavigationMessageCallbackAidl> getAidl() { + if (callbackAidl == nullptr) { + callbackAidl = sp<GnssNavigationMessageCallbackAidl>::make(); + } + return callbackAidl; + } + + sp<GnssNavigationMessageCallbackHidl> getHidl() { + if (callbackHidl == nullptr) { + callbackHidl = sp<GnssNavigationMessageCallbackHidl>::make(); + } + return callbackHidl; + } + +private: + sp<GnssNavigationMessageCallbackAidl> callbackAidl; + sp<GnssNavigationMessageCallbackHidl> callbackHidl; +}; + +struct GnssNavigationMessageCallbackUtil { + template <class T> + static void gnssNavigationMessageCbImpl(const T& message); + +private: + GnssNavigationMessageCallbackUtil() = delete; +}; + +template <class T> +void GnssNavigationMessageCallbackUtil::gnssNavigationMessageCbImpl(const T& message) { + JNIEnv* env = getJniEnv(); + + size_t dataLength = message.data.size(); + + std::vector<uint8_t> navigationData = message.data; + uint8_t* data = &(navigationData[0]); + if (dataLength == 0 || data == nullptr) { + ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength); + return; + } + + JavaObject object(env, class_gnssNavigationMessage, method_gnssNavigationMessageCtor); + SET(Type, static_cast<int32_t>(message.type)); + SET(Svid, static_cast<int32_t>(message.svid)); + SET(MessageId, static_cast<int32_t>(message.messageId)); + SET(SubmessageId, static_cast<int32_t>(message.submessageId)); + object.callSetter("setData", data, dataLength); + SET(Status, static_cast<int32_t>(message.status)); + + jobject navigationMessage = object.get(); + env->CallVoidMethod(mCallbacksObj, method_reportNavigationMessages, navigationMessage); + checkAndClearExceptionFromCallback(env, __FUNCTION__); + env->DeleteLocalRef(navigationMessage); + return; +} + +} // namespace android::gnss + +#endif // _ANDROID_SERVER_GNSS_GNSSNAVIGATIONMESSAGECALLBACK_H
\ No newline at end of file diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 429edf175be4..2f4dd57ab15b 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -26,6 +26,10 @@ <xs:element name="displayConfiguration"> <xs:complexType> <xs:sequence> + <xs:element type="densityMap" name="densityMap" minOccurs="0" maxOccurs="1"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> <xs:element type="nitsMap" name="screenBrightnessMap"> <xs:annotation name="nonnull"/> <xs:annotation name="final"/> @@ -181,5 +185,27 @@ </xs:sequence> </xs:complexType> + <xs:complexType name="densityMap"> + <xs:sequence> + <xs:element name="density" type="density" maxOccurs="unbounded" minOccurs="1"> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="density"> + <xs:sequence> + <xs:element type="xs:nonNegativeInteger" name="width"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="height"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="density"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> </xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index ad186026d30c..5b2b87c3f14e 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -1,8 +1,24 @@ // Signature format: 2.0 package com.android.server.display.config { + public class Density { + ctor public Density(); + method @NonNull public final java.math.BigInteger getDensity(); + method @NonNull public final java.math.BigInteger getHeight(); + method @NonNull public final java.math.BigInteger getWidth(); + method public final void setDensity(@NonNull java.math.BigInteger); + method public final void setHeight(@NonNull java.math.BigInteger); + method public final void setWidth(@NonNull java.math.BigInteger); + } + + public class DensityMap { + ctor public DensityMap(); + method public java.util.List<com.android.server.display.config.Density> getDensity(); + } + public class DisplayConfiguration { ctor public DisplayConfiguration(); + method @Nullable public final com.android.server.display.config.DensityMap getDensityMap(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); method public final com.android.server.display.config.SensorDetails getProxSensor(); @@ -13,6 +29,7 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); method public final void setProxSensor(com.android.server.display.config.SensorDetails); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 3839a9f39158..f605fe862fe2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10231,7 +10231,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!mHasFeature) { return null; } - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY)); synchronized (getLockObject()) { List<String> result = null; @@ -10399,14 +10400,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public List<String> getPermittedInputMethodsForCurrentUser() { + public @Nullable List<String> getPermittedInputMethodsAsUser(@UserIdInt int userId) { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(canManageUsers(caller)); + Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId)); + Preconditions.checkCallAuthorization(canManageUsers(caller) + || hasCallingOrSelfPermission(permission.QUERY_ADMIN_POLICY)); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return getPermittedInputMethodsUnchecked(userId); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + private @Nullable List<String> getPermittedInputMethodsUnchecked(@UserIdInt int userId) { synchronized (getLockObject()) { List<String> result = null; // Only device or profile owners can have permitted lists set. - List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(caller.getUserId()); + List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(userId); for (ActiveAdmin admin: admins) { List<String> fromAdmin = admin.permittedInputMethods; if (fromAdmin != null) { @@ -10421,7 +10432,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // If we have a permitted list add all system input methods. if (result != null) { List<InputMethodInfo> imes = InputMethodManagerInternal - .get().getInputMethodListAsUser(caller.getUserId()); + .get().getInputMethodListAsUser(userId); if (imes != null) { for (InputMethodInfo ime : imes) { ServiceInfo serviceInfo = ime.getServiceInfo(); @@ -10587,7 +10598,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } } - if (!mUserManager.canAddMoreUsers()) { + + String userType = demo ? UserManager.USER_TYPE_FULL_DEMO + : UserManager.USER_TYPE_FULL_SECONDARY; + int userInfoFlags = ephemeral ? UserInfo.FLAG_EPHEMERAL : 0; + + if (!mUserManager.canAddMoreUsers(userType)) { if (targetSdkVersion >= Build.VERSION_CODES.P) { throw new ServiceSpecificException( UserManager.USER_OPERATION_ERROR_MAX_USERS, "user limit reached"); @@ -10596,9 +10612,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - int userInfoFlags = ephemeral ? UserInfo.FLAG_EPHEMERAL : 0; - String userType = demo ? UserManager.USER_TYPE_FULL_DEMO - : UserManager.USER_TYPE_FULL_SECONDARY; String[] disallowedPackages = null; if (!leaveAllSystemAppsEnabled) { disallowedPackages = mOverlayPackagesProvider.getNonRequiredApps(admin, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 2f9e3344b29c..a72cf3a79d37 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -176,6 +176,7 @@ import com.android.server.recoverysystem.RecoverySystemService; import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.role.RoleServicePlatformHelper; import com.android.server.rotationresolver.RotationResolverManagerService; +import com.android.server.security.AttestationVerificationManagerService; import com.android.server.security.FileIntegrityService; import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; @@ -398,6 +399,8 @@ public final class SystemServer implements Dumpable { private static final String UWB_APEX_SERVICE_JAR_PATH = "/apex/com.android.uwb/javalib/service-uwb.jar"; private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService"; + private static final String SAFETY_CENTER_SERVICE_CLASS = + "com.android.safetycenter.SafetyCenterService"; private static final String SUPPLEMENTALPROCESS_APEX_PATH = "/apex/com.android.supplementalprocess/javalib/service-supplementalprocess.jar"; @@ -2337,6 +2340,10 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + t.traceBegin("StartAttestationVerificationService"); + mSystemServiceManager.startService(AttestationVerificationManagerService.class); + t.traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) { t.traceBegin("StartCompanionDeviceManager"); mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS); @@ -2716,6 +2723,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY); t.traceEnd(); + t.traceBegin("StartSafetyCenterService"); + mSystemServiceManager.startService(SAFETY_CENTER_SERVICE_CLASS); + t.traceEnd(); + t.traceBegin("AppSearchManagerService"); mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS); t.traceEnd(); diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java index 42115d437ee0..b7f8c00896d4 100644 --- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java @@ -55,8 +55,8 @@ import android.platform.test.annotations.Presubmit; import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.OnTransportRegisteredListener; -import com.android.server.backup.transport.TransportClient; -import com.android.server.backup.transport.TransportClientManager; +import com.android.server.backup.transport.TransportConnection; +import com.android.server.backup.transport.TransportConnectionManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.shadows.ShadowApplicationPackageManager; @@ -85,7 +85,7 @@ public class TransportManagerTest { private static final String PACKAGE_B = "some.package.b"; @Mock private OnTransportRegisteredListener mListener; - @Mock private TransportClientManager mTransportClientManager; + @Mock private TransportConnectionManager mTransportConnectionManager; private TransportData mTransportA1; private TransportData mTransportA2; private TransportData mTransportB1; @@ -206,7 +206,7 @@ public class TransportManagerTest { transportManager.registerTransports(); - verify(mTransportClientManager) + verify(mTransportConnectionManager) .getTransportClient( eq(mTransportA1.getTransportComponent()), argThat( @@ -433,10 +433,10 @@ public class TransportManagerTest { TransportManager transportManager = createTransportManagerWithRegisteredTransports(mTransportA1, mTransportA2); - TransportClient transportClient = + TransportConnection transportConnection = transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient.getTransportComponent()) + assertThat(transportConnection.getTransportComponent()) .isEqualTo(mTransportA1.getTransportComponent()); } @@ -453,10 +453,10 @@ public class TransportManagerTest { null, null); - TransportClient transportClient = + TransportConnection transportConnection = transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient).isNull(); + assertThat(transportConnection).isNull(); } @Test @@ -471,9 +471,10 @@ public class TransportManagerTest { null, null); - TransportClient transportClient = transportManager.getTransportClient("newName", "caller"); + TransportConnection transportConnection = transportManager.getTransportClient( + "newName", "caller"); - assertThat(transportClient.getTransportComponent()) + assertThat(transportConnection.getTransportComponent()) .isEqualTo(mTransportA1.getTransportComponent()); } @@ -482,9 +483,10 @@ public class TransportManagerTest { TransportManager transportManager = createTransportManagerWithRegisteredTransports(mTransportA1, mTransportA2); - TransportClient transportClient = transportManager.getCurrentTransportClient("caller"); + TransportConnection transportConnection = transportManager.getCurrentTransportClient( + "caller"); - assertThat(transportClient.getTransportComponent()) + assertThat(transportConnection.getTransportComponent()) .isEqualTo(mTransportA1.getTransportComponent()); } @@ -660,12 +662,12 @@ public class TransportManagerTest { List<TransportMock> transportMocks = new ArrayList<>(transports.length); for (TransportData transport : transports) { TransportMock transportMock = mockTransport(transport); - when(mTransportClientManager.getTransportClient( + when(mTransportConnectionManager.getTransportClient( eq(transport.getTransportComponent()), any())) - .thenReturn(transportMock.transportClient); - when(mTransportClientManager.getTransportClient( + .thenReturn(transportMock.mTransportConnection); + when(mTransportConnectionManager.getTransportClient( eq(transport.getTransportComponent()), any(), any())) - .thenReturn(transportMock.transportClient); + .thenReturn(transportMock.mTransportConnection); transportMocks.add(transportMock); } return transportMocks; @@ -706,7 +708,7 @@ public class TransportManagerTest { .map(TransportData::getTransportComponent) .collect(toSet()), selectedTransport != null ? selectedTransport.transportName : null, - mTransportClientManager); + mTransportConnectionManager); transportManager.setOnTransportRegisteredListener(mListener); return transportManager; } diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 06d51a4be920..297538ad7b4b 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -243,7 +243,7 @@ public class UserBackupManagerServiceTest { assertThat(result).isTrue(); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); } /** @@ -282,7 +282,7 @@ public class UserBackupManagerServiceTest { assertThat(filtered).asList().containsExactly(PACKAGE_1); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); } /** @@ -357,7 +357,7 @@ public class UserBackupManagerServiceTest { assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); assertThat(oldTransport).isEqualTo(mOldTransport.transportName); verify(mTransportManager) - .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any()); + .disposeOfTransportClient(eq(mNewTransportMock.mTransportConnection), any()); } /** @@ -395,7 +395,7 @@ public class UserBackupManagerServiceTest { assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); verify(callback).onSuccess(eq(mNewTransport.transportName)); verify(mTransportManager) - .disposeOfTransportClient(eq(mNewTransportMock.transportClient), any()); + .disposeOfTransportClient(eq(mNewTransportMock.mTransportConnection), any()); } /** diff --git a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index a14cc51a3ab6..bf4eeae4b4b2 100644 --- a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -50,7 +50,7 @@ import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils; import com.android.server.backup.testing.TransportTestUtils.TransportMock; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.testing.shadows.ShadowSlog; import org.junit.Before; @@ -285,7 +285,7 @@ public class PerformInitializeTaskTest { TransportData transport = transportsIterator.next(); verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); } } @@ -303,9 +303,9 @@ public class PerformInitializeTaskTest { performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any()); + .disposeOfTransportClient(eq(transportMocks.get(0).mTransportConnection), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any()); + .disposeOfTransportClient(eq(transportMocks.get(1).mTransportConnection), any()); } @Test @@ -328,14 +328,16 @@ public class PerformInitializeTaskTest { setUpTransports(mTransportManager, transport1, transport2); String registeredTransportName = transport2.transportName; IBackupTransport registeredTransport = transportMocks.get(1).transport; - TransportClient registeredTransportClient = transportMocks.get(1).transportClient; + TransportConnection + registeredTransportConnection = transportMocks.get(1).mTransportConnection; PerformInitializeTask performInitializeTask = createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); verify(registeredTransport).initializeDevice(); - verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportClient), any()); + verify(mTransportManager).disposeOfTransportClient(eq(registeredTransportConnection), + any()); verify(mObserver).onResult(eq(registeredTransportName), eq(TRANSPORT_OK)); } @@ -347,7 +349,7 @@ public class PerformInitializeTaskTest { performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @@ -356,13 +358,13 @@ public class PerformInitializeTaskTest { public void testRun_whenTransportThrowsDeadObjectException() throws Exception { TransportMock transportMock = setUpTransport(mTransport); IBackupTransport transport = transportMock.transport; - TransportClient transportClient = transportMock.transportClient; + TransportConnection transportConnection = transportMock.mTransportConnection; when(transport.initializeDevice()).thenThrow(DeadObjectException.class); PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mTransportManager).disposeOfTransportClient(eq(transportConnection), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 7d17109af7d3..fd295c0739cf 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -2665,7 +2665,7 @@ public class KeyValueBackupTaskTest { KeyValueBackupTask task = new KeyValueBackupTask( mBackupManagerService, - transportMock.transportClient, + transportMock.mTransportConnection, transportMock.transportData.transportDirName, queue, mOldJournal, diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 5883c1cb5995..9eb99aed2ba8 100644 --- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -202,7 +202,7 @@ public class ActiveRestoreSessionTest { verify(mObserver) .restoreSetsAvailable(aryEq(new RestoreSet[] {mRestoreSet1, mRestoreSet2})); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); assertThat(mWakeLock.isHeld()).isFalse(); } @@ -235,7 +235,7 @@ public class ActiveRestoreSessionTest { verify(mObserver).restoreSetsAvailable(isNull()); assertEventLogged(EventLogTags.RESTORE_TRANSPORT_FAILURE); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); assertThat(mWakeLock.isHeld()).isFalse(); } @@ -253,7 +253,7 @@ public class ActiveRestoreSessionTest { mShadowBackupLooper.runToEndOfTasks(); assertThat(result).isEqualTo(0); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); assertThat(mWakeLock.isHeld()).isFalse(); assertThat(mBackupManagerService.isRestoreInProgress()).isFalse(); // Verify it created the task properly @@ -341,7 +341,7 @@ public class ActiveRestoreSessionTest { mShadowBackupLooper.runToEndOfTasks(); assertThat(result).isEqualTo(0); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); assertThat(mWakeLock.isHeld()).isFalse(); assertThat(mBackupManagerService.isRestoreInProgress()).isFalse(); ShadowPerformUnifiedRestoreTask shadowTask = @@ -463,7 +463,7 @@ public class ActiveRestoreSessionTest { mShadowBackupLooper.runToEndOfTasks(); assertThat(result).isEqualTo(0); verify(mTransportManager) - .disposeOfTransportClient(eq(transportMock.transportClient), any()); + .disposeOfTransportClient(eq(transportMock.mTransportConnection), any()); assertThat(mWakeLock.isHeld()).isFalse(); assertThat(mBackupManagerService.isRestoreInProgress()).isFalse(); ShadowPerformUnifiedRestoreTask shadowTask = diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java index 7dd5be53157b..ce44f067aeaa 100644 --- a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java @@ -36,7 +36,7 @@ import android.os.RemoteException; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; @@ -91,9 +91,9 @@ public class TransportTestUtils { || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getCurrentTransportClient(any())) - .thenReturn(transportMock.transportClient); + .thenReturn(transportMock.mTransportConnection); when(transportManager.getCurrentTransportClientOrThrow(any())) - .thenReturn(transportMock.transportClient); + .thenReturn(transportMock.mTransportConnection); } else { // Transport not registered when(transportManager.getCurrentTransportClient(any())).thenReturn(null); @@ -123,9 +123,9 @@ public class TransportTestUtils { || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getTransportClient(eq(transportName), any())) - .thenReturn(transportMock.transportClient); + .thenReturn(transportMock.mTransportConnection); when(transportManager.getTransportClientOrThrow(eq(transportName), any())) - .thenReturn(transportMock.transportClient); + .thenReturn(transportMock.mTransportConnection); when(transportManager.getTransportName(transportComponent)).thenReturn(transportName); when(transportManager.getTransportDirName(eq(transportName))) .thenReturn(transportDirName); @@ -150,28 +150,28 @@ public class TransportTestUtils { } public static TransportMock mockTransport(TransportData transport) throws Exception { - final TransportClient transportClientMock; + final TransportConnection transportConnectionMock; int status = transport.transportStatus; ComponentName transportComponent = transport.getTransportComponent(); if (status == TransportStatus.REGISTERED_AVAILABLE || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered - transportClientMock = mock(TransportClient.class); - when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); + transportConnectionMock = mock(TransportConnection.class); + when(transportConnectionMock.getTransportComponent()).thenReturn(transportComponent); if (status == TransportStatus.REGISTERED_AVAILABLE) { // Transport registered and available IBackupTransport transportMock = mockTransportBinder(transport); - when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - when(transportClientMock.connect(any())).thenReturn(transportMock); + when(transportConnectionMock.connectOrThrow(any())).thenReturn(transportMock); + when(transportConnectionMock.connect(any())).thenReturn(transportMock); - return new TransportMock(transport, transportClientMock, transportMock); + return new TransportMock(transport, transportConnectionMock, transportMock); } else { // Transport registered but unavailable - when(transportClientMock.connectOrThrow(any())) + when(transportConnectionMock.connectOrThrow(any())) .thenThrow(TransportNotAvailableException.class); - when(transportClientMock.connect(any())).thenReturn(null); + when(transportConnectionMock.connect(any())).thenReturn(null); - return new TransportMock(transport, transportClientMock, null); + return new TransportMock(transport, transportConnectionMock, null); } } else { // Transport not registered @@ -198,15 +198,15 @@ public class TransportTestUtils { public static class TransportMock { public final TransportData transportData; - @Nullable public final TransportClient transportClient; + @Nullable public final TransportConnection mTransportConnection; @Nullable public final IBackupTransport transport; private TransportMock( TransportData transportData, - @Nullable TransportClient transportClient, + @Nullable TransportConnection transportConnection, @Nullable IBackupTransport transport) { this.transportData = transportData; - this.transportClient = transportClient; + this.mTransportConnection = transportConnection; this.transport = transport; } } diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionManagerTest.java index f033af8a9b63..102f594003fa 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionManagerTest.java @@ -44,14 +44,14 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) @Presubmit -public class TransportClientManagerTest { +public class TransportConnectionManagerTest { private static final String PACKAGE_NAME = "random.package.name"; private static final String CLASS_NAME = "random.package.name.transport.Transport"; @Mock private Context mContext; @Mock private TransportConnectionListener mTransportConnectionListener; private @UserIdInt int mUserId; - private TransportClientManager mTransportClientManager; + private TransportConnectionManager mTransportConnectionManager; private ComponentName mTransportComponent; private Intent mBindIntent; @@ -60,8 +60,8 @@ public class TransportClientManagerTest { MockitoAnnotations.initMocks(this); mUserId = UserHandle.USER_SYSTEM; - mTransportClientManager = - new TransportClientManager(mUserId, mContext, new TransportStats()); + mTransportConnectionManager = + new TransportConnectionManager(mUserId, mContext, new TransportStats()); mTransportComponent = new ComponentName(PACKAGE_NAME, CLASS_NAME); mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); @@ -75,11 +75,11 @@ public class TransportClientManagerTest { @Test public void testGetTransportClient() { - TransportClient transportClient = - mTransportClientManager.getTransportClient(mTransportComponent, "caller"); + TransportConnection transportConnection = + mTransportConnectionManager.getTransportClient(mTransportComponent, "caller"); // Connect to be able to extract the intent - transportClient.connectAsync(mTransportConnectionListener, "caller"); + transportConnection.connectAsync(mTransportConnectionListener, "caller"); verify(mContext) .bindServiceAsUser( argThat(matchesIntentAndExtras(mBindIntent)), @@ -93,10 +93,11 @@ public class TransportClientManagerTest { Bundle extras = new Bundle(); extras.putBoolean("random_extra", true); - TransportClient transportClient = - mTransportClientManager.getTransportClient(mTransportComponent, extras, "caller"); + TransportConnection transportConnection = + mTransportConnectionManager.getTransportClient(mTransportComponent, extras, + "caller"); - transportClient.connectAsync(mTransportConnectionListener, "caller"); + transportConnection.connectAsync(mTransportConnectionListener, "caller"); mBindIntent.putExtras(extras); verify(mContext) .bindServiceAsUser( @@ -108,13 +109,13 @@ public class TransportClientManagerTest { @Test public void testDisposeOfTransportClient() { - TransportClient transportClient = - spy(mTransportClientManager.getTransportClient(mTransportComponent, "caller")); + TransportConnection transportConnection = + spy(mTransportConnectionManager.getTransportClient(mTransportComponent, "caller")); - mTransportClientManager.disposeOfTransportClient(transportClient, "caller"); + mTransportConnectionManager.disposeOfTransportClient(transportConnection, "caller"); - verify(transportClient).unbind(any()); - verify(transportClient).markAsDisposed(); + verify(transportConnection).unbind(any()); + verify(transportConnection).markAsDisposed(); } private ArgumentMatcher<Intent> matchesIntentAndExtras(Intent expectedIntent) { diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java index 392f2ca9490e..de4aec61aef2 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java @@ -77,7 +77,7 @@ import java.util.function.Supplier; FrameworkShadowLooper.class }) @Presubmit -public class TransportClientTest { +public class TransportConnectionTest { private static final String PACKAGE_NAME = "some.package.name"; @Mock private Context mContext; @@ -86,7 +86,7 @@ public class TransportClientTest { @Mock private IBackupTransport.Stub mTransportBinder; @UserIdInt private int mUserId; private TransportStats mTransportStats; - private TransportClient mTransportClient; + private TransportConnection mTransportConnection; private ComponentName mTransportComponent; private String mTransportString; private Intent mBindIntent; @@ -106,8 +106,8 @@ public class TransportClientTest { mTransportString = mTransportComponent.flattenToShortString(); mTransportStats = new TransportStats(); mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent); - mTransportClient = - new TransportClient( + mTransportConnection = + new TransportConnection( mUserId, mContext, mTransportStats, @@ -132,12 +132,12 @@ public class TransportClientTest { @Test public void testGetTransportComponent_returnsTransportComponent() { - assertThat(mTransportClient.getTransportComponent()).isEqualTo(mTransportComponent); + assertThat(mTransportConnection.getTransportComponent()).isEqualTo(mTransportComponent); } @Test public void testConnectAsync_callsBindService() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller"); verify(mContext) .bindServiceAsUser( @@ -149,42 +149,42 @@ public class TransportClientTest { @Test public void testConnectAsync_callsListenerWhenConnected() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); } @Test public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); - mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + mTransportConnection.connectAsync(mTransportConnectionListener2, "caller2"); connection.onServiceConnected(mTransportComponent, mTransportBinder); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); verify(mTransportConnectionListener2) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); } @Test public void testConnectAsync_whenAlreadyConnected_callsListener() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); - mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + mTransportConnection.connectAsync(mTransportConnectionListener2, "caller2"); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener2) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient)); + .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); } @Test @@ -196,11 +196,11 @@ public class TransportClientTest { any(UserHandle.class))) .thenReturn(false); - mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller"); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); } @Test @@ -212,7 +212,7 @@ public class TransportClientTest { any(UserHandle.class))) .thenReturn(false); - mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); verify(mContext).unbindService(eq(connection)); @@ -220,64 +220,64 @@ public class TransportClientTest { @Test public void testConnectAsync_afterOnServiceDisconnectedBeforeNewConnection_callsListener() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); connection.onServiceDisconnected(mTransportComponent); - mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener2, "caller1"); verify(mTransportConnectionListener2) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); } @Test public void testConnectAsync_afterOnServiceDisconnectedAfterNewConnection_callsListener() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); connection.onServiceDisconnected(mTransportComponent); connection.onServiceConnected(mTransportComponent, mTransportBinder); - mTransportClient.connectAsync(mTransportConnectionListener2, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener2, "caller1"); // Yes, it should return null because the object became unusable, check design doc verify(mTransportConnectionListener2) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); } @Test public void testConnectAsync_callsListenerIfBindingDies() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onBindingDied(mTransportComponent); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); } @Test public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); - mTransportClient.connectAsync(mTransportConnectionListener2, "caller2"); + mTransportConnection.connectAsync(mTransportConnectionListener2, "caller2"); connection.onBindingDied(mTransportComponent); mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); verify(mTransportConnectionListener2) - .onTransportConnectionResult(isNull(), eq(mTransportClient)); + .onTransportConnectionResult(isNull(), eq(mTransportConnection)); } @Test public void testConnectAsync_beforeFrameworkCall_logsBoundTransitionEvent() { ShadowEventLog.setUp(); - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1); } @@ -285,7 +285,7 @@ public class TransportClientTest { @Test public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitionEvents() { ShadowEventLog.setUp(); - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); @@ -297,7 +297,7 @@ public class TransportClientTest { @Test public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitionEvents() { ShadowEventLog.setUp(); - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onBindingDied(mTransportComponent); @@ -308,37 +308,37 @@ public class TransportClientTest { @Test public void testConnect_whenConnected_returnsTransport() throws Exception { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); IBackupTransport transportBinder = - runInWorkerThread(() -> mTransportClient.connect("caller2")); + runInWorkerThread(() -> mTransportConnection.connect("caller2")); assertThat(transportBinder).isNotNull(); } @Test public void testConnect_afterOnServiceDisconnected_returnsNull() throws Exception { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); connection.onServiceDisconnected(mTransportComponent); IBackupTransport transportBinder = - runInWorkerThread(() -> mTransportClient.connect("caller2")); + runInWorkerThread(() -> mTransportConnection.connect("caller2")); assertThat(transportBinder).isNull(); } @Test public void testConnect_afterOnBindingDied_returnsNull() throws Exception { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onBindingDied(mTransportComponent); IBackupTransport transportBinder = - runInWorkerThread(() -> mTransportClient.connect("caller2")); + runInWorkerThread(() -> mTransportConnection.connect("caller2")); assertThat(transportBinder).isNull(); } @@ -350,30 +350,31 @@ public class TransportClientTest { // reentrant lock can't be acquired by the listener at the call-site of bindServiceAsUser(), // which is what would happened if we mocked bindServiceAsUser() to call the listener // inline. - TransportClient transportClient = spy(mTransportClient); + TransportConnection transportConnection = spy(mTransportConnection); doAnswer( invocation -> { TransportConnectionListener listener = invocation.getArgument(0); - listener.onTransportConnectionResult(mTransportBinder, transportClient); + listener.onTransportConnectionResult(mTransportBinder, + transportConnection); return null; }) - .when(transportClient) + .when(transportConnection) .connectAsync(any(), any()); IBackupTransport transportBinder = - runInWorkerThread(() -> transportClient.connect("caller")); + runInWorkerThread(() -> transportConnection.connect("caller")); assertThat(transportBinder).isNotNull(); } @Test public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitionEvents() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); ShadowEventLog.setUp(); - mTransportClient.unbind("caller1"); + mTransportConnection.unbind("caller1"); assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 0); assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0); @@ -382,7 +383,7 @@ public class TransportClientTest { @Test public void testOnServiceDisconnected_whenConnected_logsDisconnectedAndUnboundTransitionEvents() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); ShadowEventLog.setUp(); @@ -395,7 +396,7 @@ public class TransportClientTest { @Test public void testOnBindingDied_whenConnected_logsDisconnectedAndUnboundTransitionEvents() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); ShadowEventLog.setUp(); @@ -408,60 +409,60 @@ public class TransportClientTest { @Test public void testMarkAsDisposed_whenCreated() { - mTransportClient.markAsDisposed(); + mTransportConnection.markAsDisposed(); // No exception thrown } @Test public void testMarkAsDisposed_afterOnBindingDied() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onBindingDied(mTransportComponent); - mTransportClient.markAsDisposed(); + mTransportConnection.markAsDisposed(); // No exception thrown } @Test public void testMarkAsDisposed_whenConnectedAndUnbound() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); - mTransportClient.unbind("caller1"); + mTransportConnection.unbind("caller1"); - mTransportClient.markAsDisposed(); + mTransportConnection.markAsDisposed(); // No exception thrown } @Test public void testMarkAsDisposed_afterOnServiceDisconnected() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); connection.onServiceDisconnected(mTransportComponent); - mTransportClient.markAsDisposed(); + mTransportConnection.markAsDisposed(); // No exception thrown } @Test public void testMarkAsDisposed_whenBound() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); - expectThrows(RuntimeException.class, mTransportClient::markAsDisposed); + expectThrows(RuntimeException.class, mTransportConnection::markAsDisposed); } @Test public void testMarkAsDisposed_whenConnected() { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); - expectThrows(RuntimeException.class, mTransportClient::markAsDisposed); + expectThrows(RuntimeException.class, mTransportConnection::markAsDisposed); } @Test @@ -469,36 +470,36 @@ public class TransportClientTest { public void testFinalize_afterCreated() throws Throwable { ShadowLog.reset(); - mTransportClient.finalize(); + mTransportConnection.finalize(); - assertLogcatAtMost(TransportClient.TAG, Log.INFO); + assertLogcatAtMost(TransportConnection.TAG, Log.INFO); } @Test @SuppressWarnings("FinalizeCalledExplicitly") public void testFinalize_whenBound() throws Throwable { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ShadowLog.reset(); - mTransportClient.finalize(); + mTransportConnection.finalize(); - assertLogcatAtLeast(TransportClient.TAG, Log.ERROR); + assertLogcatAtLeast(TransportConnection.TAG, Log.ERROR); } @Test @SuppressWarnings("FinalizeCalledExplicitly") public void testFinalize_whenConnected() throws Throwable { - mTransportClient.connectAsync(mTransportConnectionListener, "caller1"); + mTransportConnection.connectAsync(mTransportConnectionListener, "caller1"); ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext); connection.onServiceConnected(mTransportComponent, mTransportBinder); ShadowLog.reset(); - mTransportClient.finalize(); + mTransportConnection.finalize(); expectThrows( TransportNotAvailableException.class, - () -> mTransportClient.getConnectedTransport("caller1")); - assertLogcatAtLeast(TransportClient.TAG, Log.ERROR); + () -> mTransportConnection.getConnectedTransport("caller1")); + assertLogcatAtLeast(TransportConnection.TAG, Log.ERROR); } @Test @@ -506,7 +507,7 @@ public class TransportClientTest { public void testFinalize_whenNotMarkedAsDisposed() throws Throwable { ShadowCloseGuard.setUp(); - mTransportClient.finalize(); + mTransportConnection.finalize(); assertThat(ShadowCloseGuard.hasReported()).isTrue(); } @@ -514,10 +515,10 @@ public class TransportClientTest { @Test @SuppressWarnings("FinalizeCalledExplicitly") public void testFinalize_whenMarkedAsDisposed() throws Throwable { - mTransportClient.markAsDisposed(); + mTransportConnection.markAsDisposed(); ShadowCloseGuard.setUp(); - mTransportClient.finalize(); + mTransportConnection.finalize(); assertThat(ShadowCloseGuard.hasReported()).isFalse(); } diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index adf892aa12ab..5dc048b9d7c6 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -150,7 +151,7 @@ public class CrossProfileAppsServiceImplRoboTest { PackageInfo packageInfo, @UserIdInt int userId, int uid) { when(mPackageManagerInternal.getPackageInfo( eq(CROSS_PROFILE_APP_PACKAGE_NAME), - /* flags= */ anyInt(), + /* flags= */ anyLong(), /* filterCallingUid= */ anyInt(), eq(userId))) .thenReturn(packageInfo); @@ -469,7 +470,7 @@ public class CrossProfileAppsServiceImplRoboTest { private void mockUninstallCrossProfileAppFromWorkProfile() { when(mPackageManagerInternal.getPackageInfo( eq(CROSS_PROFILE_APP_PACKAGE_NAME), - /* flags= */ anyInt(), + /* flags= */ anyLong(), /* filterCallingUid= */ anyInt(), eq(WORK_PROFILE_USER_ID))) .thenReturn(null); diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupEligibilityRules.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupEligibilityRules.java index 566b0e151402..9a74977ef6d4 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupEligibilityRules.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupEligibilityRules.java @@ -19,9 +19,8 @@ package com.android.server.testing.shadows; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import org.robolectric.annotation.Implementation; @@ -54,7 +53,7 @@ public class ShadowBackupEligibilityRules { @Implementation protected boolean appIsRunningAndEligibleForBackupWithTransport( - @Nullable TransportClient transportClient, + @Nullable TransportConnection transportConnection, String packageName) { return sAppsRunningAndEligibleForBackupWithTransport.contains(packageName); } diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java index fd51df7ab1f9..06b7fb7e6ae3 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java @@ -23,7 +23,7 @@ import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.keyvalue.KeyValueBackupReporter; import com.android.server.backup.keyvalue.KeyValueBackupTask; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import org.robolectric.annotation.Implementation; @@ -56,7 +56,7 @@ public class ShadowKeyValueBackupTask { @Implementation protected void __constructor__( UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, String transportDirName, List<String> queue, @Nullable DataChangedJournal dataChangedJournal, diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java index 5161070398d7..71010a9fe935 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java @@ -24,15 +24,12 @@ import android.content.pm.PackageInfo; import com.android.server.backup.UserBackupManagerService; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.restore.PerformUnifiedRestoreTask; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import java.util.Map; -import java.util.Set; - @Implements(PerformUnifiedRestoreTask.class) public class ShadowPerformUnifiedRestoreTask { @Nullable private static ShadowPerformUnifiedRestoreTask sLastShadow; @@ -60,7 +57,7 @@ public class ShadowPerformUnifiedRestoreTask { @Implementation protected void __constructor__( UserBackupManagerService backupManagerService, - TransportClient transportClient, + TransportConnection transportConnection, IRestoreObserver observer, IBackupManagerMonitor monitor, long restoreSetToken, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt index 98634b28ea9d..ac6b6790e519 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt @@ -120,7 +120,7 @@ class DomainVerificationValidIntentTest { @Test fun verify() { val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0 - assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags)) - .isEqualTo(params.expected) + assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, + flags.toLong())).isEqualTo(params.expected) } } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index a9099ae35b75..58854706f837 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2961,7 +2961,7 @@ public class AlarmManagerServiceTest { private void registerAppIds(String[] packages, Integer[] ids) { assertEquals(packages.length, ids.length); - when(mPackageManagerInternal.getPackageUid(anyString(), anyInt(), anyInt())).thenAnswer( + when(mPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())).thenAnswer( invocation -> { final String pkg = invocation.getArgument(0); final int index = ArrayUtils.indexOf(packages, pkg); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index b2847ce88a2c..783971a1afe4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -22,6 +22,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -80,7 +81,7 @@ public class PendingIntentControllerTest { doReturn(mActivityManagerInternal).when( () -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mIPackageManager).when(() -> AppGlobals.getPackageManager()); - when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt(), anyInt())).thenReturn( + when(mIPackageManager.getPackageUid(eq(TEST_PACKAGE_NAME), anyLong(), anyInt())).thenReturn( TEST_CALLING_UID); ActivityManagerConstants constants = mock(ActivityManagerConstants.class); constants.PENDINGINTENT_WARNING_THRESHOLD = 2000; diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java new file mode 100644 index 000000000000..3f69f1b723e3 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/DensityMapTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static com.android.server.display.DensityMap.Entry; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DensityMapTest { + + @Test + public void testConstructor_withBadConfig_throwsException() { + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(1080, 1920, 320)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(1920, 1080, 120)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 120)}) + ); + + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(3840, 2160, 120)}) + ); + + // Two entries with the same diagonal + assertThrows(IllegalStateException.class, () -> + DensityMap.createByOwning(new Entry[]{ + new Entry(500, 500, 123), + new Entry(100, 700, 456)}) + ); + } + + @Test + public void testGetDensityForResolution_withResolutionMatch_returnsDensityFromConfig() { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(720, 1280, 213), + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 640)}); + + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + + assertEquals(320, densityMap.getDensityForResolution(1080, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1920, 1080)); + + assertEquals(640, densityMap.getDensityForResolution(2160, 3840)); + assertEquals(640, densityMap.getDensityForResolution(3840, 2160)); + } + + @Test + public void testGetDensityForResolution_withDiagonalMatch_returnsDensityFromConfig() { + DensityMap densityMap = DensityMap.createByOwning( + new Entry[]{ new Entry(500, 500, 123)}); + + // 500x500 has the same diagonal as 100x700 + assertEquals(123, densityMap.getDensityForResolution(100, 700)); + } + + @Test + public void testGetDensityForResolution_withOneEntry_withNoMatch_returnsExtrapolatedDensity() { + DensityMap densityMap = DensityMap.createByOwning( + new Entry[]{ new Entry(1080, 1920, 320)}); + + assertEquals(320, densityMap.getDensityForResolution(1081, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1080, 1921)); + + assertEquals(640, densityMap.getDensityForResolution(2160, 3840)); + assertEquals(640, densityMap.getDensityForResolution(3840, 2160)); + + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + } + + @Test + public void testGetDensityForResolution_withTwoEntries_withNoMatch_returnExtrapolatedDensity() { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 320)}); + + // Resolution is smaller than all entries + assertEquals(213, densityMap.getDensityForResolution(720, 1280)); + assertEquals(213, densityMap.getDensityForResolution(1280, 720)); + + // Resolution is bigger than all entries + assertEquals(320 * 2, densityMap.getDensityForResolution(2160 * 2, 3840 * 2)); + assertEquals(320 * 2, densityMap.getDensityForResolution(3840 * 2, 2160 * 2)); + } + + @Test + public void testGetDensityForResolution_withNoMatch_returnsInterpolatedDensity() { + { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(1080, 1920, 320), + new Entry(2160, 3840, 320)}); + + assertEquals(320, densityMap.getDensityForResolution(2000, 2000)); + } + + { + DensityMap densityMap = DensityMap.createByOwning(new Entry[]{ + new Entry(720, 1280, 213), + new Entry(2160, 3840, 640)}); + + assertEquals(320, densityMap.getDensityForResolution(1080, 1920)); + assertEquals(320, densityMap.getDensityForResolution(1920, 1080)); + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index b17ff53b166c..95912b26e830 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -43,6 +43,7 @@ import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; +import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; @@ -168,12 +169,15 @@ public class PrefetchControllerTest { } } - private JobStatus createJobStatus(String testTag, int jobId) { - JobInfo jobInfo = new JobInfo.Builder(jobId, + private JobInfo createJobInfo(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestPrefetchJobService")) .setPrefetch(true) .build(); - return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); + } + + private JobStatus createJobStatus(String testTag, int jobId) { + return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, createJobInfo(jobId)); } private static JobStatus createJobStatus(String testTag, String packageName, int callingUid, @@ -331,6 +335,32 @@ public class PrefetchControllerTest { } @Test + public void testConstraintSatisfiedWhenWidget() { + final JobStatus jobNonWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 1); + final JobStatus jobWidget = createJobStatus("testConstraintSatisfiedWhenWidget", 2); + + when(mUsageStatsManagerInternal + .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(sSystemClock.millis() + 100 * HOUR_IN_MILLIS); + + final AppWidgetManager appWidgetManager = mock(AppWidgetManager.class); + when(mContext.getSystemService(AppWidgetManager.class)).thenReturn(appWidgetManager); + mPrefetchController.onSystemServicesReady(); + + when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(false); + trackJobs(jobNonWidget); + verify(mUsageStatsManagerInternal, timeout(DEFAULT_WAIT_MS)) + .getEstimatedPackageLaunchTime(SOURCE_PACKAGE, SOURCE_USER_ID); + assertFalse(jobNonWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); + + when(appWidgetManager.isBoundWidgetPackage(SOURCE_PACKAGE, SOURCE_USER_ID)) + .thenReturn(true); + trackJobs(jobWidget); + assertTrue(jobWidget.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); + } + + @Test public void testEstimatedLaunchTimeChangedToLate() { setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); when(mUsageStatsManagerInternal diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java index e2e7f5dd7ba2..94dcdf92d9d4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java @@ -58,72 +58,86 @@ public class LocationAttributionHelperTest { @Test public void testLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - mHelper.reportLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - mHelper.reportLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2)); } @Test public void testHighPowerLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - mHelper.reportHighPowerLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - mHelper.reportHighPowerLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index d0b2edadc714..890a5495ef16 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -845,6 +845,48 @@ public class LocationProviderManagerTest { } @Test + public void testLocationMonitoring_multipleIdentities() { + CallerIdentity identity1 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener1"); + CallerIdentity identity2 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener2"); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + IDENTITY.getPackageName())).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + IDENTITY.getPackageName())).isFalse(); + + ILocationListener listener1 = createMockLocationListener(); + LocationRequest request1 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request1, identity1, PERMISSION_FINE, listener1); + + ILocationListener listener2 = createMockLocationListener(); + LocationRequest request2 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request2, identity2, PERMISSION_FINE, listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener1); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isFalse(); + } + + @Test public void testProviderRequest() { assertThat(mProvider.getRequest().isActive()).isFalse(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 63416c97afa9..0e5640aa85dd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -47,6 +47,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.dx.mockito.inline.extended.ExtendedMockito.any import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean import com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt +import com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong import com.android.dx.mockito.inline.extended.ExtendedMockito.anyString import com.android.dx.mockito.inline.extended.ExtendedMockito.argThat import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn @@ -617,7 +618,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) { whenever(mocks.componentResolver.queryActivities( argThat { intent: Intent? -> intent != null && (action == intent.action) }, - nullable(), anyInt(), anyInt())) { + nullable(), anyLong(), anyInt())) { ArrayList(activities.asList().map { info: ActivityInfo? -> ResolveInfo().apply { activityInfo = info } }) @@ -627,7 +628,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { private fun mockQueryServices(action: String, vararg services: ServiceInfo) { whenever(mocks.componentResolver.queryServices( argThat { intent: Intent? -> intent != null && (action == intent.action) }, - nullable(), anyInt(), anyInt())) { + nullable(), anyLong(), anyInt())) { ArrayList(services.asList().map { info -> ResolveInfo().apply { serviceInfo = info } }) diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index fe23c14a0b95..966675819069 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -41,6 +41,7 @@ import static org.junit.Assume.assumeThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -316,14 +317,14 @@ public class WallpaperManagerServiceTests { final int lastUserId = 5; final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0); - doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt()); + doReturn(pi).when(mIpm).getServiceInfo(any(), anyLong(), anyInt()); final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); final ParceledListSlice ris = mIpm.queryIntentServices(intent, intent.resolveTypeIfNeeded(sContext.getContentResolver()), PackageManager.GET_META_DATA, 0); - doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), anyInt()); + doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyLong(), anyInt()); doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission( eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt()); @@ -348,7 +349,7 @@ public class WallpaperManagerServiceTests { final int lastUserId = 5; final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0); - doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt()); + doReturn(pi).when(mIpm).getServiceInfo(any(), anyLong(), anyInt()); final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); final ParceledListSlice ris = @@ -362,7 +363,7 @@ public class WallpaperManagerServiceTests { mService.switchUser(userId, null); verifyLastWallpaperData(userId, sImageWallpaperComponentName); // Simulate user unlocked - doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), eq(userId)); + doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyLong(), eq(userId)); mService.onUnlockUser(userId); verifyLastWallpaperData(userId, sDefaultWallpaperComponent); verifyCurrentSystemData(userId); diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 4c638d669019..bb3eb81df6ed 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -16,6 +16,13 @@ <configuration description="Runs Frameworks Services Tests."> <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push-file" key="SimpleServiceTestApp3.apk" + value="/data/local/tmp/cts/content/SimpleServiceTestApp3.apk" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> diff --git a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java index 3ace3f4c79dc..a1d4c203de18 100644 --- a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java +++ b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java @@ -66,7 +66,7 @@ public class BluetoothAirplaneModeListenerTest { when(mHelper.isBluetoothOn()).thenReturn(true); Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true); + when(mHelper.isMediaProfileConnected()).thenReturn(true); Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); when(mHelper.isAirplaneModeOn()).thenReturn(true); @@ -83,7 +83,7 @@ public class BluetoothAirplaneModeListenerTest { public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_NotPopToast() { mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT; when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true); + when(mHelper.isMediaProfileConnected()).thenReturn(true); when(mHelper.isAirplaneModeOn()).thenReturn(true); mBluetoothAirplaneModeListener.handleAirplaneModeChange(); @@ -97,7 +97,7 @@ public class BluetoothAirplaneModeListenerTest { public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_PopToast() { mBluetoothAirplaneModeListener.mToastCount = 0; when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isA2dpOrHearingAidConnected()).thenReturn(true); + when(mHelper.isMediaProfileConnected()).thenReturn(true); when(mHelper.isAirplaneModeOn()).thenReturn(true); mBluetoothAirplaneModeListener.handleAirplaneModeChange(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 086e3c0901a8..a83d51b0e5e7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -68,6 +68,7 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.MagnificationConfig; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -650,8 +651,12 @@ public class AbstractAccessibilityServiceConnectionTest { final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; - when(mMockMagnificationProcessor.setScaleAndCenter(displayId, - scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true); + MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, + SERVICE_ID)).thenReturn(true); when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false); final boolean result = mServiceConnection.setMagnificationScaleAndCenter( @@ -665,8 +670,12 @@ public class AbstractAccessibilityServiceConnectionTest { final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; - when(mMockMagnificationProcessor.setScaleAndCenter(displayId, - scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true); + MagnificationConfig config = new MagnificationConfig.Builder() + .setScale(scale) + .setCenterX(centerX) + .setCenterY(centerY).build(); + when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, + SERVICE_ID)).thenReturn(true); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.setMagnificationScaleAndCenter( diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java index c412b9403561..205c3daf84e7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java @@ -16,25 +16,33 @@ package com.android.server.accessibility; +import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE; +import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.accessibilityservice.MagnificationConfig; import android.graphics.Region; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationProcessor; +import com.android.server.accessibility.magnification.WindowMagnificationManager; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; - +import org.mockito.stubbing.Answer; /** * Tests for the {@link MagnificationProcessor} @@ -42,57 +50,115 @@ import org.mockito.MockitoAnnotations; public class MagnificationProcessorTest { private static final int TEST_DISPLAY = 0; - + private static final int SERVICE_ID = 42; + private static final float TEST_SCALE = 1.8f; + private static final float TEST_CENTER_X = 50.5f; + private static final float TEST_CENTER_Y = 100.5f; private MagnificationProcessor mMagnificationProcessor; @Mock private MagnificationController mMockMagnificationController; @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; + @Mock + private WindowMagnificationManager mMockWindowMagnificationManager; + FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub; + WindowMagnificationManagerStub mWindowMagnificationManagerStub; @Before + public void setup() { MockitoAnnotations.initMocks(this); - + mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub( + mMockFullScreenMagnificationController); + mWindowMagnificationManagerStub = new WindowMagnificationManagerStub( + mMockWindowMagnificationManager); when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( mMockFullScreenMagnificationController); + when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( + mMockWindowMagnificationManager); mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController); } @Test - public void getScale() { - final float result = 2; - when(mMockFullScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(result); + public void getScale_fullscreenMode_expectedValue() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE).build(); + setMagnificationActivated(TEST_DISPLAY, config); float scale = mMagnificationProcessor.getScale(TEST_DISPLAY); - assertEquals(scale, result, 0); + assertEquals(scale, TEST_SCALE, 0); } @Test - public void getCenterX_canControlMagnification_returnCenterX() { - final float result = 200; - when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(result); + public void getScale_windowMode_expectedValue() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float scale = mMagnificationProcessor.getScale(TEST_DISPLAY); + + assertEquals(scale, TEST_SCALE, 0); + } + + @Test + public void getCenterX_canControlFullscreenMagnification_returnCenterX() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); float centerX = mMagnificationProcessor.getCenterX( TEST_DISPLAY, /* canControlMagnification= */true); - assertEquals(centerX, result, 0); + assertEquals(centerX, TEST_CENTER_X, 0); } @Test - public void getCenterY_canControlMagnification_returnCenterY() { - final float result = 300; - when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(result); + public void getCenterX_canControlWindowMagnification_returnCenterX() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float centerX = mMagnificationProcessor.getCenterX( + TEST_DISPLAY, /* canControlMagnification= */true); + + assertEquals(centerX, TEST_CENTER_X, 0); + } + + @Test + public void getCenterY_canControlFullscreenMagnification_returnCenterY() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); float centerY = mMagnificationProcessor.getCenterY( TEST_DISPLAY, /* canControlMagnification= */false); - assertEquals(centerY, result, 0); + assertEquals(centerY, TEST_CENTER_Y, 0); } @Test - public void getMagnificationRegion_canControlMagnification_returnRegion() { + public void getCenterY_canControlWindowMagnification_returnCenterY() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + float centerY = mMagnificationProcessor.getCenterY( + TEST_DISPLAY, /* canControlMagnification= */false); + + assertEquals(centerY, TEST_CENTER_Y, 0); + } + + @Test + public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() { final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, region, /* canControlMagnification= */true); @@ -101,14 +167,25 @@ public class MagnificationProcessorTest { } @Test - public void getMagnificationRegion_notRegistered_shouldRegisterThenUnregister() { + public void getMagnificationRegion_canControlWindowMagnification_returnRegion() { + final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE); + mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, + region, /* canControlMagnification= */true); + + verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY), + eq(region)); + } + + @Test + public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { final Region region = new Region(10, 20, 100, 200); + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); doAnswer((invocation) -> { ((Region) invocation.getArguments()[1]).set(region); return null; }).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY), any()); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); final Region result = new Region(); mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY, @@ -119,44 +196,270 @@ public class MagnificationProcessorTest { } @Test - public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() { - final float centerX = 480.0f; - when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(centerX); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterX(TEST_CENTER_X).build(); + setMagnificationActivated(TEST_DISPLAY, config); final float result = mMagnificationProcessor.getCenterX( TEST_DISPLAY, /* canControlMagnification= */ true); - assertEquals(centerX, result, 0); + assertEquals(TEST_CENTER_X, result, 0); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY); } @Test - public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() { - final float centerY = 640.0f; - when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(centerY); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); final float result = mMagnificationProcessor.getCenterY( TEST_DISPLAY, /* canControlMagnification= */ true); - assertEquals(centerY, result, 0); + assertEquals(TEST_CENTER_Y, result, 0); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY); } @Test - public void setMagnificationScaleAndCenter_notRegistered_shouldRegister() { - final int serviceId = 42; - final float scale = 1.8f; - final float centerX = 50.5f; - final float centerY = 100.5f; - when(mMockFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, - scale, centerX, centerY, true, serviceId)).thenReturn(true); - when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false); + public void getCurrentMode_configDefaultMode_returnActivatedMode() { + final int targetMode = WINDOW_MODE; + setMagnificationActivated(TEST_DISPLAY, targetMode); + + int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY); + + assertEquals(WINDOW_MODE, currentMode); + } + + @Test + public void reset_fullscreenMagnificationActivated() { + setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE); + + mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + + verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false); + } + + @Test + public void reset_windowMagnificationActivated() { + setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE); - final boolean result = mMagnificationProcessor.setScaleAndCenter( - TEST_DISPLAY, scale, centerX, centerY, true, serviceId); + mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false); + + verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY); + } + + @Test + public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + final boolean result = mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); assertTrue(result); verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY); } + + @Test + public void setMagnificationConfig_windowMode_enableMagnification() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + final boolean result = mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + assertTrue(result); + } + + @Test + public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(FULLSCREEN_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + // mMockFullScreenMagnificationController.unregister(TEST_DISPLAY); + mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( + TEST_DISPLAY); + + assertConfigEquals(config, result); + } + + @Test + public void setMagnificationConfig_windowEnabled_expectedConfigValues() { + final MagnificationConfig config = new MagnificationConfig.Builder() + .setMode(WINDOW_MODE) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, config); + + mMagnificationProcessor.setMagnificationConfig( + TEST_DISPLAY, config, true, SERVICE_ID); + + final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( + TEST_DISPLAY); + + assertConfigEquals(config, result); + } + + @Test + public void setMagnificationConfig_controllingModeChangeAndAnimating_transitionConfigMode() { + final int currentActivatedMode = WINDOW_MODE; + final int targetMode = FULLSCREEN_MODE; + final MagnificationConfig oldConfig = new MagnificationConfig.Builder() + .setMode(currentActivatedMode) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X) + .setCenterY(TEST_CENTER_Y).build(); + setMagnificationActivated(TEST_DISPLAY, oldConfig); + final MagnificationConfig newConfig = new MagnificationConfig.Builder() + .setMode(targetMode) + .setScale(TEST_SCALE) + .setCenterX(TEST_CENTER_X + 10) + .setCenterY(TEST_CENTER_Y + 10).build(); + + // Has magnification animation running + when(mMockMagnificationController.hasDisableMagnificationCallback(TEST_DISPLAY)).thenReturn( + true); + setMagnificationActivated(TEST_DISPLAY, newConfig); + + final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig( + TEST_DISPLAY); + verify(mMockMagnificationController).transitionMagnificationConfigMode(eq(TEST_DISPLAY), + eq(newConfig), anyBoolean()); + assertConfigEquals(newConfig, result); + } + + private void setMagnificationActivated(int displayId, int configMode) { + setMagnificationActivated(displayId, + new MagnificationConfig.Builder().setMode(configMode).build()); + } + + private void setMagnificationActivated(int displayId, MagnificationConfig config) { + when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn( + true); + mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID); + if (config.getMode() == FULLSCREEN_MODE) { + when(mMockMagnificationController.isActivated(displayId, WINDOW_MODE)).thenReturn( + false); + mFullScreenMagnificationControllerStub.resetAndStubMethods(); + mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(), + config.getCenterX(), config.getCenterY(), true, SERVICE_ID); + } else if (config.getMode() == WINDOW_MODE) { + when(mMockMagnificationController.isActivated(displayId, FULLSCREEN_MODE)).thenReturn( + false); + mWindowMagnificationManagerStub.resetAndStubMethods(); + mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(), + config.getCenterX(), config.getCenterY()); + } + } + + private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig actual) { + assertEquals(expected.getMode(), actual.getMode()); + assertEquals(expected.getScale(), actual.getScale(), 0); + assertEquals(expected.getCenterX(), actual.getCenterX(), 0); + assertEquals(expected.getCenterY(), actual.getCenterY(), 0); + } + + private static class FullScreenMagnificationControllerStub { + private final FullScreenMagnificationController mScreenMagnificationController; + private float mScale = 1.0f; + private float mCenterX = 0; + private float mCenterY = 0; + private boolean mIsRegistered = false; + + FullScreenMagnificationControllerStub( + FullScreenMagnificationController screenMagnificationController) { + mScreenMagnificationController = screenMagnificationController; + } + + private void stubMethods() { + doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale( + TEST_DISPLAY); + doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX( + TEST_DISPLAY); + doAnswer(invocation -> mCenterY).when(mScreenMagnificationController).getCenterY( + TEST_DISPLAY); + doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered( + TEST_DISPLAY); + Answer enableMagnificationStubAnswer = invocation -> { + mScale = invocation.getArgument(1); + mCenterX = invocation.getArgument(2); + mCenterY = invocation.getArgument(3); + return true; + }; + doAnswer(enableMagnificationStubAnswer).when( + mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(), + anyFloat(), anyFloat(), eq(true), eq(SERVICE_ID)); + + Answer registerStubAnswer = invocation -> { + mIsRegistered = true; + return true; + }; + doAnswer(registerStubAnswer).when( + mScreenMagnificationController).register(eq(TEST_DISPLAY)); + + Answer unregisterStubAnswer = invocation -> { + mIsRegistered = false; + return true; + }; + doAnswer(unregisterStubAnswer).when( + mScreenMagnificationController).unregister(eq(TEST_DISPLAY)); + } + + public void resetAndStubMethods() { + Mockito.reset(mScreenMagnificationController); + stubMethods(); + } + } + + private static class WindowMagnificationManagerStub { + private final WindowMagnificationManager mWindowMagnificationManager; + private float mScale = 1.0f; + private float mCenterX = 0; + private float mCenterY = 0; + + WindowMagnificationManagerStub( + WindowMagnificationManager windowMagnificationManager) { + mWindowMagnificationManager = windowMagnificationManager; + } + + private void stubMethods() { + doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale( + TEST_DISPLAY); + doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX( + TEST_DISPLAY); + doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY( + TEST_DISPLAY); + Answer enableWindowMagnificationStubAnswer = invocation -> { + mScale = invocation.getArgument(1); + mCenterX = invocation.getArgument(2); + mCenterY = invocation.getArgument(3); + return true; + }; + doAnswer(enableWindowMagnificationStubAnswer).when( + mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY), + anyFloat(), anyFloat(), anyFloat()); + } + + public void resetAndStubMethods() { + Mockito.reset(mWindowMagnificationManager); + stubMethods(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 8a521d8a7490..ec1a0c2f1d0d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.accessibilityservice.MagnificationConfig; import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; @@ -154,7 +155,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController, never()).reset(anyInt(), any(MagnificationAnimationCallback.class)); verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(), - anyFloat(), anyFloat(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), nullable(IRemoteMagnificationAnimationCallback.class)); } @@ -229,7 +230,7 @@ public class MagnificationControllerTest { } @Test - public void transitionToFullScreen_centerNotInTheBounds_magnifyTheCenterOfMagnificationBounds() + public void transitionToFullScreen_centerNotInTheBounds_magnifyBoundsCenter() throws RemoteException { final Rect magnificationBounds = MAGNIFICATION_REGION.getBounds(); final PointF magnifiedCenter = new PointF(magnificationBounds.right + 100, @@ -289,6 +290,73 @@ public class MagnificationControllerTest { } @Test + public void configTransitionToWindowMode_fullScreenMagnifying_disableFullScreenAndEnableWindow() + throws RemoteException { + activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + obtainMagnificationConfig(MODE_WINDOW), + false); + + verify(mScreenMagnificationController).reset(eq(TEST_DISPLAY), eq(false)); + mMockConnection.invokeCallbacks(); + assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); + } + + @Test + public void configTransitionToFullScreen_windowMagnifying_disableWindowAndEnableFullScreen() + throws RemoteException { + final boolean animate = true; + activateMagnifier(MODE_WINDOW, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + obtainMagnificationConfig(MODE_FULLSCREEN), + animate); + mMockConnection.invokeCallbacks(); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, + DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + animate, MAGNIFICATION_GESTURE_HANDLER_ID); + } + + @Test + public void configTransitionToFullScreen_userSettingsDisablingFullScreen_enableFullScreen() + throws RemoteException { + activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + // User-setting mode + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, mTransitionCallBack); + + // Config-setting mode + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + obtainMagnificationConfig(MODE_FULLSCREEN), + true); + + assertEquals(DEFAULT_SCALE, mScreenMagnificationController.getScale(TEST_DISPLAY), 0); + assertEquals(MAGNIFIED_CENTER_X, mScreenMagnificationController.getCenterX(TEST_DISPLAY), + 0); + assertEquals(MAGNIFIED_CENTER_Y, mScreenMagnificationController.getCenterY(TEST_DISPLAY), + 0); + } + + @Test + public void interruptDuringTransitionToWindow_disablingFullScreen_discardPreviousTransition() + throws RemoteException { + activateMagnifier(MODE_FULLSCREEN, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); + // User-setting mode + mMagnificationController.transitionMagnificationModeLocked(TEST_DISPLAY, + MODE_WINDOW, mTransitionCallBack); + + // Config-setting mode + mMagnificationController.transitionMagnificationConfigMode(TEST_DISPLAY, + obtainMagnificationConfig(MODE_FULLSCREEN), + true); + + verify(mTransitionCallBack, never()).onResult(TEST_DISPLAY, true); + } + + @Test public void onDisplayRemoved_notifyAllModules() { mMagnificationController.onDisplayRemoved(TEST_DISPLAY); @@ -402,6 +470,16 @@ public class MagnificationControllerTest { } @Test + public void onFullScreenMagnificationActivationState_windowActivated_disableMagnification() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + + mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + + verify(mWindowMagnificationManager).disableWindowMagnification(eq(TEST_DISPLAY), eq(false)); + } + + @Test public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); @@ -609,6 +687,10 @@ public class MagnificationControllerTest { private void setMagnificationEnabled(int mode, float centerX, float centerY) throws RemoteException { setMagnificationModeSettings(mode); + activateMagnifier(mode, centerX, centerY); + } + + private void activateMagnifier(int mode, float centerX, float centerY) throws RemoteException { mScreenMagnificationControllerStubber.resetAndStubMethods(); final boolean windowMagnifying = mWindowMagnificationManager.isWindowMagnifierEnabled( TEST_DISPLAY); @@ -631,6 +713,11 @@ public class MagnificationControllerTest { Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, mode, CURRENT_USER_ID); } + private MagnificationConfig obtainMagnificationConfig(int mode) { + return new MagnificationConfig.Builder().setMode(mode).setScale(DEFAULT_SCALE).setCenterX( + MAGNIFIED_CENTER_X).setCenterY(MAGNIFIED_CENTER_Y).build(); + } + /** * Stubs public methods to simulate the real beahviours. */ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java index 2a5350454f67..0659a6019336 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MockWindowMagnificationConnection.java @@ -93,7 +93,7 @@ class MockWindowMagnificationConnection { final float scale = invocation.getArgument(1); mScale = Float.isNaN(scale) ? mScale : scale; computeMirrorWindowFrame(invocation.getArgument(2), invocation.getArgument(3)); - setAnimationCallback(invocation.getArgument(4)); + setAnimationCallback(invocation.getArgument(6)); computeSourceBounds(); mHasPendingCallback = true; if (!mSuspendCallback) { @@ -101,7 +101,7 @@ class MockWindowMagnificationConnection { } return null; }).when(mConnection).enableWindowMagnification(anyInt(), anyFloat(), anyFloat(), anyFloat(), - nullable(IRemoteMagnificationAnimationCallback.class)); + anyFloat(), anyFloat(), nullable(IRemoteMagnificationAnimationCallback.class)); } private void stubDisableWindowMagnification() throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java index 1638563e8242..3822dc362b6b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java @@ -67,7 +67,7 @@ public class WindowMagnificationConnectionWrapperTest { @Test public void enableWindowMagnification() throws RemoteException { mConnectionWrapper.enableWindowMagnification(TEST_DISPLAY, 2, 100f, 200f, - mAnimationCallback); + 0f, 0f, mAnimationCallback); verify(mAnimationCallback).onResult(true); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index 1b8aff50d2e2..b807c11d5a5c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import android.graphics.PointF; import android.graphics.Rect; import android.os.RemoteException; +import android.os.SystemClock; import android.testing.TestableContext; import android.util.DebugUtils; import android.view.InputDevice; @@ -58,10 +59,11 @@ public class WindowMagnificationGestureHandlerTest { public static final int STATE_SHOW_MAGNIFIER_SHORTCUT = 2; public static final int STATE_TWO_FINGERS_DOWN = 3; public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4; + public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5; //TODO: Test it after can injecting Handler to GestureMatcher is available. public static final int FIRST_STATE = STATE_IDLE; - public static final int LAST_STATE = STATE_SHOW_MAGNIFIER_TRIPLE_TAP; + public static final int LAST_STATE = STATE_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD; // Co-prime x and y, to potentially catch x-y-swapped errors public static final float DEFAULT_TAP_X = 301; @@ -178,6 +180,12 @@ public class WindowMagnificationGestureHandlerTest { == mWindowMagnificationGestureHandler.mDetectingState, state); } break; + case STATE_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + check(isWindowMagnifierEnabled(DISPLAY_0), state); + check(mWindowMagnificationGestureHandler.mCurrentState + == mWindowMagnificationGestureHandler.mViewportDraggingState, state); + } + break; case STATE_TWO_FINGERS_DOWN: { check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState @@ -229,6 +237,13 @@ public class WindowMagnificationGestureHandlerTest { tap(); } break; + case STATE_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + // Perform triple tap and hold gesture + tap(); + tap(); + tapAndHold(); + } + break; default: throw new IllegalArgumentException("Illegal state: " + state); } @@ -262,6 +277,10 @@ public class WindowMagnificationGestureHandlerTest { tap(); } break; + case STATE_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: { + send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); + } + break; default: throw new IllegalArgumentException("Illegal state: " + state); } @@ -308,6 +327,11 @@ public class WindowMagnificationGestureHandlerTest { send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); } + private void tapAndHold() { + send(downEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); + SystemClock.sleep(ViewConfiguration.getLongPressTimeout() + 100); + } + private String stateDump() { return "\nCurrent state dump:\n" + mWindowMagnificationGestureHandler.mCurrentState; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index 15ba358f146e..85512f36da41 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -143,8 +143,7 @@ public class WindowMagnificationManagerTest { * new connection. */ @Test - public void - setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath() + public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath() throws RemoteException { mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); MockWindowMagnificationConnection secondConnection = @@ -177,7 +176,7 @@ public class WindowMagnificationManagerTest { mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 2f, 200f, 300f); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), - eq(200f), eq(300f), notNull()); + eq(200f), eq(300f), eq(0f), eq(0f), notNull()); } @Test @@ -189,7 +188,8 @@ public class WindowMagnificationManagerTest { mAnimationCallback); verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(2f), - eq(200f), eq(300f), any(IRemoteMagnificationAnimationCallback.class)); + eq(200f), eq(300f), eq(0f), eq(0f), + any(IRemoteMagnificationAnimationCallback.class)); verify(mAnimationCallback).onResult(true); } @@ -377,6 +377,17 @@ public class WindowMagnificationManagerTest { } @Test + public void resetMagnification_enabled_windowMagnifierDisabled() { + mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN); + assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + + mWindowMagnificationManager.reset(TEST_DISPLAY); + + assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY)); + } + + @Test public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification() throws RemoteException { mWindowMagnificationManager.requestConnection(true); @@ -400,6 +411,34 @@ public class WindowMagnificationManagerTest { } @Test + public void centerGetter_enabledOnTestDisplayWindowAtCenter_expectedValues() + throws RemoteException { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_CENTER); + + assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), + eq(100f), eq(200f), eq(0f), eq(0f), notNull()); + } + + @Test + public void centerGetter_enabledOnTestDisplayWindowAtLeftTop_expectedValues() + throws RemoteException { + mWindowMagnificationManager.requestConnection(true); + mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, + 100f, 200f, WindowMagnificationManager.WINDOW_POSITION_AT_TOP_LEFT); + + assertEquals(mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 100f); + assertEquals(mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 200f); + + verify(mMockConnection.getConnection()).enableWindowMagnification(eq(TEST_DISPLAY), eq(3f), + eq(100f), eq(200f), eq(-1f), eq(-1f), notNull()); + } + + @Test public void onDisplayRemoved_enabledOnTestDisplay_disabled() { mWindowMagnificationManager.requestConnection(true); mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, 100f, 200f); diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java index 9dd413bcdbab..4b359eb11d58 100644 --- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java @@ -20,6 +20,7 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE; import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -30,7 +31,9 @@ import android.app.Instrumentation; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; +import android.os.IBinder; import android.os.SystemClock; import android.provider.DeviceConfig; import android.provider.Settings; @@ -50,6 +53,8 @@ import org.junit.runner.RunWith; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Build/Install/Run: @@ -83,6 +88,12 @@ public final class ServiceRestarterTest { private static final int ACTION_STOPPKG = 8; private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG; + private static final String LOCAL_APK_BASE_PATH = "/data/local/tmp/cts/content/"; + private static final String TEST_PACKAGE3_APK = "SimpleServiceTestApp3.apk"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_TARGET_PACKAGE = "target_package"; + private SettingsSession<String> mAMConstantsSettings; private DeviceConfigSession<String> mExtraDelaysDeviceConfig; private DeviceConfigSession<Boolean> mEnableExtraDelaysDeviceConfig; @@ -348,6 +359,83 @@ public final class ServiceRestarterTest { return res; } + @Test + public void testServiceWithDepPkgStopped() throws Exception { + final CountDownLatch[] latchHolder = new CountDownLatch[1]; + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + latchHolder[0].countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + latchHolder[0].countDown(); + } + }; + + final long timeout = 5_000; + final long shortTimeout = 2_000; + final Intent intent = new Intent(ACTION_SERVICE_WITH_DEP_PKG); + final String testPkg = TEST_PACKAGE2_NAME; + final String libPkg = TEST_PACKAGE3_NAME; + final String apkPath = LOCAL_APK_BASE_PATH + TEST_PACKAGE3_APK; + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + + intent.setComponent(ComponentName.unflattenFromString(testPkg + "/" + TEST_SERVICE_NAME)); + intent.putExtra(EXTRA_TARGET_PACKAGE, libPkg); + try { + executeShellCmd("am service-restart-backoff disable " + testPkg); + + latchHolder[0] = new CountDownLatch(1); + assertTrue("Unable to bind to test service in " + testPkg, + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)); + assertTrue("Timed out to bind service in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + assertTrue(libPkg + " should be a dependency package of " + testPkg, + isPackageDependency(testPkg, libPkg)); + + // Force-stop lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(libPkg); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Re-install the lib package, the test service should be restarted. + latchHolder[0] = new CountDownLatch(2); + assertTrue("Unable to install package " + apkPath, installPackage(apkPath)); + assertTrue("Test service in didn't restart in " + testPkg, + latchHolder[0].await(timeout, TimeUnit.MILLISECONDS)); + + Thread.sleep(shortTimeout); + + // Force-stop the service package, the test service should not be restarted. + latchHolder[0] = new CountDownLatch(2); + am.forceStopPackage(testPkg); + assertFalse("Test service should not be restarted in " + testPkg, + latchHolder[0].await(timeout * 2, TimeUnit.MILLISECONDS)); + } finally { + executeShellCmd("am service-restart-backoff enable " + testPkg); + mContext.unbindService(conn); + am.forceStopPackage(testPkg); + } + } + + private boolean isPackageDependency(String pkgName, String libPackage) throws Exception { + final String output = SystemUtil.runShellCommand("dumpsys activity processes " + pkgName); + final Matcher matcher = Pattern.compile("packageDependencies=\\{.*?\\b" + + libPackage + "\\b.*?\\}").matcher(output); + return matcher.find(); + } + + private boolean installPackage(String apkPath) throws Exception { + return executeShellCmd("pm install -t " + apkPath).equals("Success\n"); + } + private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener, long timeout) throws Exception { final Intent intent = new Intent(); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 1c49e6e64dd8..e40f5439d0c2 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -29,7 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.intThat; +import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -135,7 +135,7 @@ public final class AppHibernationServiceTest { packages.add(makePackageInfo(PACKAGE_NAME_2)); packages.add(makePackageInfo(PACKAGE_NAME_3)); doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( - intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); + longThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); UserInfo userInfo = addUser(USER_ID_1); @@ -412,7 +412,7 @@ public final class AppHibernationServiceTest { UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */); mUserInfos.add(userInfo); doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); + .getInstalledPackages(longThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); return userInfo; } diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index 205ff300c543..aa7d6aa7de71 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -38,7 +38,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.params.BackupParams; -import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportConnection; import com.android.server.backup.utils.BackupEligibilityRules; import org.junit.Before; @@ -57,7 +57,8 @@ public class UserBackupManagerServiceTest { @Mock IBackupManagerMonitor mBackupManagerMonitor; @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; - @Mock TransportClient mTransportClient; + @Mock + TransportConnection mTransportConnection; @Mock IBackupTransport mBackupTransport; @Mock BackupEligibilityRules mBackupEligibilityRules; @@ -96,7 +97,7 @@ public class UserBackupManagerServiceTest { BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver, mBackupManagerMonitor, /* flags */ 0, mBackupEligibilityRules, - mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP); + mTransportConnection, /* transportDirName */ "", OnTaskFinishedListener.NOP); assertThat(params.kvPackages).isEmpty(); assertThat(params.fullPackages).contains(TEST_PACKAGE); @@ -112,7 +113,7 @@ public class UserBackupManagerServiceTest { BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver, mBackupManagerMonitor, /* flags */ 0, mBackupEligibilityRules, - mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP); + mTransportConnection, /* transportDirName */ "", OnTaskFinishedListener.NOP); assertThat(params.kvPackages).contains(TEST_PACKAGE); assertThat(params.fullPackages).isEmpty(); @@ -128,7 +129,7 @@ public class UserBackupManagerServiceTest { BackupParams params = mService.getRequestBackupParams(TEST_PACKAGES, mBackupObserver, mBackupManagerMonitor, /* flags */ 0, mBackupEligibilityRules, - mTransportClient, /* transportDirName */ "", OnTaskFinishedListener.NOP); + mTransportConnection, /* transportDirName */ "", OnTaskFinishedListener.NOP); assertThat(params.kvPackages).isEmpty(); assertThat(params.fullPackages).isEmpty(); @@ -138,10 +139,10 @@ public class UserBackupManagerServiceTest { @Test public void testGetOperationTypeFromTransport_returnsBackupByDefault() throws Exception { - when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); + when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport); when(mBackupTransport.getTransportFlags()).thenReturn(0); - int operationType = mService.getOperationTypeFromTransport(mTransportClient); + int operationType = mService.getOperationTypeFromTransport(mTransportConnection); assertThat(operationType).isEqualTo(OperationType.BACKUP); } @@ -153,11 +154,11 @@ public class UserBackupManagerServiceTest { // rolled out. mService.shouldUseNewBackupEligibilityRules = true; - when(mTransportClient.connectOrThrow(any())).thenReturn(mBackupTransport); + when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport); when(mBackupTransport.getTransportFlags()).thenReturn( BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER); - int operationType = mService.getOperationTypeFromTransport(mTransportClient); + int operationType = mService.getOperationTypeFromTransport(mTransportConnection); assertThat(operationType).isEqualTo(OperationType.MIGRATION); } diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java deleted file mode 100644 index bae11eb86b32..000000000000 --- a/services/tests/servicestests/src/com/android/server/backup/transport/DelegatingTransportTest.java +++ /dev/null @@ -1,386 +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.server.backup.transport; - -import static junit.framework.Assert.assertEquals; - -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.app.backup.RestoreDescription; -import android.app.backup.RestoreSet; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.backup.IBackupTransport; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@Presubmit -@RunWith(AndroidJUnit4.class) -public class DelegatingTransportTest { - @Mock private IBackupTransport mBackupTransport; - @Mock private PackageInfo mPackageInfo; - @Mock private ParcelFileDescriptor mFd; - - private final String mPackageName = "testpackage"; - private final RestoreSet mRestoreSet = new RestoreSet(); - private final int mFlags = 1; - private final long mRestoreToken = 10; - private final long mSize = 100; - private final int mNumBytes = 1000; - private DelegatingTransport mDelegatingTransport; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mDelegatingTransport = new DelegatingTransport() { - @Override - protected IBackupTransport getDelegate() { - return mBackupTransport; - } - }; - } - - @Test - public void testName() throws RemoteException { - String exp = "dummy"; - when(mBackupTransport.name()).thenReturn(exp); - - String ret = mDelegatingTransport.name(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).name(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testConfigurationIntent() throws RemoteException { - Intent exp = new Intent("dummy"); - when(mBackupTransport.configurationIntent()).thenReturn(exp); - - Intent ret = mDelegatingTransport.configurationIntent(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).configurationIntent(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testCurrentDestinationString() throws RemoteException { - String exp = "dummy"; - when(mBackupTransport.currentDestinationString()).thenReturn(exp); - - String ret = mDelegatingTransport.currentDestinationString(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).currentDestinationString(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testDataManagementIntent() throws RemoteException { - Intent exp = new Intent("dummy"); - when(mBackupTransport.dataManagementIntent()).thenReturn(exp); - - Intent ret = mDelegatingTransport.dataManagementIntent(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).dataManagementIntent(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testDataManagementIntentLabel() throws RemoteException { - String exp = "dummy"; - when(mBackupTransport.dataManagementIntentLabel()).thenReturn(exp); - - CharSequence ret = mDelegatingTransport.dataManagementIntentLabel(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).dataManagementIntentLabel(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testTransportDirName() throws RemoteException { - String exp = "dummy"; - when(mBackupTransport.transportDirName()).thenReturn(exp); - - String ret = mDelegatingTransport.transportDirName(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).transportDirName(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testRequestBackupTime() throws RemoteException { - long exp = 1000L; - when(mBackupTransport.requestBackupTime()).thenReturn(exp); - - long ret = mDelegatingTransport.requestBackupTime(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).requestBackupTime(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testInitializeDevice() throws RemoteException { - int exp = 1000; - when(mBackupTransport.initializeDevice()).thenReturn(exp); - - long ret = mDelegatingTransport.initializeDevice(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).initializeDevice(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testPerformBackup() throws RemoteException { - int exp = 1000; - when(mBackupTransport.performBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp); - - int ret = mDelegatingTransport.performBackup(mPackageInfo, mFd, mFlags); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).performBackup(mPackageInfo, mFd, mFlags); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testClearBackupData() throws RemoteException { - int exp = 1000; - when(mBackupTransport.clearBackupData(mPackageInfo)).thenReturn(exp); - - int ret = mDelegatingTransport.clearBackupData(mPackageInfo); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).clearBackupData(mPackageInfo); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testFinishBackup() throws RemoteException { - int exp = 1000; - when(mBackupTransport.finishBackup()).thenReturn(exp); - - int ret = mDelegatingTransport.finishBackup(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).finishBackup(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetAvailableRestoreSets() throws RemoteException { - RestoreSet[] exp = new RestoreSet[] {mRestoreSet}; - when(mBackupTransport.getAvailableRestoreSets()).thenReturn(exp); - - RestoreSet[] ret = mDelegatingTransport.getAvailableRestoreSets(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getAvailableRestoreSets(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetCurrentRestoreSet() throws RemoteException { - long exp = 1000; - when(mBackupTransport.getCurrentRestoreSet()).thenReturn(exp); - - long ret = mDelegatingTransport.getCurrentRestoreSet(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getCurrentRestoreSet(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testStartRestore() throws RemoteException { - int exp = 1000; - PackageInfo[] packageInfos = {mPackageInfo}; - when(mBackupTransport.startRestore(mRestoreToken, packageInfos)).thenReturn(exp); - - int ret = mDelegatingTransport.startRestore(mRestoreToken, packageInfos); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).startRestore(mRestoreToken, packageInfos); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testNextRestorePackage() throws RemoteException { - RestoreDescription exp = new RestoreDescription(mPackageName, 1); - when(mBackupTransport.nextRestorePackage()).thenReturn(exp); - - RestoreDescription ret = mDelegatingTransport.nextRestorePackage(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).nextRestorePackage(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetRestoreData() throws RemoteException { - int exp = 1000; - when(mBackupTransport.getRestoreData(mFd)).thenReturn(exp); - - int ret = mDelegatingTransport.getRestoreData(mFd); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getRestoreData(mFd); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void tesFinishRestore() throws RemoteException { - mDelegatingTransport.finishRestore(); - - verify(mBackupTransport, times(1)).finishRestore(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testRequestFullBackupTime() throws RemoteException { - long exp = 1000L; - when(mBackupTransport.requestFullBackupTime()).thenReturn(exp); - - long ret = mDelegatingTransport.requestFullBackupTime(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).requestFullBackupTime(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testPerformFullBackup() throws RemoteException { - int exp = 1000; - when(mBackupTransport.performFullBackup(mPackageInfo, mFd, mFlags)).thenReturn(exp); - - int ret = mDelegatingTransport.performFullBackup(mPackageInfo, mFd, mFlags); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).performFullBackup(mPackageInfo, mFd, mFlags); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testCheckFullBackupSize() throws RemoteException { - int exp = 1000; - when(mBackupTransport.checkFullBackupSize(mSize)).thenReturn(exp); - - int ret = mDelegatingTransport.checkFullBackupSize(mSize); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).checkFullBackupSize(mSize); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testSendBackupData() throws RemoteException { - int exp = 1000; - when(mBackupTransport.sendBackupData(mNumBytes)).thenReturn(exp); - - int ret = mDelegatingTransport.sendBackupData(mNumBytes); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).sendBackupData(mNumBytes); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testCancelFullBackup() throws RemoteException { - mDelegatingTransport.cancelFullBackup(); - - verify(mBackupTransport, times(1)).cancelFullBackup(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testIsAppEligibleForBackup() throws RemoteException { - boolean exp = true; - when(mBackupTransport.isAppEligibleForBackup(mPackageInfo, true)).thenReturn(exp); - - boolean ret = mDelegatingTransport.isAppEligibleForBackup(mPackageInfo, true); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).isAppEligibleForBackup(mPackageInfo, true); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetBackupQuota() throws RemoteException { - long exp = 1000; - when(mBackupTransport.getBackupQuota(mPackageName, true)).thenReturn(exp); - - long ret = mDelegatingTransport.getBackupQuota(mPackageName, true); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getBackupQuota(mPackageName, true); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetNextFullRestoreDataChunk() throws RemoteException { - int exp = 1000; - when(mBackupTransport.getNextFullRestoreDataChunk(mFd)).thenReturn(exp); - - int ret = mDelegatingTransport.getNextFullRestoreDataChunk(mFd); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getNextFullRestoreDataChunk(mFd); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testAbortFullRestore() throws RemoteException { - int exp = 1000; - when(mBackupTransport.abortFullRestore()).thenReturn(exp); - - int ret = mDelegatingTransport.abortFullRestore(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).abortFullRestore(); - verifyNoMoreInteractions(mBackupTransport); - } - - @Test - public void testGetTransportFlags() throws RemoteException { - int exp = 1000; - when(mBackupTransport.getTransportFlags()).thenReturn(exp); - - int ret = mDelegatingTransport.getTransportFlags(); - - assertEquals(exp, ret); - verify(mBackupTransport, times(1)).getTransportFlags(); - verifyNoMoreInteractions(mBackupTransport); - } -} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index e3e3900c47e0..d192697827f6 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -143,8 +143,8 @@ public class BiometricSchedulerTest { final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); - final BiometricPromptClientMonitor client1 = - new BiometricPromptClientMonitor(mContext, mToken, lazyDaemon1, listener1); + final TestAuthenticationClient client1 = + new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); @@ -180,8 +180,8 @@ public class BiometricSchedulerTest { @Test public void testCancelNotInvoked_whenOperationWaitingForCookie() { final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class); - final BiometricPromptClientMonitor client1 = new BiometricPromptClientMonitor(mContext, - mToken, lazyDaemon1, mock(ClientMonitorCallbackConverter.class)); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class)); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); // Schedule a BiometricPrompt authentication request @@ -195,6 +195,8 @@ public class BiometricSchedulerTest { // should go back to idle, since in this case the framework has not even requested the HAL // to authenticate yet. mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); assertNull(mScheduler.mCurrentOperation); } @@ -316,6 +318,10 @@ public class BiometricSchedulerTest { eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0) /* vendorCode */); assertNull(mScheduler.getCurrentClient()); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); + assertTrue(client2.isAlreadyDone()); + assertTrue(client2.mDestroyed); } @Test @@ -465,39 +471,9 @@ public class BiometricSchedulerTest { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } - private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> { - - public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, ClientMonitorCallbackConverter listener) { - super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, - false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, - TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */, - 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class), - false /* isKeyguard */, true /* shouldVibrate */, - false /* isKeyguardBypassEnabled */); - } - - @Override - protected void stopHalOperation() { - } - - @Override - protected void startHalOperation() { - } - - @Override - protected void handleLifecycleAfterAuth(boolean authenticated) { - - } - - @Override - public boolean wasUserDetected() { - return false; - } - } - private static class TestAuthenticationClient extends AuthenticationClient<Object> { int mNumCancels = 0; + boolean mDestroyed = false; public TestAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @@ -530,6 +506,13 @@ public class BiometricSchedulerTest { return false; } + @Override + public void destroy() { + mDestroyed = true; + super.destroy(); + } + + @Override public void cancel() { mNumCancels++; super.cancel(); @@ -595,6 +578,7 @@ public class BiometricSchedulerTest { @Override public void destroy() { + super.destroy(); mDestroyed = true; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 2777bdf910b7..3c809f9e4814 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1735,11 +1735,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { pi.applicationInfo.flags = flags; doReturn(pi).when(getServices().ipackageManager).getPackageInfo( eq(packageName), - anyInt(), + anyLong(), eq(userId)); doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo( eq(packageName), - anyInt(), + anyLong(), eq(userId)); doReturn(true).when(getServices().ipackageManager).isPackageAvailable(packageName, userId); // Setup application UID with the PackageManager @@ -4708,11 +4708,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Ensure packages are *not* flagged as test_only. doReturn(new ApplicationInfo()).when(getServices().ipackageManager).getApplicationInfo( eq(admin1.getPackageName()), - anyInt(), + anyLong(), eq(CALLER_USER_HANDLE)); doReturn(new ApplicationInfo()).when(getServices().ipackageManager).getApplicationInfo( eq(admin2.getPackageName()), - anyInt(), + anyLong(), eq(CALLER_USER_HANDLE)); // Initial state is disabled. @@ -7078,7 +7078,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { doReturn(ai).when(getServices().ipackageManager).getApplicationInfo( eq(admin1.getPackageName()), - anyInt(), + anyLong(), eq(CALLER_USER_HANDLE)); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java index fe0df5818651..b8824c3d8268 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @@ -221,7 +220,7 @@ public abstract class DpmTestBase { doReturn(ai).when(mServices.ipackageManager).getApplicationInfo( eq(admin.getPackageName()), - anyInt(), + anyLong(), eq(UserHandle.getUserId(packageUid))); // Set up queryBroadcastReceivers(). @@ -248,7 +247,7 @@ public abstract class DpmTestBase { doReturn(aci).when(mServices.ipackageManager).getReceiverInfo( eq(admin), - anyInt(), + anyLong(), eq(UserHandle.getUserId(packageUid))); doReturn(new String[] {admin.getPackageName()}).when(mServices.ipackageManager) diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 285806b5dcd7..c6757261ce42 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -59,6 +59,20 @@ public class BrightnessMappingStrategyTest { 5000 }; + private static final int[] LUX_LEVELS_IDLE = { + 0, + 10, + 40, + 80, + 200, + 655, + 1200, + 2500, + 4400, + 8000, + 10000 + }; + private static final float[] DISPLAY_LEVELS_NITS = { 13.25f, 54.0f, @@ -73,6 +87,20 @@ public class BrightnessMappingStrategyTest { 478.5f, }; + private static final float[] DISPLAY_LEVELS_NITS_IDLE = { + 23.25f, + 64.0f, + 88.85f, + 115.02f, + 142.7f, + 180.12f, + 222.1f, + 275.2f, + 345.8f, + 425.2f, + 468.5f, + }; + private static final int[] DISPLAY_LEVELS_BACKLIGHT = { 9, 30, @@ -88,7 +116,6 @@ public class BrightnessMappingStrategyTest { }; private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; - private static final float[] DISPLAY_LEVELS_RANGE_NITS = { 13.25f, 478.5f }; private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f }; @@ -118,6 +145,8 @@ public class BrightnessMappingStrategyTest { new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f }, new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f }); + private static final float TOLERANCE = 0.0001f; + @Test public void testSimpleStrategyMappingAtControlPoints() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); @@ -357,6 +386,27 @@ public class BrightnessMappingStrategyTest { assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc)); } + @Test + public void testIdleModeConfigLoadsCorrectly() { + Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + + // Create an idle mode bms + // This will fail if it tries to fetch the wrong configuration. + BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc); + assertNotNull("BrightnessMappingStrategy should not be null", bms); + + // Ensure that the config is the one we set + // Ensure that the lux -> brightness -> nits path works. () + for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) { + assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE); + assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i], + TOLERANCE); + assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])), + DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE); + } + } + private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) { // Save out all of the initial brightness data for comparison after reset. float[] initialBrightnessLevels = new float[LUX_LEVELS.length]; @@ -421,14 +471,39 @@ public class BrightnessMappingStrategyTest { brightnessLevelsNits); } + private Resources createResourcesIdle(int[] luxLevels, float[] brightnessLevelsNits) { + return createResources(EMPTY_INT_ARRAY, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY, + luxLevels, brightnessLevelsNits); + } + private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, float[] brightnessLevelsNits) { + return createResources(luxLevels, brightnessLevelsBacklight, brightnessLevelsNits, + EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY); + + } + + private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, + float[] brightnessLevelsNits, int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) { + Resources mockResources = mock(Resources.class); + // For historical reasons, the lux levels resource implicitly defines the first point as 0, // so we need to chop it off of the array the mock resource object returns. - int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length); - when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels)) - .thenReturn(luxLevelsResource); + // Don't mock if these values are not set. If we try to use them, we will fail. + if (luxLevels.length > 0) { + int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length); + when(mockResources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevels)) + .thenReturn(luxLevelsResource); + } + if (luxLevelsIdle.length > 0) { + int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1, + luxLevelsIdle.length); + when(mockResources.getIntArray( + com.android.internal.R.array.config_autoBrightnessLevelsIdle)) + .thenReturn(luxLevelsIdleResource); + } when(mockResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) @@ -438,6 +513,10 @@ public class BrightnessMappingStrategyTest { when(mockResources.obtainTypedArray( com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mockBrightnessLevelNits); + TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle); + when(mockResources.obtainTypedArray( + com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle)) + .thenReturn(mockBrightnessLevelNitsIdle); when(mockResources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum)) diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index cf4bdf60c34a..b588db66a08f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; @@ -153,6 +154,7 @@ public class ActiveSourceActionTest { mHdmiControlService); audioDevice.init(); mLocalDevices.add(audioDevice); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 4ff7c6694aae..ff01cb1a3a1d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -115,6 +116,7 @@ public class ArcInitiationActionFromAvrTest { mAction = new ArcInitiationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); + hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index c6bb9144b983..a44a5cde0276 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.google.common.truth.Truth.assertThat; @@ -114,6 +115,7 @@ public class ArcTerminationActionFromAvrTest { mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); + hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); hdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java new file mode 100644 index 000000000000..968a75c043ab --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerInternalWrapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.hdmi; + +/** + * Fake class which stubs PowerManagerInternalWrapper (useful for testing). + */ +public class FakePowerManagerInternalWrapper extends PowerManagerInternalWrapper { + + private long mIdleDurationMs = -1; + + /** + * Sets the duration (in milliseconds) that this device has been idle for. + */ + public void setIdleDuration(long idleDurationMs) { + mIdleDurationMs = idleDurationMs; + } + + @Override + public boolean wasDeviceIdleFor(long ms) { + return ms <= mIdleDurationMs; + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 17f827da1ae7..a4113924294b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -15,6 +15,7 @@ */ package com.android.server.hdmi; +import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; @@ -206,6 +207,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { 4, HdmiPortInfo.PORT_INPUT, HDMI_3_PHYSICAL_ADDRESS, true, false, false); mNativeWrapper.setPortInfo(mHdmiPortInfo); mHdmiControlService.initService(); + mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); // No TV device interacts with AVR so system audio control won't be turned on here diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 055459c86705..2d13e692a3a9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -76,6 +76,8 @@ public class HdmiCecLocalDevicePlaybackTest { private int mPlaybackLogicalAddress; private boolean mWokenUp; private boolean mActiveMediaSessionsPaused; + private FakePowerManagerInternalWrapper mPowerManagerInternal = + new FakePowerManagerInternalWrapper(); @Before public void setUp() { @@ -146,6 +148,7 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(context); mHdmiControlService.setPowerManager(mPowerManager); + mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mPlaybackPhysicalAddress = 0x2000; mNativeWrapper.setPhysicalAddress(mPlaybackPhysicalAddress); @@ -1969,4 +1972,41 @@ public class HdmiCecLocalDevicePlaybackTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortPressed); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortReleased); } + + @Test + public void onHotplugInAfterHotplugOut_noStandbyAfterDelay() { + mPowerManager.setInteractive(true); + mNativeWrapper.onHotplugEvent(1, false); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward( + HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS / 2); + mNativeWrapper.onHotplugEvent(1, true); + mTestLooper.dispatchAll(); + + mPowerManagerInternal.setIdleDuration( + HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + mTestLooper.dispatchAll(); + + assertThat(mPowerManager.isInteractive()).isTrue(); + } + + @Test + public void onHotplugOut_standbyAfterDelay_onlyAfterDeviceIdle() { + mPowerManager.setInteractive(true); + mNativeWrapper.onHotplugEvent(1, false); + mTestLooper.dispatchAll(); + + mPowerManagerInternal.setIdleDuration(0); + mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + mTestLooper.dispatchAll(); + assertThat(mPowerManager.isInteractive()).isTrue(); + + mPowerManagerInternal.setIdleDuration( + HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + mTestLooper.moveTimeForward(HdmiCecLocalDevicePlayback.STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS); + mTestLooper.dispatchAll(); + assertThat(mPowerManager.isInteractive()).isFalse(); + } } diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index b3a513fb3b57..ddc58b23f635 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -23,6 +23,7 @@ import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -110,7 +111,7 @@ public class LocaleManagerServiceTest { @Test(expected = SecurityException.class) public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception { doReturn(DEFAULT_UID) - .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyInt(), anyInt()); + .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION); try { @@ -153,7 +154,7 @@ public class LocaleManagerServiceTest { @Test public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception { doReturn(DEFAULT_UID) - .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyInt(), anyInt()); + .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); // if package is not owned by the caller, the calling app should have the following // permission. We will mock this to succeed to imitate that. setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION); @@ -168,7 +169,7 @@ public class LocaleManagerServiceTest { @Test public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception { doReturn(Binder.getCallingUid()) - .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyInt(), anyInt()); + .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES); @@ -179,7 +180,7 @@ public class LocaleManagerServiceTest { @Test(expected = IllegalArgumentException.class) public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception { doReturn(INVALID_UID) - .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyInt(), anyInt()); + .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt()); try { mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, LocaleList.getEmptyLocaleList()); @@ -192,7 +193,7 @@ public class LocaleManagerServiceTest { @Test(expected = SecurityException.class) public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception { doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) - .getPackageUid(anyString(), anyInt(), anyInt()); + .getPackageUid(anyString(), anyLong(), anyInt()); setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES); try { @@ -210,7 +211,7 @@ public class LocaleManagerServiceTest { throws Exception { // any valid app calling for its own package or having appropriate permission doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) - .getPackageUid(anyString(), anyInt(), anyInt()); + .getPackageUid(anyString(), anyLong(), anyInt()); setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES); doReturn(null) .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); @@ -225,7 +226,7 @@ public class LocaleManagerServiceTest { public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList() throws Exception { doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) - .getPackageUid(anyString(), anyInt(), anyInt()); + .getPackageUid(anyString(), anyLong(), anyInt()); setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES); doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null)) .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt()); @@ -240,7 +241,7 @@ public class LocaleManagerServiceTest { public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales() throws Exception { doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal) - .getPackageUid(anyString(), anyInt(), anyInt()); + .getPackageUid(anyString(), anyLong(), anyInt()); doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); @@ -254,7 +255,7 @@ public class LocaleManagerServiceTest { public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales() throws Exception { doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) - .getPackageUid(anyString(), anyInt(), anyInt()); + .getPackageUid(anyString(), anyLong(), anyInt()); setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES); doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index f45c869949b5..3722ba4ec468 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; @@ -2357,7 +2358,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected void prepareIntentActivities(ComponentName cn) { when(mMockPackageManagerInternal.queryIntentActivities( - anyOrNull(Intent.class), anyStringOrNull(), anyInt(), anyInt(), anyInt())) + anyOrNull(Intent.class), anyStringOrNull(), anyLong(), anyInt(), anyInt())) .thenReturn(Collections.singletonList( ri(cn.getPackageName(), cn.getClassName(), false, 0))); } diff --git a/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java index 764c504eeede..6245f829fc5a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java @@ -22,8 +22,9 @@ import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions; import static com.google.common.truth.Truth.assertThat; import android.os.Bundle; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.BundleUtils; @@ -35,6 +36,7 @@ import org.junit.runner.RunWith; * Build/Install/Run: * atest com.android.server.pm.BundleUtilsTest */ +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class BundleUtilsTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java index b228c839c849..54ab133d760e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java @@ -32,6 +32,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.ParsingPackageUtils; import android.os.Build; +import android.platform.test.annotations.Presubmit; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.pkg.PackageUserStateImpl; @@ -40,6 +41,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +@Presubmit public class CompatibilityModeTest { private boolean mCompatibilityModeEnabled;; diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java index e811c1f315fe..3cb5d5f92810 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java @@ -3,9 +3,10 @@ package com.android.server.pm; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; @@ -14,7 +15,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - import static org.testng.Assert.assertThrows; import android.Manifest; @@ -581,7 +581,7 @@ public class CrossProfileAppsServiceImplTest { private void mockAppsInstalled(String packageName, int user, boolean installed) { when(mPackageManagerInternal.getPackageInfo( eq(packageName), - anyInt(), + anyLong(), anyInt(), eq(user))) .thenReturn(installed ? createInstalledPackageInfo() : null); @@ -604,7 +604,7 @@ public class CrossProfileAppsServiceImplTest { mActivityInfo = activityInfo; when(mPackageManagerInternal.queryIntentActivities( - any(Intent.class), nullable(String.class), anyInt(), anyInt(), anyInt())) + any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt())) .thenReturn(Collections.singletonList(resolveInfo)); } diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java index 96318632da98..6b6d84af5f60 100644 --- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java @@ -20,6 +20,7 @@ package com.android.server.pm; import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; import android.content.pm.Signature; +import android.platform.test.annotations.Presubmit; import android.test.AndroidTestCase; import android.util.ArrayMap; import android.util.ArraySet; @@ -33,6 +34,7 @@ import java.io.IOException; import java.security.PublicKey; import java.security.cert.CertificateException; +@Presubmit public class KeySetManagerServiceTest extends AndroidTestCase { private WatchedArrayMap<String, PackageSetting> mPackagesMap; diff --git a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java index 6a9ef8a2b7bd..9ea7907ef4d0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ModuleInfoProviderTest.java @@ -21,6 +21,7 @@ import static org.mockito.MockitoAnnotations.initMocks; import android.content.Context; import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; +import android.platform.test.annotations.Presubmit; import android.test.InstrumentationTestCase; import com.android.frameworks.servicestests.R; @@ -30,6 +31,7 @@ import org.mockito.Mock; import java.util.Collections; import java.util.List; +@Presubmit public class ModuleInfoProviderTest extends InstrumentationTestCase { @Mock private ApexManager mApexManager; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java index 6a85c8b2e2ba..b81a4efaba37 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java @@ -37,6 +37,7 @@ import android.content.pm.PackageManagerInternal; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -69,15 +70,21 @@ import java.util.regex.Pattern; // atest PackageManagerServiceTest // runtest -c com.android.server.pm.PackageManagerServiceTest frameworks-services // bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest +@Postsubmit @RunWith(AndroidJUnit4.class) public class PackageManagerServiceTest { + private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; + private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/"; private static final String TEST_APP_APK = "StubTestApp.apk"; private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp"; + private IPackageManager mIPackageManager; + @Before public void setUp() throws Exception { + mIPackageManager = AppGlobals.getPackageManager(); } @After @@ -620,20 +627,19 @@ public class PackageManagerServiceTest { @Test public void testInstallReason_afterUpdate_keepUnchanged() throws Exception { - final IPackageManager pm = AppGlobals.getPackageManager(); final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); try { // Try to install test APK with reason INSTALL_REASON_POLICY runShellCommand("pm install --install-reason 1 " + testApk); assertWithMessage("The install reason of test APK is incorrect.").that( - pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( - PackageManager.INSTALL_REASON_POLICY); + mIPackageManager.getInstallReason(TEST_PKG_NAME, + UserHandle.myUserId())).isEqualTo(PackageManager.INSTALL_REASON_POLICY); // Try to update test APK with different reason INSTALL_REASON_USER runShellCommand("pm install --install-reason 4 " + testApk); assertWithMessage("The install reason should keep unchanged after update.").that( - pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( - PackageManager.INSTALL_REASON_POLICY); + mIPackageManager.getInstallReason(TEST_PKG_NAME, + UserHandle.myUserId())).isEqualTo(PackageManager.INSTALL_REASON_POLICY); } finally { runShellCommand("pm uninstall " + TEST_PKG_NAME); } @@ -642,7 +648,6 @@ public class PackageManagerServiceTest { @Test public void testInstallReason_userRemainsUninstalled_keepUnknown() throws Exception { Assume.assumeTrue(UserManager.supportsMultipleUsers()); - final IPackageManager pm = AppGlobals.getPackageManager(); final UserManager um = UserManager.get( InstrumentationRegistry.getInstrumentation().getContext()); final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); @@ -651,21 +656,21 @@ public class PackageManagerServiceTest { // Try to install test APK with reason INSTALL_REASON_POLICY runShellCommand("pm install --install-reason 1 " + testApk); assertWithMessage("The install reason of test APK is incorrect.").that( - pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( - PackageManager.INSTALL_REASON_POLICY); + mIPackageManager.getInstallReason(TEST_PKG_NAME, + UserHandle.myUserId())).isEqualTo(PackageManager.INSTALL_REASON_POLICY); // Create and start the 2nd user. userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier(); runShellCommand("am start-user -w " + userId); // Since the test APK isn't installed on the 2nd user, the reason should be unknown. assertWithMessage("The install reason in 2nd user should be unknown.").that( - pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( PackageManager.INSTALL_REASON_UNKNOWN); // Try to update test APK with different reason INSTALL_REASON_USER runShellCommand("pm install --install-reason 4 " + testApk); assertWithMessage("The install reason in 2nd user should keep unknown.").that( - pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( PackageManager.INSTALL_REASON_UNKNOWN); } finally { runShellCommand("pm uninstall " + TEST_PKG_NAME); @@ -678,7 +683,6 @@ public class PackageManagerServiceTest { @Test public void testInstallReason_installForAllUsers_sameReason() throws Exception { Assume.assumeTrue(UserManager.supportsMultipleUsers()); - final IPackageManager pm = AppGlobals.getPackageManager(); final UserManager um = UserManager.get( InstrumentationRegistry.getInstrumentation().getContext()); final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); @@ -691,8 +695,9 @@ public class PackageManagerServiceTest { // Try to install test APK to all users with reason INSTALL_REASON_POLICY runShellCommand("pm install --install-reason 1 " + testApk); assertWithMessage("The install reason is inconsistent across users.").that( - pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( - pm.getInstallReason(TEST_PKG_NAME, userId)); + mIPackageManager.getInstallReason(TEST_PKG_NAME, + UserHandle.myUserId())).isEqualTo( + mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)); } finally { runShellCommand("pm uninstall " + TEST_PKG_NAME); if (userId != UserHandle.USER_NULL) { @@ -704,7 +709,6 @@ public class PackageManagerServiceTest { @Test public void testInstallReason_installSeparately_withSeparatedReason() throws Exception { Assume.assumeTrue(UserManager.supportsMultipleUsers()); - final IPackageManager pm = AppGlobals.getPackageManager(); final UserManager um = UserManager.get( InstrumentationRegistry.getInstrumentation().getContext()); final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); @@ -717,13 +721,13 @@ public class PackageManagerServiceTest { // Try to install test APK on the current user with reason INSTALL_REASON_POLICY runShellCommand("pm install --user cur --install-reason 1 " + testApk); assertWithMessage("The install reason on the current user is incorrect.").that( - pm.getInstallReason(TEST_PKG_NAME, UserHandle.myUserId())).isEqualTo( - PackageManager.INSTALL_REASON_POLICY); + mIPackageManager.getInstallReason(TEST_PKG_NAME, + UserHandle.myUserId())).isEqualTo(PackageManager.INSTALL_REASON_POLICY); // Try to install test APK on the 2nd user with reason INSTALL_REASON_USER runShellCommand("pm install --user " + userId + " --install-reason 4 " + testApk); assertWithMessage("The install reason on the 2nd user is incorrect.").that( - pm.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( + mIPackageManager.getInstallReason(TEST_PKG_NAME, userId)).isEqualTo( PackageManager.INSTALL_REASON_USER); } finally { runShellCommand("pm uninstall " + TEST_PKG_NAME); @@ -732,4 +736,26 @@ public class PackageManagerServiceTest { } } } + + @Test + public void testSetSplashScreenTheme_samePackage_succeeds() throws Exception { + mIPackageManager.setSplashScreenTheme(PACKAGE_NAME, null /* themeName */, + UserHandle.myUserId()); + // Invoking setSplashScreenTheme on the same package shouldn't get any exception. + } + + @Test + public void testSetSplashScreenTheme_differentPackage_fails() throws Exception { + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + try { + runShellCommand("pm install " + testApk); + mIPackageManager.setSplashScreenTheme(TEST_PKG_NAME, null /* themeName */, + UserHandle.myUserId()); + fail("setSplashScreenTheme did not throw SecurityException as expected"); + } catch (SecurityException e) { + // expected + } finally { + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index ab37e9bf6deb..a9a3469d52cc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -47,6 +47,7 @@ import android.os.BaseBundle; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; @@ -91,6 +92,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class PackageManagerSettingsTests { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java index b6d4b31b2c78..7e4474fd849c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.fail; import android.content.Context; import android.content.pm.Signature; import android.content.pm.SigningDetails; +import android.platform.test.annotations.Presubmit; import android.util.TypedXmlPullParser; import android.util.Xml; @@ -44,6 +45,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +@Presubmit @RunWith(AndroidJUnit4.class) public class PackageSignaturesTest { private static final String TEST_RESOURCES_FOLDER = "PackageSignaturesTest"; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index c9f3cb248a3a..828d419c02ff 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.overlay.OverlayPaths; import android.os.PersistableBundle; +import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; @@ -41,6 +42,7 @@ import com.android.server.pm.pkg.SuspendParams; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class PackageUserStateTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java index 1fff4f084fc4..ecf7803e7a0f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java @@ -18,8 +18,10 @@ package com.android.server.pm; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.platform.test.annotations.Presubmit; import android.test.AndroidTestCase; +@Presubmit public class PackageVerificationStateTest extends AndroidTestCase { private static final int REQUIRED_UID = 1948; diff --git a/services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java b/services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java index b73c9ea949b8..e7adf7b757f1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/RestrictionsSetTest.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -37,6 +38,7 @@ import org.junit.runner.RunWith; import java.util.List; /** Test for {@link RestrictionsSet}. */ +@Presubmit @RunWith(AndroidJUnit4.class) public class RestrictionsSetTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index ec5228fec3f3..32a88bd22986 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -95,13 +95,15 @@ import android.os.Handler; import android.os.Looper; import android.os.Process; import android.os.UserHandle; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; import android.util.Log; import android.util.SparseArray; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; +import androidx.test.filters.SmallTest; + import com.android.frameworks.servicestests.R; import com.android.server.pm.ShortcutService.ConfigConstants; import com.android.server.pm.ShortcutService.FileOutputStreamWithPath; @@ -135,6 +137,7 @@ import java.util.function.BiConsumer; adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest1 \ -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @SmallTest public class ShortcutManagerTest1 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java index e92c849b50b6..57ada9b9c499 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest10.java @@ -15,10 +15,13 @@ */ package com.android.server.pm; -import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils - .assertExpectException; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.ComponentName; import android.content.Intent; import android.content.pm.LauncherActivityInfo; @@ -26,9 +29,9 @@ import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.os.Process; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; -import static org.mockito.Mockito.*; +import androidx.test.filters.SmallTest; /** * Tests for {@link ShortcutManager#createShortcutResultIntent(ShortcutInfo)} and relevant APIs. @@ -39,6 +42,7 @@ import static org.mockito.Mockito.*; adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest10 \ -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @SmallTest public class ShortcutManagerTest10 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java index c8a405284468..98fa2d68fb73 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java @@ -30,6 +30,7 @@ import android.content.pm.LauncherApps.ShortcutChangeCallback; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.ShortcutInfo; import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; import com.android.server.pm.ShortcutService.ConfigConstants; @@ -42,6 +43,7 @@ import java.util.List; * atest -c com.android.server.pm.ShortcutManagerTest11 */ +@Presubmit public class ShortcutManagerTest11 extends BaseShortcutManagerTest { private static final ShortcutQuery QUERY_MATCH_ALL = createShortcutQuery( diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java index bc2d2563b12d..bcd216dd35d4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java @@ -49,8 +49,11 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { @Override protected void tearDown() throws Exception { - setCaller(CALLING_PACKAGE_1, USER_0); - mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0).removeAllShortcutsAsync(); + if (mService.isAppSearchEnabled()) { + setCaller(CALLING_PACKAGE_1, USER_0); + mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0) + .removeAllShortcutsAsync(); + } super.tearDown(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 90a127701505..408d2c525f70 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -43,8 +43,10 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.PersistableBundle; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.test.MoreAsserts; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; import com.android.frameworks.servicestests.R; import com.android.server.pm.ShortcutUser.PackageWithUser; @@ -64,6 +66,7 @@ import java.util.Locale; adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest2 \ -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @SmallTest public class ShortcutManagerTest2 extends BaseShortcutManagerTest { // ShortcutInfo tests diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java index ba26f7930dcb..43e527c3d706 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest3.java @@ -21,7 +21,9 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import android.content.ComponentName; import android.content.pm.ShortcutInfo; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; import com.android.frameworks.servicestests.R; import com.android.server.pm.ShortcutService.ConfigConstants; @@ -31,6 +33,7 @@ import java.util.Set; /** * Tests related to shortcut rank auto-adjustment. */ +@Presubmit @SmallTest public class ShortcutManagerTest3 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java index 7546c43c24d7..11a2a8a39a46 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest4.java @@ -24,15 +24,17 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils import android.content.Intent; import android.os.Bundle; import android.os.PersistableBundle; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; import android.util.Xml; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class ShortcutManagerTest4 extends BaseShortcutManagerTest { @@ -134,4 +136,4 @@ public class ShortcutManagerTest4 extends BaseShortcutManagerTest { }); }); } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java index 203b2caff22c..400d3a891a8d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest5.java @@ -25,7 +25,9 @@ import android.content.pm.PackageInfo; import android.content.pm.ShortcutServiceInternal; import android.content.res.XmlResourceParser; import android.os.Looper; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; import com.android.server.LocalServices; @@ -38,6 +40,7 @@ import java.util.Set; * All the tests here actually talks to the real IPackageManager, so we can't test complicated * cases. Instead we just make sure they all work reasonably without at least crashing. */ +@Presubmit @SmallTest public class ShortcutManagerTest5 extends BaseShortcutManagerTest { private ShortcutService mShortcutService; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java index 63df4bc2edc4..6c10bfd274da 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest6.java @@ -15,12 +15,15 @@ */ package com.android.server.pm; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; /** * Tests for {@link ShortcutService#hasShortcutHostPermissionInner}, which includes * {@link ShortcutService#getDefaultLauncher}. */ +@Presubmit @SmallTest public class ShortcutManagerTest6 extends BaseShortcutManagerTest { public void testHasShortcutHostPermissionInner_with3pLauncher_complicated() { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java index b21b04979424..b2fd8aa7d405 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java @@ -33,7 +33,9 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; import com.android.frameworks.servicestests.R; import com.android.server.pm.ShortcutService.ConfigConstants; @@ -48,6 +50,7 @@ import java.util.concurrent.atomic.AtomicInteger; * * Launcher related commands are tested in */ +@Presubmit @SmallTest public class ShortcutManagerTest7 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java index 58e00f20374e..2293808a5d64 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java @@ -40,11 +40,13 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; import android.test.MoreAsserts; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import android.util.Pair; +import androidx.test.filters.SmallTest; + import com.android.frameworks.servicestests.R; import org.mockito.ArgumentCaptor; @@ -63,6 +65,7 @@ import org.mockito.ArgumentCaptor; * - Reading icons from requested shortcuts. * - Invalid pre-approved token. */ +@Presubmit @SmallTest public class ShortcutManagerTest8 extends BaseShortcutManagerTest { private ShortcutRequestPinProcessor mProcessor; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java index 55b4b936133d..a47a8df51c9f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java @@ -32,7 +32,9 @@ import android.content.IntentSender; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; import android.os.UserHandle; -import android.test.suitebuilder.annotation.SmallTest; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; import org.mockito.ArgumentCaptor; @@ -46,6 +48,7 @@ import org.mockito.ArgumentCaptor; adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest9 \ -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner */ +@Presubmit @SmallTest public class ShortcutManagerTest9 extends BaseShortcutManagerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java index 826a8d446e1d..4af91c6881bb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SuspendDialogInfoTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import android.content.pm.SuspendDialogInfo; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -31,6 +32,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class SuspendDialogInfoTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING new file mode 100644 index 000000000000..85a73bb22009 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING @@ -0,0 +1,41 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.pm." + }, + { + "include-annotation": "android.platform.test.annotations.Presubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ], + "postsubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.pm." + }, + { + "include-annotation": "android.platform.test.annotations.Postsubmit" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} + diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java index 7916bd37060e..a4afe09c98a4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -42,6 +43,7 @@ import java.util.concurrent.TimeUnit; * To run the test: * bit FrameworksServicesTests:com.android.server.pm.UserLifecycleStressTest */ +@Postsubmit @RunWith(AndroidJUnit4.class) @LargeTest public class UserLifecycleStressTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index 35c513f3de8e..fdf94bec8c18 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -27,6 +27,7 @@ import android.os.Looper; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -48,6 +49,7 @@ import java.util.List; * runtest -c com.android.server.pm.UserManagerServiceCreateProfileTest frameworks-services * </pre> */ +@Postsubmit @RunWith(AndroidJUnit4.class) @MediumTest public class UserManagerServiceCreateProfileTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java index b0423bfd19af..1f4c9f8cd343 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.fail; import android.app.PropertyInvalidatedCache; import android.content.pm.UserInfo; import android.os.Looper; +import android.platform.test.annotations.Postsubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -45,6 +46,7 @@ import java.util.LinkedHashSet; * -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner * </pre> */ +@Postsubmit @RunWith(AndroidJUnit4.class) @MediumTest public class UserManagerServiceIdRecyclingTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 6c1c019f504e..34b40c716b4f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -22,18 +22,20 @@ import android.os.FileUtils; import android.os.Parcelable; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import android.support.test.uiautomator.UiDevice; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; import android.text.TextUtils; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; import java.io.File; import java.io.IOException; import java.util.Arrays; +@Postsubmit @SmallTest public class UserManagerServiceTest extends AndroidTestCase { private static String[] STRING_ARRAY = new String[] {"<tag", "<![CDATA["}; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index dfc25e0c7cb6..92fddc76343d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -46,6 +46,7 @@ import android.os.Looper; import android.os.Parcel; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import android.text.TextUtils; import androidx.test.InstrumentationRegistry; @@ -69,6 +70,7 @@ import java.util.List; * runtest -c com.android.server.pm.UserManagerServiceUserInfoTest frameworks-services * </pre> */ +@Presubmit @RunWith(AndroidJUnit4.class) @MediumTest public class UserManagerServiceUserInfoTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index f1acc6679877..971b036f7d0e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -39,6 +39,7 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.os.Bundle; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import androidx.test.InstrumentationRegistry; @@ -58,6 +59,7 @@ import java.util.List; * * <p>Run with: atest UserManagerServiceUserTypeTest */ +@Presubmit @RunWith(AndroidJUnit4.class) @MediumTest public class UserManagerServiceUserTypeTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index b76c279a82bc..cf6165fdbd79 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -35,14 +35,15 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import android.provider.Settings; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Slog; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.google.common.collect.Range; @@ -63,6 +64,7 @@ import java.util.stream.Collectors; import javax.annotation.concurrent.GuardedBy; /** Test {@link UserManager} functionality. */ +@Postsubmit @RunWith(AndroidJUnit4.class) public final class UserManagerTest { // Taken from UserManagerService @@ -207,6 +209,65 @@ public final class UserManagerTest { assertThat(hasUser(user2.id)).isTrue(); } + /** + * Tests that UserManager knows how many users can be created. + * + * We can only test this with regular secondary users, since some other user types have weird + * rules about when or if they count towards the max. + */ + @MediumTest + @Test + public void testAddTooManyUsers() throws Exception { + final String userType = UserManager.USER_TYPE_FULL_SECONDARY; + final UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType); + + final int maxUsersForType = userTypeDetails.getMaxAllowed(); + final int maxUsersOverall = UserManager.getMaxSupportedUsers(); + + int currentUsersOfType = 0; + int currentUsersOverall = 0; + final List<UserInfo> userList = mUserManager.getAliveUsers(); + for (UserInfo user : userList) { + currentUsersOverall++; + if (userType.equals(user.userType)) { + currentUsersOfType++; + } + } + + final int remainingUserType = maxUsersForType == UserTypeDetails.UNLIMITED_NUMBER_OF_USERS ? + Integer.MAX_VALUE : maxUsersForType - currentUsersOfType; + final int remainingOverall = maxUsersOverall - currentUsersOverall; + final int remaining = Math.min(remainingUserType, remainingOverall); + + Slog.v(TAG, "maxUsersForType=" + maxUsersForType + + ", maxUsersOverall=" + maxUsersOverall + + ", currentUsersOfType=" + currentUsersOfType + + ", currentUsersOverall=" + currentUsersOverall + + ", remaining=" + remaining); + + assumeTrue("Device supports too many users for this test to be practical", remaining < 20); + + int usersAdded; + for (usersAdded = 0; usersAdded < remaining; usersAdded++) { + Slog.v(TAG, "Adding user " + usersAdded); + assertThat(mUserManager.canAddMoreUsers()).isTrue(); + assertThat(mUserManager.canAddMoreUsers(userType)).isTrue(); + + final UserInfo user = createUser("User " + usersAdded, userType, 0); + assertThat(user).isNotNull(); + assertThat(hasUser(user.id)).isTrue(); + } + Slog.v(TAG, "Added " + usersAdded + " users."); + + assertWithMessage("Still thinks more users of that type can be added") + .that(mUserManager.canAddMoreUsers(userType)).isFalse(); + if (currentUsersOverall + usersAdded >= maxUsersOverall) { + assertThat(mUserManager.canAddMoreUsers()).isFalse(); + } + + assertThat(createUser("User beyond", userType, 0)).isNull(); + } + @MediumTest @Test public void testRemoveUser() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java index ddf0cd0e9235..07a5303c9d65 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java @@ -22,10 +22,12 @@ import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.Presubmit; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; import android.util.SparseArray; +import androidx.test.filters.SmallTest; + /** * Tests for {@link com.android.server.pm.UserRestrictionsUtils}. * @@ -37,6 +39,7 @@ import android.util.SparseArray; -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner * </pre> */ +@Presubmit @SmallTest public class UserRestrictionsUtilsTest extends AndroidTestCase { public void testNonNull() { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java index b11bb85e8278..ba7a10350f0b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -43,6 +43,7 @@ import android.content.pm.UserInfo; import android.os.Looper; import android.os.SystemProperties; import android.os.UserManager; +import android.platform.test.annotations.Postsubmit; import android.support.test.uiautomator.UiDevice; import android.util.ArrayMap; import android.util.ArraySet; @@ -76,6 +77,7 @@ import java.util.Set; * atest com.android.server.pm.UserSystemPackageInstallerTest * </pre> */ +@Postsubmit @RunWith(AndroidJUnit4.class) @MediumTest public class UserSystemPackageInstallerTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java index b2c300255aef..95af1e1ef16a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/WatchedIntentHandlingTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import android.content.ComponentName; import android.content.IntentFilter; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -29,6 +30,7 @@ import org.junit.Test; import java.util.Iterator; +@Presubmit @SmallTest public class WatchedIntentHandlingTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java index fdb6e9f5f4ce..a16ecb1c708c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java @@ -18,6 +18,8 @@ package com.android.server.pm.dex; import static org.mockito.Mockito.inOrder; +import android.platform.test.annotations.Presubmit; + import com.android.internal.art.ArtStatsLog; import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger; @@ -44,6 +46,7 @@ import java.util.zip.ZipOutputStream; * * Run with "atest ArtStatsLogUtilsTest". */ +@Presubmit @RunWith(JUnit4.class) public final class ArtStatsLogUtilsTest { private static final String TAG = ArtStatsLogUtilsTest.class.getSimpleName(); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index 2a7a2ffa7dbf..b7b55ba16fb2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -30,6 +30,7 @@ import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.FileUtils; +import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -61,6 +62,7 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class DexMetadataHelperTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index bc84e350a329..d5893c8d0b9f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.annotations.Presubmit; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -32,6 +34,7 @@ import com.android.server.pm.PackageManagerServiceCompilerMapping; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class DexoptOptionsTests { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 34cefec4655b..1dcb0b7eb159 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.fail; import android.content.pm.SharedLibraryInfo; import android.content.pm.parsing.ParsingPackage; +import android.platform.test.annotations.Presubmit; import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -46,6 +47,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class DexoptUtilsTest { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java index 7992ba33c8da..d55f96782084 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DynamicCodeLoggerTests.java @@ -31,6 +31,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -51,6 +52,7 @@ import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; import org.mockito.stubbing.Stubber; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class DynamicCodeLoggerTests { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 3450710f60a0..c98e7c3c49e7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.os.Build; +import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -49,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class PackageDexUsageTests { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java index f4cdc8cd4d9a..e075379284f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDynamicCodeLoadingTests.java @@ -30,6 +30,8 @@ import static org.testng.Assert.assertThrows; import static java.nio.charset.StandardCharsets.UTF_8; +import android.platform.test.annotations.Presubmit; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -50,6 +52,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class PackageDynamicCodeLoadingTests { diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt index 51c268e6e53e..4059a496e8ea 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt @@ -16,17 +16,18 @@ package com.android.server.pm.parsing +import android.Manifest import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageParser import android.platform.test.annotations.Postsubmit +import com.android.internal.util.ArrayUtils import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.appInfo import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.pkgInfo import com.android.server.pm.parsing.pkg.AndroidPackage import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -91,9 +92,18 @@ class AndroidPackageInfoFlagBehaviorTest : AndroidPackageParsingTestBase() { listOf(it.configPreferences, it.reqFeatures, it.featureGroups) }, pkgInfo(PackageManager.GET_PERMISSIONS) { - listOf(it.permissions, it.requestedPermissions, it.requestedPermissionsFlags) + listOf( + it.permissions, + // Strip compatibility permission added in T + it.requestedPermissions?.filter { x -> + x != Manifest.permission.POST_NOTIFICATIONS + }?.ifEmpty { null }?.toTypedArray(), + // Strip the flag from compatibility permission added in T + it.requestedPermissionsFlags?.filterIndexed { index, _ -> + index != ArrayUtils.indexOf(it.requestedPermissions, + Manifest.permission.POST_NOTIFICATIONS) + }?.ifEmpty { null }?.toTypedArray()) }, - appInfo(PackageManager.GET_META_DATA) { listOf(it.metaData) }, appInfo(PackageManager.GET_SHARED_LIBRARY_FILES) { listOf(it.sharedLibraryFiles, it.sharedLibraryFiles) diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index df8786f26c84..122661ea93da 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -16,6 +16,7 @@ package com.android.server.pm.parsing +import android.Manifest import android.content.Context import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo @@ -34,6 +35,7 @@ import android.os.Environment import android.os.Process import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.util.ArrayUtils import com.android.server.pm.PackageManagerService import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal @@ -145,8 +147,8 @@ open class AndroidPackageParsingTestBase { flags: Int = 0, userId: Int = 0 ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId, - mockPkgSetting(pkg)) + return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, + userId, mockPkgSetting(pkg)) } fun newAppInfoWithoutState( @@ -154,8 +156,8 @@ open class AndroidPackageParsingTestBase { flags: Int = 0, userId: Int = 0 ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags, dummyUserState, userId, - mockPkgSetting(pkg)) + return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, + userId, mockPkgSetting(pkg)) } fun oldPackageInfo(pkg: PackageParser.Package, flags: Int = 0): PackageInfo? { @@ -164,7 +166,7 @@ open class AndroidPackageParsingTestBase { } fun newPackageInfo(pkg: AndroidPackage, flags: Int = 0): PackageInfo? { - return PackageInfoUtils.generate(pkg, intArrayOf(), flags, 5, 6, emptySet(), + return PackageInfoUtils.generate(pkg, intArrayOf(), flags.toLong(), 5, 6, emptySet(), dummyUserState, 0, mockPkgSetting(pkg)) } @@ -329,7 +331,10 @@ open class AndroidPackageParsingTestBase { .ignored("Update for fixing b/128526493 and the testing is no longer valid")} enabled=${this.enabled} exported=${this.exported} - flags=${Integer.toBinaryString(this.flags)} + flags=${Integer.toBinaryString( + // Strip flag added in T + this.flags and (ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES.inv())) + } icon=${this.icon} labelRes=${this.labelRes} launchMode=${this.launchMode} @@ -501,13 +506,22 @@ open class AndroidPackageParsingTestBase { receivers=${this.receivers?.joinToString { it.dumpToString() } .ignored("Checked separately in test")} reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} - requestedPermissions=${this.requestedPermissions?.contentToString()} + requestedPermissions=${ + // Strip compatibility permission added in T + this.requestedPermissions?.filter { x -> + x != Manifest.permission.POST_NOTIFICATIONS + }?.ifEmpty { null }?.joinToString() + } requestedPermissionsFlags=${ - this.requestedPermissionsFlags?.map { + // Strip the flag from compatibility permission added in T + this.requestedPermissionsFlags?.filterIndexed { index, _ -> + index != ArrayUtils.indexOf(requestedPermissions, + Manifest.permission.POST_NOTIFICATIONS) + }?.map { // Newer flags are stripped it and (PackageInfo.REQUESTED_PERMISSION_REQUIRED or PackageInfo.REQUESTED_PERMISSION_GRANTED) - }?.joinToString() + }?.ifEmpty { null }?.joinToString() } requiredAccountType=${this.requiredAccountType} requiredForAllUsers=${this.requiredForAllUsers} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index c4aa8622c4a1..f53042183af8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.pm.parsing.ParsingPackage import android.content.pm.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseResult +import android.platform.test.annotations.Presubmit import androidx.test.InstrumentationRegistry import com.android.frameworks.servicestests.R import com.google.common.truth.Truth.assertThat @@ -36,6 +37,7 @@ import org.junit.rules.TemporaryFolder * * This verifies these failures when the APK targets R. */ +@Presubmit class PackageParsingDeferErrorTest { companion object { diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java index 3261dfaa95c9..3551af83aa47 100644 --- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java @@ -31,6 +31,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Process; +import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -41,6 +42,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +@Presubmit @RunWith(AndroidJUnit4.class) public class LegacyPermissionManagerServiceTest { private static final int SYSTEM_UID = 1000; diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 117680b928f2..6ee6020c7985 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -48,13 +48,14 @@ public class ConfigurationInternalTest { .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); { ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) .build(); assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOnConfig.getGeoDetectionEnabledSetting()); @@ -79,7 +80,7 @@ public class ConfigurationInternalTest { { ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(false) + .setAutoDetectionEnabledSetting(false) .build(); assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOffConfig.getGeoDetectionEnabledSetting()); @@ -110,13 +111,14 @@ public class ConfigurationInternalTest { .setUserConfigAllowed(false) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); { ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) .build(); assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOnConfig.getGeoDetectionEnabledSetting()); @@ -142,7 +144,7 @@ public class ConfigurationInternalTest { { ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(false) + .setAutoDetectionEnabledSetting(false) .build(); assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOffConfig.getGeoDetectionEnabledSetting()); @@ -174,13 +176,14 @@ public class ConfigurationInternalTest { .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); { ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) .build(); assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOnConfig.getGeoDetectionEnabledSetting()); @@ -203,7 +206,7 @@ public class ConfigurationInternalTest { } { ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(false) + .setAutoDetectionEnabledSetting(false) .build(); assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOffConfig.getGeoDetectionEnabledSetting()); @@ -236,13 +239,14 @@ public class ConfigurationInternalTest { .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(false) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); { ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) .build(); assertTrue(autoOnConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOnConfig.getGeoDetectionEnabledSetting()); @@ -266,7 +270,7 @@ public class ConfigurationInternalTest { } { ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig) - .setAutoDetectionEnabled(false) + .setAutoDetectionEnabledSetting(false) .build(); assertFalse(autoOffConfig.getAutoDetectionEnabledSetting()); assertTrue(autoOffConfig.getGeoDetectionEnabledSetting()); @@ -288,4 +292,18 @@ public class ConfigurationInternalTest { assertTrue(configuration.isGeoDetectionEnabled()); } } + + @Test + public void test_telephonyFallbackSupported() { + ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) + .setUserConfigAllowed(true) + .setTelephonyDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) + .setTelephonyFallbackSupported(true) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) + .build(); + assertTrue(config.isTelephonyFallbackSupported()); + } } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java index 9d1c74b14525..a97ad8c57a0d 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java @@ -151,12 +151,12 @@ class FakeServiceConfigAccessor implements ServiceConfigAccessor { } @Override - public void setRecordProviderStateChanges(boolean enabled) { + public void setRecordStateChangesForTests(boolean enabled) { failUnimplemented(); } @Override - public boolean getRecordProviderStateChanges() { + public boolean getRecordStateChangesForTests() { return failUnimplemented(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index ee3195e92c6d..2d0dca25465b 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -51,6 +51,11 @@ class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override + public void enableTelephonyTimeZoneFallback() { + throw new UnsupportedOperationException(); + } + + @Override public MetricsTimeZoneDetectorState generateMetricsState() { throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java index 97b8360172f2..97095c4f675f 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestState.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Arrays; /** * A test support class used for tracking a piece of state in test objects like fakes and mocks. @@ -79,6 +80,11 @@ public class TestState<T> { assertEquals(expectedCount, getChangeCount()); } + /** Asserts the value has been {@link #set} to the expected values in the order given. */ + public void assertChanges(T... expected) { + assertEquals(Arrays.asList(expected), mValues); + } + /** * Returns the latest value passed to {@link #set}. If {@link #set} hasn't been called then the * initial value is returned. diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index ac2b27f40fba..193b2e3d0766 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -190,6 +190,9 @@ public class TimeZoneDetectorServiceTest { createTimeZoneConfiguration(true /* autoDetectionEnabled */); mTimeZoneDetectorService.updateConfiguration(autoDetectEnabledConfiguration); + // The configuration update notification is asynchronous. + mTestHandler.waitForMessagesToBeProcessed(); + verify(mMockContext).enforceCallingPermission( eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString()); @@ -369,16 +372,17 @@ public class TimeZoneDetectorServiceTest { } private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) { - // Default geo detection settings from auto detection settings - they are not important to - // the tests. + // Default geo detection settings from the auto detection setting - they are not important + // to the tests. final boolean geoDetectionEnabled = autoDetectionEnabled; return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) + .setTelephonyFallbackSupported(false) .setUserConfigAllowed(true) - .setAutoDetectionEnabled(autoDetectionEnabled) - .setLocationEnabled(geoDetectionEnabled) - .setGeoDetectionEnabled(geoDetectionEnabled) + .setAutoDetectionEnabledSetting(autoDetectionEnabled) + .setLocationEnabledSetting(geoDetectionEnabled) + .setGeoDetectionEnabledSetting(geoDetectionEnabled) .build(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index e6036c429a1f..ef1b4f58dd76 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -60,6 +60,7 @@ import java.util.function.Function; public class TimeZoneDetectorStrategyImplTest { private static final @UserIdInt int USER_ID = 9876; + private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234; /** A time zone used for initialization that does not occur elsewhere in tests. */ private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC"; private static final int SLOT_INDEX1 = 10000; @@ -89,9 +90,10 @@ public class TimeZoneDetectorStrategyImplTest { .setUserConfigAllowed(false) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(false) - .setLocationEnabled(true) - .setGeoDetectionEnabled(false) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(false) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(false) .build(); private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED = @@ -99,9 +101,10 @@ public class TimeZoneDetectorStrategyImplTest { .setUserConfigAllowed(false) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED = @@ -109,9 +112,10 @@ public class TimeZoneDetectorStrategyImplTest { .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(false) .setGeoDetectionFeatureSupported(false) - .setAutoDetectionEnabled(false) - .setLocationEnabled(true) - .setGeoDetectionEnabled(false) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(false) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(false) .build(); private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED = @@ -119,37 +123,37 @@ public class TimeZoneDetectorStrategyImplTest { .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(false) - .setLocationEnabled(true) - .setGeoDetectionEnabled(false) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(false) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(false) .build(); private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) + .setTelephonyFallbackSupported(false) .setUserConfigAllowed(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(false) .build(); private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED = new ConfigurationInternal.Builder(USER_ID) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) + .setTelephonyFallbackSupported(false) .setUserConfigAllowed(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy; private FakeEnvironment mFakeEnvironment; - // A fake source of time for suggestions. This will typically be incremented after every use. - @ElapsedRealtimeLong private long mElapsedRealtimeMillis; - @Before public void setUp() { mFakeEnvironment = new FakeEnvironment(); @@ -531,7 +535,7 @@ public class TimeZoneDetectorStrategyImplTest { boolean geoDetectionEnabled) { ConfigurationInternal geoTzEnabledConfig = new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED_GEO_DISABLED) - .setGeoDetectionEnabled(geoDetectionEnabled) + .setGeoDetectionEnabledSetting(geoDetectionEnabled) .build(); Script script = new Script() .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) @@ -753,6 +757,204 @@ public class TimeZoneDetectorStrategyImplTest { } @Test + public void testTelephonyFallback() { + ConfigurationInternal config = new ConfigurationInternal.Builder( + CONFIG_AUTO_ENABLED_GEO_ENABLED) + .setTelephonyFallbackSupported(true) + .build(); + + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .simulateConfigurationInternalChange(config) + .resetConfigurationTracking(); + + // Confirm initial state is as expected. + script.verifyTelephonyFallbackIsEnabled(true) + .verifyTimeZoneNotChanged(); + + // Although geolocation detection is enabled, telephony fallback should be used initially + // and until a suitable "certain" geolocation suggestion is received. + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Paris"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneChangedAndReset(telephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving an "uncertain" geolocation suggestion should have no effect. + { + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. + { + GeolocationTimeZoneSuggestion geolocationSuggestion = + createCertainGeolocationSuggestion("Europe/London"); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .verifyTelephonyFallbackIsEnabled(false); + } + + // Used to record the last telephony suggestion received, which will be used when fallback + // takes place. + TelephonyTimeZoneSuggestion lastTelephonySuggestion; + + // Telephony suggestions should now be ignored and geolocation detection is "in control". + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Berlin"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + lastTelephonySuggestion = telephonySuggestion; + } + + // Geolocation suggestions should continue to be used as normal (previous telephony + // suggestions are not used, even when the geolocation suggestion is uncertain). + { + GeolocationTimeZoneSuggestion geolocationSuggestion = + createCertainGeolocationSuggestion("Europe/Rome"); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .verifyTelephonyFallbackIsEnabled(false); + + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + // No change needed, device will already be set to Europe/Rome. + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + } + + // Enable telephony fallback. Nothing will change, because the geolocation is still certain, + // but fallback will remain enabled. + { + script.simulateIncrementClock() + .simulateEnableTelephonyFallback() + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Make the geolocation algorithm uncertain. + { + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Make the geolocation algorithm certain, disabling telephony fallback. + { + GeolocationTimeZoneSuggestion geolocationSuggestion = + createCertainGeolocationSuggestion("Europe/Lisbon"); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .verifyTelephonyFallbackIsEnabled(false); + + } + + // Demonstrate what happens when geolocation is uncertain when telephony fallback is + // enabled. + { + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false) + .simulateEnableTelephonyFallback() + .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + } + + @Test + public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() { + ConfigurationInternal config = new ConfigurationInternal.Builder( + CONFIG_AUTO_ENABLED_GEO_ENABLED) + .setTelephonyFallbackSupported(true) + .build(); + + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID) + .simulateConfigurationInternalChange(config) + .resetConfigurationTracking(); + + // Confirm initial state is as expected. + script.verifyTelephonyFallbackIsEnabled(true) + .verifyTimeZoneNotChanged(); + + // Receiving an "uncertain" geolocation suggestion should have no effect. + { + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back + // to + { + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Similar to the case above, but force a fallback attempt after making a "certain" + // geolocation suggestion. + // Geolocation suggestions should continue to be used as normal (previous telephony + // suggestions are not used, even when the geolocation suggestion is uncertain). + { + GeolocationTimeZoneSuggestion geolocationSuggestion = + createCertainGeolocationSuggestion("Europe/Rome"); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion) + .verifyTimeZoneChangedAndReset(geolocationSuggestion) + .verifyTelephonyFallbackIsEnabled(false); + + GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion = + createUncertainGeolocationSuggestion(); + script.simulateIncrementClock() + .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + + script.simulateIncrementClock() + .simulateEnableTelephonyFallback() + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + } + + @Test public void testGenerateMetricsState() { ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED; String expectedDeviceTimeZoneId = "InitialZoneId"; @@ -792,8 +994,8 @@ public class TimeZoneDetectorStrategyImplTest { // Update the config and confirm that the config metrics state updates also. expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig) - .setAutoDetectionEnabled(true) - .setGeoDetectionEnabled(true) + .setAutoDetectionEnabledSetting(true) + .setGeoDetectionEnabledSetting(true) .build(); expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0); @@ -835,6 +1037,8 @@ public class TimeZoneDetectorStrategyImplTest { assertEquals(config.isTelephonyDetectionSupported(), actualState.isTelephonyDetectionSupported()); assertEquals(config.isGeoDetectionSupported(), actualState.isGeoDetectionSupported()); + assertEquals(config.isTelephonyFallbackSupported(), + actualState.isTelephonyTimeZoneFallbackSupported()); assertEquals(config.getAutoDetectionEnabledSetting(), actualState.getAutoDetectionEnabledSetting()); assertEquals(config.getGeoDetectionEnabledSetting(), @@ -865,7 +1069,7 @@ public class TimeZoneDetectorStrategyImplTest { private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() { return GeolocationTimeZoneSuggestion.createCertainSuggestion( - mElapsedRealtimeMillis++, null); + mFakeEnvironment.elapsedRealtimeMillis(), null); } private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion( @@ -874,7 +1078,7 @@ public class TimeZoneDetectorStrategyImplTest { GeolocationTimeZoneSuggestion suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion( - mElapsedRealtimeMillis++, Arrays.asList(zoneIds)); + mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds)); suggestion.addDebugInfo("Test suggestion"); return suggestion; } @@ -883,16 +1087,25 @@ public class TimeZoneDetectorStrategyImplTest { private final TestState<String> mTimeZoneId = new TestState<>(); private ConfigurationInternal mConfigurationInternal; + private @ElapsedRealtimeLong long mElapsedRealtimeMillis; private ConfigurationChangeListener mConfigurationInternalChangeListener; void initializeConfig(ConfigurationInternal configurationInternal) { mConfigurationInternal = configurationInternal; } + void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) { + mElapsedRealtimeMillis = elapsedRealtimeMillis; + } + void initializeTimeZoneSetting(String zoneId) { mTimeZoneId.init(zoneId); } + void incrementClock() { + mElapsedRealtimeMillis++; + } + @Override public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) { mConfigurationInternalChangeListener = listener; @@ -936,6 +1149,12 @@ public class TimeZoneDetectorStrategyImplTest { void commitAllChanges() { mTimeZoneId.commitLatest(); } + + @Override + @ElapsedRealtimeLong + public long elapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } } /** @@ -949,6 +1168,16 @@ public class TimeZoneDetectorStrategyImplTest { return this; } + Script initializeClock(long elapsedRealtimeMillis) { + mFakeEnvironment.initializeClock(elapsedRealtimeMillis); + return this; + } + + Script simulateIncrementClock() { + mFakeEnvironment.incrementClock(); + return this; + } + /** * Simulates the user / user's configuration changing. */ @@ -963,7 +1192,7 @@ public class TimeZoneDetectorStrategyImplTest { Script simulateSetAutoMode(boolean autoDetectionEnabled) { ConfigurationInternal newConfig = new ConfigurationInternal.Builder( mFakeEnvironment.getCurrentUserConfigurationInternal()) - .setAutoDetectionEnabled(autoDetectionEnabled) + .setAutoDetectionEnabledSetting(autoDetectionEnabled) .build(); simulateConfigurationInternalChange(newConfig); return this; @@ -975,7 +1204,7 @@ public class TimeZoneDetectorStrategyImplTest { Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) { ConfigurationInternal newConfig = new ConfigurationInternal.Builder( mFakeEnvironment.getCurrentUserConfigurationInternal()) - .setGeoDetectionEnabled(geoDetectionEnabled) + .setGeoDetectionEnabledSetting(geoDetectionEnabled) .build(); simulateConfigurationInternalChange(newConfig); return this; @@ -1009,6 +1238,15 @@ public class TimeZoneDetectorStrategyImplTest { } /** + * Simulates the time zone detection strategty receiving a signal that allows it to do + * telephony fallback. + */ + Script simulateEnableTelephonyFallback() { + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + return this; + } + + /** * Confirms that the device's time zone has not been set by previous actions since the test * state was last reset. */ @@ -1044,6 +1282,13 @@ public class TimeZoneDetectorStrategyImplTest { return this; } + /** Verifies the state for telephony fallback. */ + Script verifyTelephonyFallbackIsEnabled(boolean expectedEnabled) { + assertEquals(expectedEnabled, + mTimeZoneDetectorStrategy.isTelephonyFallbackEnabledForTests()); + return this; + } + Script resetConfigurationTracking() { mFakeEnvironment.commitAllChanges(); return this; diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java index 463ac5281eb4..d54e1f1d8ec3 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java @@ -21,6 +21,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_PROVIDERS_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProviderController.STATE_UNKNOWN; import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED; import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; import static com.android.server.timezonedetector.location.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED; @@ -45,7 +53,9 @@ import android.util.IndentingPrintWriter; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.TestState; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger; import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State; import org.junit.Before; import org.junit.Test; @@ -57,9 +67,9 @@ import java.util.Collections; import java.util.List; import java.util.Objects; -/** Tests for {@link ControllerImpl}. */ +/** Tests for {@link LocationTimeZoneProviderController}. */ @Presubmit -public class ControllerImplTest { +public class LocationTimeZoneProviderControllerTest { private static final long ARBITRARY_TIME_MILLIS = 12345L; @@ -73,6 +83,7 @@ public class ControllerImplTest { TimeZoneProviderEvent.createPermanentFailureEvent(ARBITRARY_TIME_MILLIS, "Test"); private TestThreadingDomain mTestThreadingDomain; + private TestMetricsLogger mTestMetricsLogger; private TestCallback mTestCallback; private TestLocationTimeZoneProvider mTestPrimaryLocationTimeZoneProvider; private TestLocationTimeZoneProvider mTestSecondaryLocationTimeZoneProvider; @@ -82,11 +93,12 @@ public class ControllerImplTest { // For simplicity, the TestThreadingDomain uses the test's main thread. To execute posted // runnables, the test must call methods on mTestThreadingDomain otherwise those runnables // will never get a chance to execute. - LocationTimeZoneProvider.ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> { - // Stubbed. - }; mTestThreadingDomain = new TestThreadingDomain(); + mTestMetricsLogger = new TestMetricsLogger(); + mTestCallback = new TestCallback(mTestThreadingDomain); + + ProviderMetricsLogger stubbedProviderMetricsLogger = stateEnum -> {}; mTestPrimaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( stubbedProviderMetricsLogger, mTestThreadingDomain, "primary"); mTestSecondaryLocationTimeZoneProvider = new TestLocationTimeZoneProvider( @@ -94,11 +106,20 @@ public class ControllerImplTest { } @Test + public void controllerStartsInUnknownState() { + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); + assertControllerState(controller, STATE_UNKNOWN); + } + + @Test public void initializationFailure_primary() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); @@ -106,25 +127,29 @@ public class ControllerImplTest { // Initialize. After initialization the providers must be initialized and one should be // started. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void initializationFailure_secondary() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); @@ -132,387 +157,460 @@ public class ControllerImplTest { // Initialize. After initialization the providers must be initialized and one should be // started. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void initializationFailure_both() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true); mTestSecondaryLocationTimeZoneProvider.setFailDuringInitialization(true); // Initialize. After initialization the providers must be initialized and one should be // started. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_FAILED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void initialState_started() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); Duration expectedInitTimeout = testEnvironment.getProviderInitializationTimeout() .plus(testEnvironment.getProviderInitializationTimeoutFuzz()); // Initialize. After initialization the providers must be initialized and one should be // started. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestPrimaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void initialState_disabled() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); // Initialize. After initialization the providers must be initialized but neither should be // started. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertInitialized(); mTestSecondaryLocationTimeZoneProvider.assertInitialized(); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_uncertaintySuggestionSentIfNoEventReceived() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); // The primary should have reported uncertainty, which should trigger the controller to // start the uncertainty timeout and start the secondary. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate time passing with no provider event being received from either the primary or // secondary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); // Now both initialization timeouts should have triggered. The uncertainty timeout should // still not be triggered. mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Finally, the uncertainty timeout should cause the controller to make an uncertain // suggestion. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_UNCERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); mTestCallback.assertUncertainSuggestionMadeAndCommit(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_eventReceivedBeforeInitializationTimeout() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a // suggestion to be made. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_eventReceivedFromPrimaryAfterInitializationTimeout() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the primary provider. This should cause a // suggestion to be made and the secondary to be shut down. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_eventReceivedFromSecondaryAfterInitializationTimeout() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a // suggestion to be made. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_repeatedPrimaryCertainty() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a // suggestion to be made. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // A second, identical event should not cause another suggestion. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // And a third, different event should cause another suggestion. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_repeatedSecondaryCertainty() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate time passing with no provider event being received from the primary. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a // suggestion to be made. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // A second, identical event should not cause another suggestion. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // And a third, different event should cause another suggestion. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_uncertaintyTriggersASuggestionAfterUncertaintyTimeout() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a // suggestion to be made and ensure the primary is considered initialized. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event being received from the primary provider. This should not // cause a suggestion to be made straight away, but the uncertainty timeout should be @@ -520,12 +618,14 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate a location event being received from the secondary provider. This should cause a // suggestion to be made, cancel the uncertainty timeout and ensure the secondary is @@ -533,13 +633,15 @@ public class ControllerImplTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event being received from the secondary provider. This should not // cause a suggestion to be made straight away, but the uncertainty timeout should be @@ -547,65 +649,77 @@ public class ControllerImplTest { mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate time passing. This means the uncertainty timeout should fire and the uncertain // suggestion should be made. mTestThreadingDomain.executeNext(); + assertControllerState(controller, STATE_UNCERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN); mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void enabled_briefUncertaintyTriggersNoSuggestion() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a location event being received from the primary provider. This should cause a // suggestion to be made. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Uncertainty should not cause a suggestion to be made straight away, but the uncertainty // timeout should be started and the secondary should be started. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the primary provider should cause the controller to make another // suggestion, the uncertainty timeout should be cancelled and the secondary should be @@ -613,81 +727,97 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void configChanges_enableAndDisableWithNoPreviousSuggestion() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void configChanges_enableAndDisableWithPreviousSuggestion() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_DISABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_DISABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a success event being received from the primary provider. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. // Because there had been a previous suggestion, the controller should withdraw it @@ -695,27 +825,33 @@ public class ControllerImplTest { // of the time zone. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void configChanges_userSwitch_enabledToEnabled() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate the primary provider suggesting a time zone. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( @@ -723,18 +859,20 @@ public class ControllerImplTest { // Receiving a "success" provider event should cause a suggestion to be made synchronously, // and also clear the scheduled uncertainty suggestion. + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate the user change (but geo detection still enabled). testEnvironment.simulateConfigChange(USER2_CONFIG_GEO_DETECTION_ENABLED); // Confirm that the previous suggestion was overridden. - mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertControllerState(controller, STATE_INITIALIZING); // We expect the provider to end up in PROVIDER_STATE_STARTED_INITIALIZING, but it should // have been stopped when the user changed. @@ -744,129 +882,158 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig( PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING); + mTestCallback.assertUncertainSuggestionMadeAndCommit(); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void primaryPermFailure_secondaryEventsReceived() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure location event being received from the primary provider. This should // cause the secondary to be started. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate uncertainty from the secondary. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the secondary provider should cause the controller to make // another suggestion, the uncertainty timeout should be cancelled. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate uncertainty from the secondary. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); } @Test public void primaryPermFailure_disableAndEnable() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure location event being received from the primary provider. This should // cause the secondary to be started. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void secondaryPermFailure_primaryEventsReceived() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event from the primary. This will start the secondary, which will // give this test the opportunity to simulate its failure. Then it will be possible to @@ -874,61 +1041,73 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate failure event from the secondary. This should just affect the secondary's state. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // And a success event from the primary provider should cause the controller to make // a suggestion, the uncertainty timeout should be cancelled. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate uncertainty from the primary. The secondary cannot be started. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); } @Test public void secondaryPermFailure_disableAndEnable() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate an uncertain event from the primary. This will start the secondary, which will // give this test the opportunity to simulate its failure. Then it will be possible to @@ -936,97 +1115,117 @@ public class ControllerImplTest { mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Simulate failure event from the secondary. This should just affect the secondary's state. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertUncertaintyTimeoutSet(testEnvironment, controllerImpl); + assertUncertaintyTimeoutSet(testEnvironment, controller); // Now signal a config change so that geo detection is disabled. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_DISABLED); + assertControllerState(controller, STATE_STOPPED); mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Now signal a config change so that geo detection is enabled. Only the primary can be // started. testEnvironment.simulateConfigChange(USER1_CONFIG_GEO_DETECTION_ENABLED); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void bothPermFailure_disableAndEnable() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate a failure event from the primary. This will start the secondary. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestMetricsLogger.assertStateChangesAndCommit(); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate failure event from the secondary. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT); + assertControllerState(controller, STATE_FAILED); mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED); mTestCallback.assertUncertainSuggestionMadeAndCommit(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } @Test public void stateRecording() { // The test provider enables state recording by default. - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, true /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial states. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); { - LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_INITIALIZING, state.getControllerState()); assertNull(state.getLastSuggestion()); + assertControllerRecordedStates(state, + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); assertProviderStates(state.getPrimaryProviderStates(), PROVIDER_STATE_STOPPED, PROVIDER_STATE_STARTED_INITIALIZING); assertProviderStates(state.getSecondaryProviderStates(), PROVIDER_STATE_STOPPED); } - controllerImpl.clearRecordedProviderStates(); + controller.clearRecordedStates(); // Simulate some provider behavior that will show up in the state recording. @@ -1035,33 +1234,39 @@ public class ControllerImplTest { USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT); { - LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_INITIALIZING, state.getControllerState()); assertNull(state.getLastSuggestion()); + assertControllerRecordedStates(state); assertProviderStates( state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN); assertProviderStates( state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_INITIALIZING); } - controllerImpl.clearRecordedProviderStates(); + controller.clearRecordedStates(); // Simulate a certain event from the secondary. mTestSecondaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); { - LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), state.getLastSuggestion().getZoneIds()); + assertControllerRecordedStates(state, STATE_CERTAIN); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates( state.getSecondaryProviderStates(), PROVIDER_STATE_STARTED_CERTAIN); } - controllerImpl.clearRecordedProviderStates(); + controller.clearRecordedStates(); { - LocationTimeZoneManagerServiceState state = controllerImpl.getStateForTests(); + LocationTimeZoneManagerServiceState state = controller.getStateForTests(); + assertEquals(STATE_CERTAIN, state.getControllerState()); assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(), state.getLastSuggestion().getZoneIds()); + assertControllerRecordedStates(state); assertProviderStates(state.getPrimaryProviderStates()); assertProviderStates(state.getSecondaryProviderStates()); } @@ -1078,19 +1283,23 @@ public class ControllerImplTest { @Test public void destroy() { - ControllerImpl controllerImpl = new ControllerImpl(mTestThreadingDomain, - mTestPrimaryLocationTimeZoneProvider, mTestSecondaryLocationTimeZoneProvider); + LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController( + mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider, + mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */); TestEnvironment testEnvironment = new TestEnvironment( - mTestThreadingDomain, controllerImpl, USER1_CONFIG_GEO_DETECTION_ENABLED); + mTestThreadingDomain, controller, USER1_CONFIG_GEO_DETECTION_ENABLED); // Initialize and check initial state. - controllerImpl.initialize(testEnvironment, mTestCallback); + controller.initialize(testEnvironment, mTestCallback); + assertControllerState(controller, STATE_INITIALIZING); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING); mTestCallback.assertNoSuggestionMade(); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Simulate the primary provider suggesting a time zone. mTestPrimaryLocationTimeZoneProvider.simulateTimeZoneProviderEvent( @@ -1098,15 +1307,21 @@ public class ControllerImplTest { // Receiving a "success" provider event should cause a suggestion to be made synchronously, // and also clear the scheduled uncertainty suggestion. + assertControllerState(controller, STATE_CERTAIN); mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit( PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED); mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit(); + mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN); mTestCallback.assertCertainSuggestionMadeFromEventAndCommit( USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); // Trigger destroy(). - controllerImpl.destroy(); + controller.destroy(); + + assertControllerState(controller, STATE_DESTROYED); + mTestMetricsLogger.assertStateChangesAndCommit( + STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED); // Confirm that the previous suggestion was overridden. mTestCallback.assertUncertainSuggestionMadeAndCommit(); @@ -1115,7 +1330,7 @@ public class ControllerImplTest { PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED); mTestSecondaryLocationTimeZoneProvider.assertStateChangesAndCommit( PROVIDER_STATE_DESTROYED); - assertFalse(controllerImpl.isUncertaintyTimeoutSet()); + assertFalse(controller.isUncertaintyTimeoutSet()); } private static void assertUncertaintyTimeoutSet( @@ -1135,6 +1350,17 @@ public class ControllerImplTest { .build()); } + private static void assertControllerState(LocationTimeZoneProviderController controller, + @State String expectedState) { + assertEquals(expectedState, controller.getStateForTests().getControllerState()); + } + + private static void assertControllerRecordedStates( + LocationTimeZoneManagerServiceState state, + @State String... expectedStates) { + assertEquals(Arrays.asList(expectedStates), state.getControllerStates()); + } + private static class TestEnvironment extends LocationTimeZoneProviderController.Environment { // These timeouts are set deliberately so that: @@ -1206,6 +1432,22 @@ public class ControllerImplTest { } } + private static class TestMetricsLogger + implements LocationTimeZoneProviderController.MetricsLogger { + + private final TestState<@State String> mLatestStateEnum = new TestState<>(); + + @Override + public void onStateChange(@State String stateEnum) { + mLatestStateEnum.set(stateEnum); + } + + public void assertStateChangesAndCommit(@State String... expectedStateEnums) { + mLatestStateEnum.assertChanges(expectedStateEnums); + mLatestStateEnum.commitLatest(); + } + } + private static class TestCallback extends LocationTimeZoneProviderController.Callback { private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>(); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index 16ac1d602de5..a2df31305c2f 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -41,14 +41,15 @@ final class TestSupport { } private static ConfigurationInternal createUserConfig( - @UserIdInt int userId, boolean geoDetectionEnabled) { + @UserIdInt int userId, boolean geoDetectionEnabledSetting) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(true) .setTelephonyDetectionFeatureSupported(true) .setGeoDetectionFeatureSupported(true) - .setAutoDetectionEnabled(true) - .setLocationEnabled(true) - .setGeoDetectionEnabled(geoDetectionEnabled) + .setTelephonyFallbackSupported(false) + .setAutoDetectionEnabledSetting(true) + .setLocationEnabledSetting(true) + .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting) .build(); } } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java index 37165075e1ba..7eb6c9789fe4 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java @@ -16,7 +16,7 @@ package com.android.server.uri; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -121,47 +121,47 @@ public class UriGrantsMockContext extends ContextWrapper { LocalServices.addService(PackageManagerInternal.class, mPmInternal); for (int userId : new int[] { USER_PRIMARY, USER_SECONDARY }) { - when(mPmInternal.getPackageUid(eq(PKG_SOCIAL), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_SOCIAL), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_SOCIAL)); - when(mPmInternal.getPackageUid(eq(PKG_CAMERA), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_CAMERA), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_CAMERA)); - when(mPmInternal.getPackageUid(eq(PKG_PRIVATE), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_PRIVATE), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_PRIVATE)); - when(mPmInternal.getPackageUid(eq(PKG_PUBLIC), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_PUBLIC), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_PUBLIC)); - when(mPmInternal.getPackageUid(eq(PKG_FORCE), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_FORCE), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_FORCE)); - when(mPmInternal.getPackageUid(eq(PKG_COMPLEX), anyInt(), eq(userId))) + when(mPmInternal.getPackageUid(eq(PKG_COMPLEX), anyLong(), eq(userId))) .thenReturn(UserHandle.getUid(userId, UID_COMPLEX)); - when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyLong(), eq(userId), eq(Process.SYSTEM_UID))) .thenReturn(buildCameraProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_CAMERA), anyLong(), eq(userId), eq(UserHandle.getUid(userId, UID_CAMERA)))) .thenReturn(buildCameraProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyLong(), eq(userId), eq(Process.SYSTEM_UID))) .thenReturn(buildPrivateProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_PRIVATE), anyLong(), eq(userId), eq(UserHandle.getUid(userId, UID_PRIVATE)))) .thenReturn(buildPrivateProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyLong(), eq(userId), eq(Process.SYSTEM_UID))) .thenReturn(buildPublicProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_PUBLIC), anyLong(), eq(userId), eq(UserHandle.getUid(userId, UID_PUBLIC)))) .thenReturn(buildPublicProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_FORCE), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_FORCE), anyLong(), eq(userId), eq(Process.SYSTEM_UID))) .thenReturn(buildForceProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_FORCE), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_FORCE), anyLong(), eq(userId), eq(UserHandle.getUid(userId, UID_FORCE)))) .thenReturn(buildForceProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyLong(), eq(userId), eq(Process.SYSTEM_UID))) .thenReturn(buildComplexProvider(userId)); - when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyInt(), eq(userId), + when(mPmInternal.resolveContentProvider(eq(PKG_COMPLEX), anyLong(), eq(userId), eq(UserHandle.getUid(userId, UID_COMPLEX)))) .thenReturn(buildComplexProvider(userId)); } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 9e46e1f2be92..949ee01d6872 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -63,6 +63,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -530,8 +531,8 @@ public class AppStandbyControllerTests { eq(UserHandle.getAppId(ai.uid)), eq(userIdForTest), anyLong())) .thenReturn(idle[i]); } - when(mInjector.mPackageManagerInternal.getInstalledApplications(anyInt(), eq(userIdForTest), - anyInt())).thenReturn(installedApps); + when(mInjector.mPackageManagerInternal.getInstalledApplications(anyLong(), + eq(userIdForTest), anyInt())).thenReturn(installedApps); final int[] returnedIdleUids = controllerUnderTest.getIdleUidsForUser(userIdForTest); assertEquals(expectedIdleUids.length, returnedIdleUids.length); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java index 7d24a2f5845e..beee2a7520ec 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -16,6 +16,19 @@ package com.android.server.vibrator; +import static android.os.VibrationAttributes.USAGE_ALARM; +import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST; +import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK; +import static android.os.VibrationAttributes.USAGE_NOTIFICATION; +import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION; +import static android.os.VibrationAttributes.USAGE_RINGTONE; +import static android.os.VibrationAttributes.USAGE_TOUCH; +import static android.os.VibrationAttributes.USAGE_UNKNOWN; +import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH; +import static android.os.Vibrator.VIBRATION_INTENSITY_LOW; +import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM; +import static android.os.Vibrator.VIBRATION_INTENSITY_OFF; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -39,9 +52,7 @@ import android.os.Handler; import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.UserHandle; -import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.Vibrator; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -70,16 +81,19 @@ import org.mockito.junit.MockitoRule; public class VibrationSettingsTest { private static final int UID = 1; - private static final int USER_OPERATION_TIMEOUT_MILLIS = 60_000; // 1 min private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() .setBatterySaverEnabled(true).build(); - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock; - @Mock private PowerManagerInternal mPowerManagerInternalMock; + @Mock + private VibrationSettings.OnVibratorSettingsChanged mListenerMock; + @Mock + private PowerManagerInternal mPowerManagerInternalMock; private TestLooper mTestLooper; private ContextWrapper mContextSpy; @@ -112,7 +126,7 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); } @@ -127,16 +141,14 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); - verify(mListenerMock, times(7)).onChange(); + verify(mListenerMock, times(8)).onChange(); } @Test @@ -171,89 +183,83 @@ public class VibrationSettingsTest { VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper())); - assertFalse(vibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_RINGTONE)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK)); } @Test public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() { - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK)); } @Test public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() { - int usageRingtone = VibrationAttributes.USAGE_RINGTONE; setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); setRingerMode(AudioManager.RINGER_MODE_VIBRATE); - assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_SILENT); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_MAX); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone)); + assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE)); } @Test public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() { - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH)); mVibrationSettings.mUidObserver.onUidStateChanged( UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); - assertFalse(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH)); + assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH)); } @Test @@ -261,38 +267,32 @@ public class VibrationSettingsTest { mVibrationSettings.mUidObserver.onUidStateChanged( UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, - VibrationAttributes.USAGE_NOTIFICATION)); - assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE)); } @Test public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() { mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_ALARM)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_RINGTONE)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST)); } @Test public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() { mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH)); - assertTrue(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_NOTIFICATION)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH)); + assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION)); mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertFalse(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH)); - assertFalse(mVibrationSettings.shouldVibrateForPowerMode( - VibrationAttributes.USAGE_NOTIFICATION)); + assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH)); + assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION)); } @Test @@ -324,108 +324,128 @@ public class VibrationSettingsTest { @Test public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper())); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + assertEquals(VIBRATION_INTENSITY_HIGH, + vibrationSettings.getDefaultIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + vibrationSettings.getDefaultIntensity(USAGE_RINGTONE)); } @Test public void getDefaultIntensity_returnsIntensityFromVibratorService() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getDefaultIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW); + + setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE)); } @Test public void getCurrentIntensity_returnsIntensityFromSettings() { - mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH); + setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW); setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM)); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN)); - assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM, - mVibrationSettings.getCurrentIntensity( - VibrationAttributes.USAGE_PHYSICAL_EMULATION)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW); + + assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM)); + assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); } @Test public void getCurrentIntensity_updateTriggeredAfterUserSwitched() { - mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF); + setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); + + // Switching user is not working with FakeSettingsProvider. + // Testing the broadcast flow manually. + Settings.System.putIntForUser(mContextSpy.getContentResolver(), + Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, + UserHandle.USER_CURRENT); + mVibrationSettings.mUserReceiver.onReceive(mContextSpy, + new Intent(Intent.ACTION_USER_SWITCHED)); + assertEquals(VIBRATION_INTENSITY_LOW, + mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); + } + + @Test + public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() { + mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM); + setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF); + assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + // If haptic feedback is off, fallback to default value. + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_MEDIUM, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); // Switching user is not working with FakeSettingsProvider. // Testing the broadcast flow manually. Settings.System.putIntForUser(mContextSpy.getContentResolver(), - Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW, + Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH, UserHandle.USER_CURRENT); mVibrationSettings.mUserReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_USER_SWITCHED)); - assertEquals(Vibrator.VIBRATION_INTENSITY_LOW, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_TOUCH)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK)); + assertEquals(VIBRATION_INTENSITY_HIGH, + mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index be83efb2c928..c0f75966bcf2 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -126,15 +126,23 @@ public class VibratorManagerServiceTest { new VibrationAttributes.Builder().setUsage( VibrationAttributes.USAGE_RINGTONE).build(); - @Rule public MockitoRule rule = MockitoJUnit.rule(); - @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - - @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock; - @Mock private PackageManagerInternal mPackageManagerInternalMock; - @Mock private PowerManagerInternal mPowerManagerInternalMock; - @Mock private PowerSaveState mPowerSaveStateMock; - @Mock private AppOpsManager mAppOpsManagerMock; - @Mock private IInputManager mIInputManagerMock; + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + + @Mock + private VibratorManagerService.NativeWrapper mNativeWrapperMock; + @Mock + private PackageManagerInternal mPackageManagerInternalMock; + @Mock + private PowerManagerInternal mPowerManagerInternalMock; + @Mock + private PowerSaveState mPowerSaveStateMock; + @Mock + private AppOpsManager mAppOpsManagerMock; + @Mock + private IInputManager mIInputManagerMock; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); @@ -397,6 +405,7 @@ public class VibratorManagerServiceTest { @Test public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception { mockVibrators(0, 1, 2); + mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK); VibratorManagerService service = createSystemReadyService(); IVibratorStateListener[] listeners = new IVibratorStateListener[3]; for (int i = 0; i < 3; i++) { @@ -536,7 +545,7 @@ public class VibratorManagerServiceTest { setRingerMode(AudioManager.RINGER_MODE_NORMAL); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); VibratorManagerService service = createSystemReadyService(); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); // Wait before checking it never played. @@ -544,14 +553,14 @@ public class VibratorManagerServiceTest { service, /* timeout= */ 50)); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); service = createSystemReadyService(); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS); assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, service, TEST_TIMEOUT_MILLIS)); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); service = createSystemReadyService(); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS); assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, @@ -601,8 +610,8 @@ public class VibratorManagerServiceTest { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build(); - VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder( - audioAttributes, effect).build(); + VibrationAttributes vibrationAttributes = + new VibrationAttributes.Builder(audioAttributes).build(); vibrate(service, effect, vibrationAttributes); @@ -621,7 +630,7 @@ public class VibratorManagerServiceTest { vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), new VibrationAttributes.Builder().setUsage( VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build()); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), + vibrate(service, VibrationEffect.createOneShot(2000, 200), new VibrationAttributes.Builder().setUsage( VibrationAttributes.USAGE_UNKNOWN).build()); @@ -635,13 +644,67 @@ public class VibratorManagerServiceTest { inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString()); inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST), + eq(AudioAttributes.USAGE_VOICE_COMMUNICATION), anyInt(), anyString()); inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); } @Test + public void vibrate_withAttributesUnknownUsage_usesEffectToIdentifyTouchUsage() { + VibratorManagerService service = createSystemReadyService(); + + VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage( + VibrationAttributes.USAGE_UNKNOWN); + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), unknownAttributes); + vibrate(service, VibrationEffect.createOneShot(200, 200), unknownAttributes); + vibrate(service, VibrationEffect.createWaveform( + new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, -1), unknownAttributes); + vibrate(service, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL) + .compose(), + unknownAttributes); + + verify(mAppOpsManagerMock, times(4)) + .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), + eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString()); + verify(mAppOpsManagerMock, never()) + .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), + eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); + } + + @Test + public void vibrate_withAttributesUnknownUsage_ignoresEffectIfNotHapticFeedbackCandidate() { + VibratorManagerService service = createSystemReadyService(); + + VibrationAttributes unknownAttributes = VibrationAttributes.createForUsage( + VibrationAttributes.USAGE_UNKNOWN); + vibrate(service, VibrationEffect.get(VibrationEffect.RINGTONES[0]), unknownAttributes); + vibrate(service, VibrationEffect.createOneShot(2000, 200), unknownAttributes); + vibrate(service, VibrationEffect.createWaveform( + new long[] { 100, 200, 300 }, new int[] {1, 2, 3}, 0), unknownAttributes); + vibrate(service, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .compose(), + unknownAttributes); + + verify(mAppOpsManagerMock, never()) + .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), + eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString()); + verify(mAppOpsManagerMock, times(4)) + .checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), + eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); + } + + @Test public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); @@ -1109,19 +1172,19 @@ public class VibratorManagerServiceTest { setRingerMode(AudioManager.RINGER_MODE_NORMAL); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); createSystemReadyService(); int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_MUTE, scale); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); createSystemReadyService(); scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); + setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); createSystemReadyService(); scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_NONE, scale); diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml index fdaf7ccc5f0d..1bc47759d078 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml @@ -18,6 +18,7 @@ package="com.android.servicestests.apps.simpleservicetestapp"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application> <service android:name=".SimpleService" diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java index ae46f52ff70c..8270583df0f0 100644 --- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java +++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java @@ -17,8 +17,10 @@ package com.android.servicestests.apps.simpleservicetestapp; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; @@ -33,6 +35,9 @@ public class SimpleService extends Service { private static final String TEST_CLASS = "com.android.servicestests.apps.simpleservicetestapp.SimpleService"; + private static final String ACTION_SERVICE_WITH_DEP_PKG = + "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG"; + private static final String EXTRA_CALLBACK = "callback"; private static final String EXTRA_COMMAND = "command"; private static final String EXTRA_FLAGS = "flags"; @@ -121,6 +126,21 @@ public class SimpleService extends Service { @Override public IBinder onBind(Intent intent) { + if (ACTION_SERVICE_WITH_DEP_PKG.equals(intent.getAction())) { + final String targetPkg = intent.getStringExtra(EXTRA_TARGET_PACKAGE); + Log.i(TAG, "SimpleService.onBind: " + ACTION_SERVICE_WITH_DEP_PKG + " " + targetPkg); + if (targetPkg != null) { + Context pkgContext = null; + try { + pkgContext = createPackageContext(targetPkg, + Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to create package context for " + pkgContext, e); + } + // This effectively loads the target package as a dependency. + pkgContext.getClassLoader(); + } + } return mBinder; } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index cdb7230a66cb..2f054b004d42 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -26,6 +26,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -809,7 +810,7 @@ public class ManagedServicesTest extends UiServiceTestCase { service.isComponentEnabledForCurrentProfiles( unapprovedAdditionalComponent)); verify(mIpm, never()).getServiceInfo( - eq(unapprovedAdditionalComponent), anyInt(), anyInt()); + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); } } @@ -953,7 +954,7 @@ public class ManagedServicesTest extends UiServiceTestCase { service.isComponentEnabledForCurrentProfiles( unapprovedAdditionalComponent)); verify(mIpm, never()).getServiceInfo( - eq(unapprovedAdditionalComponent), anyInt(), anyInt()); + eq(unapprovedAdditionalComponent), anyLong(), anyInt()); } } @@ -1702,14 +1703,14 @@ public class ManagedServicesTest extends UiServiceTestCase { assertTrue(service.isComponentEnabledForCurrentProfiles( componentName)); verify(mIpm, times(1)).getServiceInfo( - eq(componentName), anyInt(), anyInt()); + eq(componentName), anyLong(), anyInt()); } } else { ComponentName componentName = ComponentName.unflattenFromString(packageOrComponent); assertTrue(service.isComponentEnabledForCurrentProfiles(componentName)); verify(mIpm, times(1)).getServiceInfo( - eq(componentName), anyInt(), anyInt()); + eq(componentName), anyLong(), anyInt()); } } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ea3a4cd865cb..837850fc1011 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -99,6 +99,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; @@ -119,6 +122,7 @@ import android.app.RemoteInput; import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; +import android.companion.AssociationInfo; import android.companion.ICompanionDeviceManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -393,7 +397,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // MockPackageManager - default returns ApplicationInfo with matching calling UID mContext.setMockPackageManager(mPackageManagerClient); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())) + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())) .thenAnswer((Answer<ApplicationInfo>) invocation -> { Object[] args = invocation.getArguments(); return getApplicationInfo((String) args[0], mUid); @@ -2335,10 +2339,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateChannelNotifyListener() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -2364,10 +2366,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testCreateChannelGroupNotifyListener() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b"); NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m"); @@ -2385,10 +2385,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateChannelNotifyListener() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); mTestNotificationChannel.setLightColor(Color.CYAN); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), @@ -2404,10 +2402,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testDeleteChannelNotifyListener() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -2423,10 +2419,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) @@ -2439,10 +2433,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testDeleteChannelGroupNotifyListener() throws Exception { - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) @@ -2457,10 +2449,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean())) .thenReturn(mTestNotificationChannel); @@ -2479,9 +2469,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(emptyList()); try { mBinderService.updateNotificationChannelFromPrivilegedListener( @@ -2502,10 +2491,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mListener = mock(ManagedServices.ManagedServiceInfo.class); mListener.component = new ComponentName(PKG, PKG); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); @@ -2530,10 +2517,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mBinderService.getNotificationChannelsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -2545,9 +2530,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(emptyList()); try { mBinderService.getNotificationChannelsFromPrivilegedListener( @@ -2566,7 +2550,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(new ArrayList<>()); + .thenReturn(emptyList()); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); mBinderService.getNotificationChannelsFromPrivilegedListener( @@ -2581,7 +2565,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(new ArrayList<>()); + .thenReturn(emptyList()); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false); try { @@ -2599,10 +2583,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -2622,10 +2604,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); - associations.add("a"); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(singletonList(mock(AssociationInfo.class))); mBinderService.getNotificationChannelGroupsFromPrivilegedListener( null, PKG, Process.myUserHandle()); @@ -2636,9 +2616,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(emptyList()); try { mBinderService.getNotificationChannelGroupsFromPrivilegedListener( @@ -2654,9 +2633,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); - List<String> associations = new ArrayList<>(); when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) - .thenReturn(associations); + .thenReturn(emptyList()); mListener = mock(ManagedServices.ManagedServiceInfo.class); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -5065,7 +5043,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testIsCallerInstantApp_primaryUser() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(0))).thenReturn(info); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{"any"}); assertTrue(mService.isCallerInstantApp(45770, 0)); @@ -5078,8 +5056,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testIsCallerInstantApp_secondaryUser() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(10))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(0))).thenReturn(null); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{"any"}); assertTrue(mService.isCallerInstantApp(68638450, 10)); @@ -5089,7 +5067,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testIsCallerInstantApp_userAllNotification() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(USER_SYSTEM))) + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(USER_SYSTEM))) .thenReturn(info); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{"any"}); @@ -5103,8 +5081,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testResolveNotificationUid_sameApp_nonSystemUser() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = Binder.getCallingUid(); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(10))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(0))).thenReturn(null); int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 10); @@ -5115,7 +5093,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testResolveNotificationUid_sameApp() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = Binder.getCallingUid(); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(0))).thenReturn(info); int actualUid = mService.resolveNotificationUid("caller", "caller", info.uid, 0); @@ -5126,7 +5104,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testResolveNotificationUid_sameAppDiffPackage() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = Binder.getCallingUid(); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), eq(0))).thenReturn(info); int actualUid = mService.resolveNotificationUid("caller", "callerAlso", info.uid, 0); @@ -5137,7 +5115,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testResolveNotificationUid_sameAppWrongUid() throws Exception { ApplicationInfo info = new ApplicationInfo(); info.uid = 1356347; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(info); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())).thenReturn(info); try { mService.resolveNotificationUid("caller", "caller", 9, 0); @@ -5176,7 +5154,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { PackageManager.NameNotFoundException.class); ApplicationInfo ai = new ApplicationInfo(); ai.uid = -1; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())).thenReturn(ai); final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); try { @@ -5196,7 +5174,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { PackageManager.NameNotFoundException.class); ApplicationInfo ai = new ApplicationInfo(); ai.uid = -1; - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())).thenReturn(ai); + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())).thenReturn(ai); // unlike the post case, ignore instead of throwing final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); @@ -7326,12 +7304,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Make sure the shortcut is cached. verify(mShortcutServiceInternal).cacheShortcuts( - anyInt(), any(), eq(PKG), eq(Collections.singletonList(VALID_CONVO_SHORTCUT_ID)), + anyInt(), any(), eq(PKG), eq(singletonList(VALID_CONVO_SHORTCUT_ID)), eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); // Test: Remove the shortcut when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null); - launcherAppsCallback.getValue().onShortcutsChanged(PKG, Collections.emptyList(), + launcherAppsCallback.getValue().onShortcutsChanged(PKG, emptyList(), UserHandle.getUserHandleForUid(mUid)); waitForIdle(); @@ -7399,7 +7377,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Make sure the shortcut is cached. verify(mShortcutServiceInternal).cacheShortcuts( - anyInt(), any(), eq(PKG), eq(Collections.singletonList(shortcutId)), + anyInt(), any(), eq(PKG), eq(singletonList(shortcutId)), eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)); // Test: Remove the notification diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index 29ef339a6382..2e5cf3c2d98f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -44,6 +44,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; @@ -307,7 +308,7 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { // MockPackageManager - default returns ApplicationInfo with matching calling UID mContext.setMockPackageManager(mPackageManagerClient); - when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())) + when(mPackageManager.getApplicationInfo(anyString(), anyLong(), anyInt())) .thenAnswer((Answer<ApplicationInfo>) invocation -> { Object[] args = invocation.getArguments(); return getApplicationInfo((String) args[0], mUid); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index 4cdae8860409..5800400e3745 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -28,6 +28,7 @@ import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -42,7 +43,6 @@ import android.content.pm.ParceledListSlice; import android.permission.IPermissionManager; import android.test.suitebuilder.annotation.SmallTest; import android.util.Pair; -import android.util.Slog; import androidx.test.runner.AndroidJUnit4; @@ -169,7 +169,8 @@ public class PermissionHelperTest extends UiServiceTestCase { ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>( ImmutableList.of(notThis, none, first, second)); - when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos); + when(mPackageManager.getInstalledPackages(eq((long) GET_PERMISSIONS), anyInt())) + .thenReturn(infos); Set<Pair<Integer, String>> actual = mPermissionHelper.getAppsRequestingPermission(0); @@ -181,7 +182,7 @@ public class PermissionHelperTest extends UiServiceTestCase { int userId = 1; ParceledListSlice<PackageInfo> infos = ParceledListSlice.emptyList(); when(mPackageManager.getPackagesHoldingPermissions( - eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId))) + eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyLong(), eq(userId))) .thenReturn(infos); assertThat(mPermissionHelper.getAppsGrantedPermission(userId)).isNotNull(); } @@ -206,7 +207,7 @@ public class PermissionHelperTest extends UiServiceTestCase { ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>( ImmutableList.of(first, second)); when(mPackageManager.getPackagesHoldingPermissions( - eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId))) + eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyLong(), eq(userId))) .thenReturn(infos); Set<Pair<Integer, String>> expected = @@ -305,11 +306,11 @@ public class PermissionHelperTest extends UiServiceTestCase { ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>( ImmutableList.of(first, second)); when(mPackageManager.getPackagesHoldingPermissions( - eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId))) + eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyLong(), eq(userId))) .thenReturn(infos); ParceledListSlice<PackageInfo> requesting = new ParceledListSlice<>( ImmutableList.of(first, second, third)); - when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())) + when(mPackageManager.getInstalledPackages(eq((long) GET_PERMISSIONS), anyInt())) .thenReturn(requesting); Map<Pair<Integer, String>, Boolean> expected = ImmutableMap.of(new Pair(1, "first"), true, 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 ae24785caa3a..7a133ea2a300 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1778,11 +1778,6 @@ public class ActivityRecordTests extends WindowTestsBase { anyInt() /* orientation */, anyInt() /* lastRotation */); // Set to visible so the activity can freeze the screen. activity.setVisibility(true); - // Update the display policy to make the screen fully turned on so the freeze is allowed - display.getDisplayPolicy().screenTurnedOn(null); - display.getDisplayPolicy().finishKeyguardDrawn(); - display.getDisplayPolicy().finishWindowsDrawn(); - display.getDisplayPolicy().finishScreenTurningOn(); display.rotateInDifferentOrientationIfNeeded(activity); display.setFixedRotationLaunchingAppUnchecked(activity); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 0cab9119a5f7..e2f0658f3da3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -63,6 +63,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; @@ -352,7 +353,7 @@ public class ActivityStarterTests extends WindowTestsBase { doReturn(null).when(mMockPackageManager).getDefaultHomeActivity(anyInt()); doReturn(mMockPackageManager).when(mAtm).getPackageManagerInternalLocked(); doReturn(false).when(mMockPackageManager).isInstantAppInstallerComponent(any()); - doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyInt(), anyInt(), + doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt()); doReturn(new ComponentName("", "")).when(mMockPackageManager).getSystemUiServiceComponent(); 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 6fa306b004a2..506270657e42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -334,6 +334,18 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testExitAnimationDone_beforeAppTransition() { + final Task task = createTask(mDisplayContent); + final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win"); + spyOn(win); + win.mAnimatingExit = true; + mDisplayContent.mAppTransition.setTimeout(); + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + verify(win).onExitAnimationDone(); + } + + @Test public void testGetAnimationTargets_windowsAreBeingReplaced() { // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible) // +- [AppWindow1] (being-replaced) 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 08be15e16b37..d7a0ab3afbc2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -681,7 +681,7 @@ public class DisplayContentTests extends WindowTestsBase { final int maxWidth = 300; final int resultingHeight = (maxWidth * baseHeight) / baseWidth; - final int resultingDensity = baseDensity; + final int resultingDensity = (baseDensity * maxWidth) / baseWidth; displayContent.setMaxUiWidth(maxWidth); verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity); @@ -756,6 +756,33 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testSetForcedDensity() { + final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo(); + final int baseWidth = 1280; + final int baseHeight = 720; + final int baseDensity = 320; + + displayContent.mInitialDisplayWidth = baseWidth; + displayContent.mInitialDisplayHeight = baseHeight; + displayContent.mInitialDisplayDensity = baseDensity; + displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); + + final int forcedDensity = 600; + + // Verify that forcing the density is honored and the size doesn't change. + displayContent.setForcedDensity(forcedDensity, 0 /* userId */); + verifySizes(displayContent, baseWidth, baseHeight, forcedDensity); + + // Verify that forcing the density is idempotent. + displayContent.setForcedDensity(forcedDensity, 0 /* userId */); + verifySizes(displayContent, baseWidth, baseHeight, forcedDensity); + + // Verify that forcing resolution won't affect the already forced density. + displayContent.setForcedSize(1800, 1200); + verifySizes(displayContent, 1800, 1200, forcedDensity); + } + + @Test public void testDisplayCutout_rot0() { final DisplayContent dc = createNewDisplay(); dc.mInitialDisplayWidth = 200; diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index a8ede13e5de6..407f9cfdbe3e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -29,7 +30,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; @@ -44,6 +47,7 @@ import android.view.WindowManagerGlobal; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.inputmethod.InputMethodMenuController; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -62,18 +66,24 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { private InputMethodMenuController mController; private DualDisplayAreaGroupPolicyTest.DualDisplayContent mSecondaryDisplay; + private IWindowManager mIWindowManager; + private DisplayManagerGlobal mDisplayManagerGlobal; + @Before public void setUp() throws Exception { - // Let the Display to be created with the DualDisplay policy. + // Let the Display be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); mController = new InputMethodMenuController(mock(InputMethodManagerService.class)); + mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent + .Builder(mAtm, 1000, 1000).build(); + mSecondaryDisplay.getDisplayInfo().state = STATE_ON; // Mock addWindowTokenWithOptions to create a test window token. - IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); - spyOn(wms); + mIWindowManager = WindowManagerGlobal.getWindowManagerService(); + spyOn(mIWindowManager); doAnswer(invocation -> { Object[] args = invocation.getArguments(); IBinder clientToken = (IBinder) args[0]; @@ -83,19 +93,24 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG, null /* options */); return dc.getImeContainer().getConfiguration(); - }).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG), - anyInt(), any()); - - mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent - .Builder(mAtm, 1000, 1000).build(); - - // Mock DisplayManagerGlobal to return test display when obtaining Display instance. + }).when(mIWindowManager).attachWindowContextToDisplayArea(any(), + eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any()); + mDisplayManagerGlobal = DisplayManagerGlobal.getInstance(); + spyOn(mDisplayManagerGlobal); final int displayId = mSecondaryDisplay.getDisplayId(); final Display display = mSecondaryDisplay.getDisplay(); - DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance(); - spyOn(displayManagerGlobal); - doReturn(display).when(displayManagerGlobal).getCompatibleDisplay(eq(displayId), + doReturn(display).when(mDisplayManagerGlobal).getCompatibleDisplay(eq(displayId), (Resources) any()); + Context systemUiContext = ActivityThread.currentActivityThread() + .getSystemUiContext(displayId); + spyOn(systemUiContext); + doReturn(display).when(systemUiContext).getDisplay(); + } + + @After + public void tearDown() { + reset(mIWindowManager); + reset(mDisplayManagerGlobal); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 6737b1ade785..730275cde40b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -16,11 +16,14 @@ package com.android.server.wm; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; @@ -91,6 +94,7 @@ public class TaskFragmentTest extends WindowTestsBase { final Rect endBounds = new Rect(500, 500, 1000, 1000); mTaskFragment.setBounds(startBounds); doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); clearInvocations(mTransaction); mTaskFragment.setBounds(endBounds); @@ -108,6 +112,25 @@ public class TaskFragmentTest extends WindowTestsBase { verify(mTransaction).setWindowCrop(mLeash, 500, 500); } + @Test + public void testNotOkToAnimate_doNotStartChangeTransition() { + mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer); + final Rect startBounds = new Rect(0, 0, 1000, 1000); + final Rect endBounds = new Rect(500, 500, 1000, 1000); + mTaskFragment.setBounds(startBounds); + doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); + + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + displayPolicy.screenTurnedOff(); + + assertFalse(mTaskFragment.okToAnimate()); + + mTaskFragment.setBounds(endBounds); + + verify(mTaskFragment, never()).initializeChangeTransition(any()); + } + /** * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an * activity that has not yet been attached to a process because it is being initialized but diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index c0ae8a5ac5f4..3065e7dab296 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -168,6 +168,11 @@ class TestDisplayContent extends DisplayContent { doReturn(false).when(displayPolicy).hasStatusBar(); doReturn(false).when(newDisplay).supportsSystemDecorations(); } + // Update the display policy to make the screen fully turned on so animation is allowed + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); if (mStatusBarHeight > 0) { doReturn(true).when(displayPolicy).hasStatusBar(); doAnswer(invocation -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index f366f57bae08..caaf4e495e63 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -54,7 +54,6 @@ import android.view.RoundedCorners; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; -import android.window.ITransitionPlayer; import androidx.test.filters.SmallTest; @@ -313,11 +312,7 @@ public class WallpaperControllerTests extends WindowTestsBase { wallpaperWindow.setHasSurface(true); // Set-up mock shell transitions - final IBinder mockBinder = mock(IBinder.class); - final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class); - doReturn(mockBinder).when(mockPlayer).asBinder(); - mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer, - null /* appThread */); + registerTestTransitionPlayer(); Transition transit = mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN); @@ -338,10 +333,21 @@ public class WallpaperControllerTests extends WindowTestsBase { assertFalse(token.isVisibleRequested()); assertTrue(token.isVisible()); - transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class)); - transit.finishTransition(); + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + token.finishSync(t, false /* cancel */); + transit.onTransactionReady(transit.getSyncId(), t); + dc.mTransitionController.finishTransition(transit); assertFalse(wallpaperWindow.isVisible()); assertFalse(token.isVisible()); + + // Assume wallpaper was visible. When transaction is ready without wallpaper target, + // wallpaper should be requested to be invisible. + token.setVisibility(true); + transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE); + dc.mTransitionController.collect(token); + transit.onTransactionReady(transit.getSyncId(), t); + assertFalse(token.isVisibleRequested()); + assertTrue(token.isVisible()); } private WindowState createWallpaperTargetWindow(DisplayContent dc) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index e5eba57f223d..646647fcc4ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -17,22 +17,35 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.window.WindowProvider.KEY_IS_WINDOW_PROVIDER_SERVICE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.app.IWindowToken; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.DisplayInfo; import androidx.test.filters.SmallTest; @@ -55,12 +68,15 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { private static final int ANOTHER_UID = 1000; private final IBinder mClientToken = new Binder(); - private WindowContainer mContainer; + private WindowContainer<?> mContainer; @Before public void setUp() { mController = new WindowContextListenerController(); mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); + // Make display on to verify configuration propagation. + mDefaultDisplay.getDisplayInfo().state = STATE_ON; + mDisplayContent.getDisplayInfo().state = STATE_ON; } @Test @@ -76,7 +92,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(2, mController.mListeners.size()); - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); mController.registerWindowContainerListener(mClientToken, container, -1, TYPE_APPLICATION_OVERLAY, null /* options */); @@ -89,6 +105,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(container, listener.getWindowContainer()); } + @UseTestDisplay @Test public void testRegisterWindowContextListenerClientConfigPropagation() { final TestWindowTokenClient clientToken = new TestWindowTokenClient(); @@ -107,7 +124,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(mDisplayContent.mDisplayId, clientToken.mDisplayId); // Update the WindowContainer. - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); final Configuration config2 = container.getConfiguration(); final Rect bounds2 = new Rect(0, 0, 20, 20); @@ -174,7 +191,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { .setDisplayContent(mDefaultDisplay) .setFromClientToken(true) .build(); - final DisplayArea da = windowContextCreatedToken.getDisplayArea(); + final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea(); mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken, TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */); @@ -192,11 +209,12 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { // Let the Display to be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); - Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); + doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Create a DisplayContent with dual RootDisplayArea DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent = new DualDisplayAreaGroupPolicyTest.DualDisplayContent .Builder(mAtm, 1000, 1000).build(); + dualDisplayContent.getDisplayInfo().state = STATE_ON; final DisplayArea.Tokens imeContainer = dualDisplayContent.getImeContainer(); // Put the ImeContainer to the first sub-RootDisplayArea dualDisplayContent.mFirstRoot.placeImeContainer(imeContainer); @@ -222,7 +240,62 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertThat(mController.getContainer(mClientToken)).isEqualTo(imeContainer); } - private class TestWindowTokenClient extends IWindowToken.Stub { + @Test + public void testConfigUpdateForSuspendedWindowContext() { + final TestWindowTokenClient mockToken = new TestWindowTokenClient(); + spyOn(mockToken); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(mockToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, null /* options */); + + verify(mockToken, never()).onConfigurationChanged(any(), anyInt()); + + // Turn on the display and verify if the client receive the callback + Display display = mContainer.getDisplayContent().getDisplay(); + spyOn(display); + Mockito.doAnswer(invocation -> { + final DisplayInfo info = mContainer.getDisplayContent().getDisplayInfo(); + info.state = STATE_ON; + ((DisplayInfo) invocation.getArgument(0)).copyFrom(info); + return null; + }).when(display).getDisplayInfo(any(DisplayInfo.class)); + + mContainer.getDisplayContent().onDisplayChanged(); + + assertThat(mockToken.mConfiguration).isEqualTo(config1); + assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId()); + } + + @Test + public void testReportConfigUpdateForSuspendedWindowProviderService() { + final TestWindowTokenClient clientToken = new TestWindowTokenClient(); + final Bundle options = new Bundle(); + options.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(clientToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, options); + + assertThat(clientToken.mConfiguration).isEqualTo(config1); + assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId); + } + + private static class TestWindowTokenClient extends IWindowToken.Stub { private Configuration mConfiguration; private int mDisplayId; private boolean mRemoved; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 996b4b22dde7..92fd68296006 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -198,6 +198,13 @@ class WindowTestsBase extends SystemServiceTestsBase { SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); + // Update the display policy to make the screen fully turned on so animation is allowed + final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy(); + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); + mTransaction = mSystemServicesTestRule.mTransaction; mMockSession = mock(Session.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 722aee7a8f6e..e5dc5576277e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -500,8 +500,6 @@ public class ZOrderingTests extends WindowTestsBase { mDisplayContent.assignChildLayers(mTransaction); assertWindowHigher(splitWindow1, belowTaskWindow); - assertWindowHigher(splitWindow1, belowTaskWindow); - assertWindowHigher(splitWindow2, belowTaskWindow); assertWindowHigher(splitWindow2, belowTaskWindow); assertWindowHigher(mDockedDividerWindow, splitWindow1); assertWindowHigher(mDockedDividerWindow, splitWindow2); @@ -509,6 +507,39 @@ public class ZOrderingTests extends WindowTestsBase { assertWindowHigher(pinnedWindow, aboveTaskWindow); } + + @Test + public void testDockedDividerPosition_noAboveTask() { + final Task pinnedTask = + createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); + final WindowState pinnedWindow = + createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow"); + + final Task belowTask = + createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final WindowState belowTaskWindow = + createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow"); + + final Task splitScreenTask1 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow1 = + createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1"); + final Task splitScreenTask2 = + createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); + final WindowState splitWindow2 = + createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2"); + splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2); + splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1); + + mDisplayContent.assignChildLayers(mTransaction); + + assertWindowHigher(splitWindow1, belowTaskWindow); + assertWindowHigher(splitWindow2, belowTaskWindow); + assertWindowHigher(mDockedDividerWindow, splitWindow1); + assertWindowHigher(mDockedDividerWindow, splitWindow2); + assertWindowHigher(pinnedWindow, mDockedDividerWindow); + } + @Test public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() { // create RecentsAnimationController diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index f4f06fd81c58..96c78bc2d0ef 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -191,7 +191,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mContext.registerReceiver(mBroadcastReceiver, filter, null, handler, - Context.RECEIVER_NOT_EXPORTED); + Context.RECEIVER_EXPORTED); } public boolean showSessionLocked(Bundle args, int flags, diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index 7d857a2dc1b4..fabe612743bb 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -856,9 +856,10 @@ public abstract class EuiccService extends Service { mExecutor.execute(new Runnable() { @Override public void run() { + // TODO(b/207392528: use portIndex API once implemented) int result = - EuiccService.this.onSwitchToSubscriptionWithPort( - slotId, portIndex, iccid, forceDeactivateSim); + EuiccService.this.onSwitchToSubscription( + slotId, iccid, forceDeactivateSim); try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 23cf5116b2da..e88106cb95fe 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -1,6 +1,8 @@ package android.telephony; import android.annotation.IntDef; +import android.net.NetworkAgent; +import android.net.NetworkCapabilities; import android.telecom.Connection; import android.telephony.data.ApnSetting; @@ -664,4 +666,59 @@ public class Annotation { TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE, TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR}) public @interface ThermalMitigationResult {} + + /** + * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate + * android.net.NetworkCapabilities.NetCapability here. Must update here when new capabilities + * are added in {@link NetworkCapabilities}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "NET_CAPABILITY_" }, value = { + NetworkCapabilities.NET_CAPABILITY_MMS, + NetworkCapabilities.NET_CAPABILITY_SUPL, + NetworkCapabilities.NET_CAPABILITY_DUN, + NetworkCapabilities.NET_CAPABILITY_FOTA, + NetworkCapabilities.NET_CAPABILITY_IMS, + NetworkCapabilities.NET_CAPABILITY_CBS, + NetworkCapabilities.NET_CAPABILITY_WIFI_P2P, + NetworkCapabilities.NET_CAPABILITY_IA, + NetworkCapabilities.NET_CAPABILITY_RCS, + NetworkCapabilities.NET_CAPABILITY_XCAP, + NetworkCapabilities.NET_CAPABILITY_EIMS, + NetworkCapabilities.NET_CAPABILITY_NOT_METERED, + NetworkCapabilities.NET_CAPABILITY_INTERNET, + NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED, + NetworkCapabilities.NET_CAPABILITY_TRUSTED, + NetworkCapabilities.NET_CAPABILITY_NOT_VPN, + NetworkCapabilities.NET_CAPABILITY_VALIDATED, + NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL, + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, + NetworkCapabilities.NET_CAPABILITY_FOREGROUND, + NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED, + NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED, + NetworkCapabilities.NET_CAPABILITY_OEM_PAID, + NetworkCapabilities.NET_CAPABILITY_MCX, + NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE, + NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL, + NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED, + NetworkCapabilities.NET_CAPABILITY_ENTERPRISE, + NetworkCapabilities.NET_CAPABILITY_VSIM, + NetworkCapabilities.NET_CAPABILITY_BIP, + NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT, + }) + public @interface NetCapability { } + + /** + * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate + * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status + * are added in {@link NetworkAgent}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "VALIDATION_STATUS_" }, value = { + NetworkAgent.VALIDATION_STATUS_VALID, + NetworkAgent.VALIDATION_STATUS_NOT_VALID + }) + public @interface ValidationStatus {} } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3f0f50c5f62a..6f92c31bac78 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -5116,13 +5116,11 @@ public class CarrierConfigManager { public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array"; /** - * Network capability priority for determine the satisfy order in telephony. This is used when - * the network only allows single PDN. The priority is from the lowest 0 to the highest 100. - * The long-lived network request usually has the lowest priority. This allows other short-lived - * requests like MMS requests to be established. Emergency request always has the highest - * priority. + * Network capability priority for determine the satisfy order in telephony. The priority is + * from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority. + * This allows other short-lived requests like MMS requests to be established. Emergency request + * always has the highest priority. * - * // TODO: Remove KEY_APN_PRIORITY_STRING_ARRAY * @hide */ public static final String KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY = @@ -5132,17 +5130,17 @@ public class CarrierConfigManager { * Defines the rules for data retry. * * The syntax of the retry rule: - * 1. Retry based on {@link NetworkCapabilities} - * "capabilities=[netCaps1|netCaps2|...], [retry_interval=x], [backoff=[true|false]], - * [maximum_retries=y]" + * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities + * are supported. + * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]" * * 2. Retry based on {@link DataFailCause} - * "fail_causes=[cause1|cause2|cause3|...], [retry_interval=x], [backoff=[true|false]], - * [maximum_retries=y]" + * "fail_causes=[cause1|cause2|cause3|..], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]" * - * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause} + * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause}. Note that only + * APN-type network capabilities are supported. * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...], - * [retry_interval=x], [backoff=[true|false]], [maximum_retries=y]" + * [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]" * * For example, * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached @@ -5151,7 +5149,12 @@ public class CarrierConfigManager { * * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254 * , maximum_retries=0" means for those fail causes, never retry with timers. Note that - * when environment changes, retry can still happens. + * when environment changes, retry can still happen. + * + * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" + * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000" + * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s, + * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries. * * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS * @hide @@ -5941,6 +5944,9 @@ public class CarrierConfigManager { "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2", "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3" }); + + // Do not modify the priority unless you know what you are doing. This will have significant + // impacts on the order of data network setup. sDefaults.putStringArray( KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] { "eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50", @@ -5951,9 +5957,10 @@ public class CarrierConfigManager { "capabilities=eims, retry_interval=1000, maximum_retries=20", "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|" + "2254, maximum_retries=0", // No retry for those causes - "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, " - + "backoff=true, maximum_retries=13", - "capabilities=mms|supl|cbs, retry_interval=2000" + "capabilities=mms|supl|cbs, retry_interval=2000", + "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" + + "5000|10000|15000|20000|40000|60000|120000|240000|" + + "600000|1200000|1800000, maximum_retries=20" }); sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index 88efe1f6a4a7..56bf3039d209 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1076,6 +1076,13 @@ public final class DataFailCause { */ public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009; + /** + * The request is not supported by the vendor. + * + * @hide + */ + public static final int REQUEST_NOT_SUPPORTED = 0x1000A; + private static final Map<Integer, String> sFailCauseMap; static { sFailCauseMap = new HashMap<>(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 88b21e0428b0..ae2facda70cb 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3465,15 +3465,39 @@ public class TelephonyManager { * @see #SIM_STATE_PRESENT * * @hide + * @deprecated instead use {@link #getSimCardState(int, int)} */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @Deprecated public @SimState int getSimCardState(int physicalSlotIndex) { - int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex)); + int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex, DEFAULT_PORT_INDEX)); return getSimCardStateFromSimState(simState); } /** + * Returns a constant indicating the state of the device SIM card in a physical slot and + * port index. + * + * @param physicalSlotIndex physical slot index + * @param portIndex The port index is an enumeration of the ports available on the UICC. + * Use {@link UiccPortInfo#getPortIndex()} to get portIndex. + * + * @see #SIM_STATE_UNKNOWN + * @see #SIM_STATE_ABSENT + * @see #SIM_STATE_CARD_IO_ERROR + * @see #SIM_STATE_CARD_RESTRICTED + * @see #SIM_STATE_PRESENT + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimState int getSimCardState(int physicalSlotIndex, int portIndex) { + int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex, portIndex)); + return getSimCardStateFromSimState(simState); + } + /** * Converts SIM state to SIM card state. * @param simState * @return SIM card state @@ -3493,13 +3517,19 @@ public class TelephonyManager { /** * Converts a physical slot index to logical slot index. * @param physicalSlotIndex physical slot index + * @param portIndex The port index is an enumeration of the ports available on the UICC. + * Use {@link UiccPortInfo#getPortIndex()} to get portIndex. * @return logical slot index */ - private int getLogicalSlotIndex(int physicalSlotIndex) { + private int getLogicalSlotIndex(int physicalSlotIndex, int portIndex) { UiccSlotInfo[] slotInfos = getUiccSlotsInfo(); if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length && slotInfos[physicalSlotIndex] != null) { - return slotInfos[physicalSlotIndex].getLogicalSlotIdx(); + for (UiccPortInfo portInfo : slotInfos[physicalSlotIndex].getPorts()) { + if (portInfo.getPortIndex() == portIndex) { + return portInfo.getLogicalSlotIndex(); + } + } } return SubscriptionManager.INVALID_SIM_SLOT_INDEX; @@ -3539,12 +3569,42 @@ public class TelephonyManager { * @see #SIM_STATE_LOADED * * @hide + * @deprecated instead use {@link #getSimApplicationState(int, int)} */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @Deprecated public @SimState int getSimApplicationState(int physicalSlotIndex) { int simState = - SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex)); + SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex, + DEFAULT_PORT_INDEX)); + return getSimApplicationStateFromSimState(simState); + } + + /** + * Returns a constant indicating the state of the card applications on the device SIM card in + * a physical slot. + * + * @param physicalSlotIndex physical slot index + * @param portIndex The port index is an enumeration of the ports available on the UICC. + * Use {@link UiccPortInfo#getPortIndex()} to get portIndex. + * + * @see #SIM_STATE_UNKNOWN + * @see #SIM_STATE_PIN_REQUIRED + * @see #SIM_STATE_PUK_REQUIRED + * @see #SIM_STATE_NETWORK_LOCKED + * @see #SIM_STATE_NOT_READY + * @see #SIM_STATE_PERM_DISABLED + * @see #SIM_STATE_LOADED + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @SimState int getSimApplicationState(int physicalSlotIndex, int portIndex) { + int simState = + SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex, + portIndex)); return getSimApplicationStateFromSimState(simState); } @@ -4143,18 +4203,21 @@ public class TelephonyManager { * should be {@link #getPhoneCount()} if success, otherwise return an empty map. * * @hide + * @deprecated use {@link #getSimSlotMapping()} instead. */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @NonNull + @Deprecated public Map<Integer, Integer> getLogicalToPhysicalSlotMapping() { Map<Integer, Integer> slotMapping = new HashMap<>(); try { ITelephony telephony = getITelephony(); if (telephony != null) { - int[] slotMappingArray = telephony.getSlotsMapping(mContext.getOpPackageName()); - for (int i = 0; i < slotMappingArray.length; i++) { - slotMapping.put(i, slotMappingArray[i]); + List<UiccSlotMapping> simSlotsMapping = telephony.getSlotsMapping( + mContext.getOpPackageName()); + for (UiccSlotMapping slotMap : simSlotsMapping) { + slotMapping.put(slotMap.getLogicalSlotIndex(), slotMap.getPhysicalSlotIndex()); } } } catch (RemoteException e) { @@ -4163,6 +4226,33 @@ public class TelephonyManager { return slotMapping; } + /** + * Get the mapping from logical slots to physical sim slots and port indexes. Initially the + * logical slot index was mapped to physical slot index, but with support for multi-enabled + * profile(MEP) logical slot is now mapped to port index. + * + * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical + * slots to ports and physical slots. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @NonNull + public Collection<UiccSlotMapping> getSimSlotMapping() { + List<UiccSlotMapping> slotMap = new ArrayList<>(); + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + slotMap = telephony.getSlotsMapping(mContext.getOpPackageName()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + return slotMap; + } // // // Subscriber Info diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java index 7dfe450fdb1a..74f9c876cc40 100644 --- a/telephony/java/android/telephony/UiccCardInfo.java +++ b/telephony/java/android/telephony/UiccCardInfo.java @@ -156,9 +156,11 @@ public final class UiccCardInfo implements Parcelable { @Nullable @Deprecated public String getIccId() { - if (mIccIdAccessRestricted) { - throw new UnsupportedOperationException("getIccId from UiccPortInfo"); - } + // Temporarily bypassing exception + // TODO: add exception once refactoring completed. + //if (mIccIdAccessRestricted) { + // throw new UnsupportedOperationException("getIccId from UiccPortInfo"); + //} //always return ICCID from first port. return getPorts().stream().findFirst().get().getIccId(); } @@ -258,7 +260,7 @@ public final class UiccCardInfo implements Parcelable { + ", mEid=" + mEid + ", mIccId=" - + SubscriptionInfo.givePrintableIccid(mIccId) + + SubscriptionInfo.givePrintableIccid(getIccId()) + ", mPhysicalSlotIndex=" + mPhysicalSlotIndex + ", mIsRemovable=" diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 2b1c8c863eb6..a8668e7d77d4 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -159,9 +159,11 @@ public class UiccSlotInfo implements Parcelable { */ @Deprecated public boolean getIsActive() { - if (mLogicalSlotAccessRestricted) { - throw new UnsupportedOperationException("get port status from UiccPortInfo"); - } + // Temporarily bypassing exception + // TODO: add exception once refactoring completed. + //if (mLogicalSlotAccessRestricted) { + // throw new UnsupportedOperationException("get port status from UiccPortInfo"); + //} //always return status from first port. return getPorts().stream().findFirst().get().isActive(); } @@ -196,9 +198,11 @@ public class UiccSlotInfo implements Parcelable { */ @Deprecated public int getLogicalSlotIdx() { - if (mLogicalSlotAccessRestricted) { - throw new UnsupportedOperationException("get logical slot index from UiccPortInfo"); - } + // Temporarily bypassing exception + // TODO: add exception once refactoring completed. + //if (mLogicalSlotAccessRestricted) { + // throw new UnsupportedOperationException("get logical slot index from UiccPortInfo"); + //} //always return logical slot index from first port. //portList always have at least one element. return getPorts().stream().findFirst().get().getLogicalSlotIndex(); diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index 93903d2658cd..c1d16a9aaea7 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -18,13 +18,16 @@ package android.telephony.data; import static android.telephony.data.ApnSetting.ProtocolType; +import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.NetworkCapabilities; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Annotation.ApnType; +import android.telephony.Annotation.NetCapability; import android.telephony.TelephonyManager; import android.telephony.TelephonyManager.NetworkTypeBitMask; import android.telephony.data.ApnSetting.AuthType; @@ -66,7 +69,13 @@ public final class DataProfile implements Parcelable { private final @Nullable TrafficDescriptor mTrafficDescriptor; - private final boolean mPreferred; + private boolean mPreferred; + + /** + * The last timestamp of this data profile being used for data network setup. Never add this + * to {@link #equals(Object)} and {@link #hashCode()}. + */ + private @ElapsedRealtimeLong long mSetupTimestamp; private DataProfile(@NonNull Builder builder) { mApnSetting = builder.mApnSetting; @@ -99,6 +108,7 @@ public final class DataProfile implements Parcelable { mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader()); mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader()); mPreferred = source.readBoolean(); + mSetupTimestamp = source.readLong(); } /** @@ -291,6 +301,16 @@ public final class DataProfile implements Parcelable { } /** + * Set the preferred flag for the data profile. + * + * @param preferred {@code true} if this data profile is preferred for internet. + * @hide + */ + public void setPreferred(boolean preferred) { + mPreferred = preferred; + } + + /** * @return {@code true} if this data profile was used to bring up the last default * (i.e internet) data connection successfully, or the one chosen by the user in Settings' * APN editor. For one carrier there can be only one profiled preferred. @@ -315,6 +335,94 @@ public final class DataProfile implements Parcelable { return mTrafficDescriptor; } + /** + * Check if this data profile can satisfy certain network capabilities + * + * @param networkCapabilities The network capabilities. Note that the non-APN-type capabilities + * will be ignored. + * + * @return {@code true} if this data profile can satisfy the given network capabilities. + * @hide + */ + public boolean canSatisfy(@NonNull @NetCapability int[] networkCapabilities) { + if (mApnSetting != null) { + for (int netCap : networkCapabilities) { + if (!canSatisfy(netCap)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Check if this data profile can satisfy a certain network capability. + * + * @param networkCapability The network capability. Note that the non-APN-type capability + * will always be satisfied. + * @return {@code true} if this data profile can satisfy the given network capability. + * @hide + */ + public boolean canSatisfy(@NetCapability int networkCapability) { + return mApnSetting != null && mApnSetting.canHandleType( + networkCapabilityToApnType(networkCapability)); + } + + /** + * Convert network capability into APN type. + * + * @param networkCapability Network capability. + * @return APN type. + * @hide + */ + private static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) { + switch (networkCapability) { + case NetworkCapabilities.NET_CAPABILITY_MMS: + return ApnSetting.TYPE_MMS; + case NetworkCapabilities.NET_CAPABILITY_SUPL: + return ApnSetting.TYPE_SUPL; + case NetworkCapabilities.NET_CAPABILITY_DUN: + return ApnSetting.TYPE_DUN; + case NetworkCapabilities.NET_CAPABILITY_FOTA: + return ApnSetting.TYPE_FOTA; + case NetworkCapabilities.NET_CAPABILITY_IMS: + return ApnSetting.TYPE_IMS; + case NetworkCapabilities.NET_CAPABILITY_CBS: + return ApnSetting.TYPE_CBS; + case NetworkCapabilities.NET_CAPABILITY_XCAP: + return ApnSetting.TYPE_XCAP; + case NetworkCapabilities.NET_CAPABILITY_EIMS: + return ApnSetting.TYPE_EMERGENCY; + case NetworkCapabilities.NET_CAPABILITY_INTERNET: + return ApnSetting.TYPE_DEFAULT; + case NetworkCapabilities.NET_CAPABILITY_MCX: + return ApnSetting.TYPE_MCX; + case NetworkCapabilities.NET_CAPABILITY_IA: + return ApnSetting.TYPE_IA; + default: + return ApnSetting.TYPE_NONE; + } + } + + /** + * Set the timestamp of this data profile being used for data network setup. + * + * @hide + */ + public void setLastSetupTimestamp(@ElapsedRealtimeLong long timestamp) { + mSetupTimestamp = timestamp; + } + + /** + * @return the timestamp of this data profile being used for data network setup. + * + * @hide + */ + public @ElapsedRealtimeLong long getLastSetupTimestamp() { + return mSetupTimestamp; + } + @Override public int describeContents() { return 0; @@ -323,8 +431,8 @@ public final class DataProfile implements Parcelable { @NonNull @Override public String toString() { - return "DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred=" - + mPreferred; + return "[DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred=" + + mPreferred + "]"; } @Override @@ -333,6 +441,7 @@ public final class DataProfile implements Parcelable { dest.writeParcelable(mApnSetting, flags); dest.writeParcelable(mTrafficDescriptor, flags); dest.writeBoolean(mPreferred); + dest.writeLong(mSetupTimestamp); } public static final @android.annotation.NonNull Parcelable.Creator<DataProfile> CREATOR = diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index dbc6cb6da33b..6d094cbc4188 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2134,9 +2134,9 @@ interface ITelephony { String callingFeatureId); /** - * Get the mapping from logical slots to physical slots. + * Get the mapping from logical slots to port index. */ - int[] getSlotsMapping(String callingPackage); + List<UiccSlotMapping> getSlotsMapping(String callingPackage); /** * Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 0bb61987a514..22320fd53631 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -27,9 +27,8 @@ package { java_sdk_library { name: "android.test.mock", - - srcs: [ - ":android-test-mock-sources", + srcs: [":android-test-mock-sources"], + api_srcs: [ // Note: Below are NOT APIs of this library. We only take APIs under // the android.test.mock package. They however provide private APIs that // android.test.mock APIs references to. We need to have the classes in @@ -44,15 +43,9 @@ java_sdk_library { "app-compat-annotations", "unsupportedappusage", ], - api_packages: [ "android.test.mock", ], - // Only include android.test.mock.* classes. Jarjar rules below removes - // classes in other packages like android.content. In order to keep the - // list up-to-date, permitted_packages ensures that the library contains - // clases under android.test.mock after the jarjar rules are applied. - jarjar_rules: "jarjar-rules.txt", permitted_packages: [ "android.test.mock", ], diff --git a/test-mock/jarjar-rules.txt b/test-mock/jarjar-rules.txt deleted file mode 100644 index 4420a4413f5b..000000000000 --- a/test-mock/jarjar-rules.txt +++ /dev/null @@ -1,7 +0,0 @@ -zap android.accounts.** -zap android.app.** -zap android.content.** -zap android.database.** -zap android.os.** -zap android.util.** -zap android.view.** diff --git a/tests/AttestationVerificationTest/Android.bp b/tests/AttestationVerificationTest/Android.bp new file mode 100644 index 000000000000..a4741eedaac0 --- /dev/null +++ b/tests/AttestationVerificationTest/Android.bp @@ -0,0 +1,44 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "AttestationVerificationTest", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + defaults: ["cts_defaults"], + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + platform_apis: true, + certificate: "platform", + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "compatibility-device-util-axt", + "androidx.test.rules", + "androidx.test.ext.junit", + "platform-test-annotations", + ], +} diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml new file mode 100755 index 000000000000..c42bde9dca3a --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.security.attestationverification"> + + <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" /> + <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" /> + + <application> + <uses-library android:name="android.test.runner"/> + <activity android:name=".SystemAttestationVerificationTest$TestActivity" /> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.attestationverification"> + </instrumentation> +</manifest> diff --git a/tests/AttestationVerificationTest/AndroidTest.xml b/tests/AttestationVerificationTest/AndroidTest.xml new file mode 100644 index 000000000000..132576035952 --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Platform tests for Attestation Verification Framework"> + <option name="test-tag" value="AttestationVerificationTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="AttestationVerificationTest.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.security.attestationverification" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt new file mode 100644 index 000000000000..48bfd6f5d33c --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -0,0 +1,90 @@ +package android.security.attestationverification + +import android.os.Bundle +import android.app.Activity +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN +import java.lang.IllegalArgumentException +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class SystemAttestationVerificationTest { + + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + } + } + + @Test + fun verifyAttestation_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_tooBigMaxAgeThrows() { + val future = CompletableFuture<VerificationToken>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + future.complete(token) + } + + assertThrows(IllegalArgumentException::class.java) { + avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), future.getSoon(), + Duration.ofSeconds(3601)) + } + } + + private fun <T> CompletableFuture<T>.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 3c9ba20c340a..ca73503164e6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry @@ -196,13 +195,13 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } - @Postsubmit + @FlakyTest @Test fun runPresubmitAssertion() { flickerRule.checkPresubmitAssertions() } - @Postsubmit + @FlakyTest @Test fun runPostsubmitAssertion() { flickerRule.checkPostsubmitAssertions() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index b1bdb3167099..39d2518033c7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -105,7 +105,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete } } - @FlakyTest(bugId = 190189685) + @Postsubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index ac907525ac13..61fe02e05152 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -106,7 +106,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - @FlakyTest + @Postsubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index cc5d9d2b20b5..1c149162291b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -144,7 +144,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @FlakyTest + @Postsubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 429841bf4736..61fe07a17901 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -110,7 +110,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * Checks that the app layer doesn't exist at the start of the transition, that it is * created (invisible) and becomes visible during the transition */ - @FlakyTest + @Postsubmit @Test fun appLayerBecomesVisible() { testSpec.assertLayers { @@ -169,7 +169,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** {@inheritDoc} */ - @FlakyTest + @Postsubmit @Test override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() @@ -211,7 +211,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** {@inheritDoc} */ - @FlakyTest + @Postsubmit @Test override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = super.visibleWindowsShownMoreThanOneConsecutiveEntry() @@ -227,7 +227,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ - @Postsubmit + @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 5b0372d1be25..0a64939cb4ca 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.quickswitch import android.app.Instrumentation import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -133,7 +134,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the transition starts with [testApp2] being the top window. */ - @Postsubmit + @Presubmit @Test fun startsWithApp2WindowBeingOnTop() { testSpec.assertWmStart { @@ -284,7 +285,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() @@ -293,21 +294,21 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 99bc1155233d..5b633764a380 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.quickswitch import android.app.Instrumentation import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -298,7 +299,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() @@ -307,21 +308,21 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index a97a48eb6b19..c18798f0a4b0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.rotation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -97,13 +96,13 @@ class ChangeAppRotationTest( } } - @Postsubmit + @FlakyTest @Test fun runPresubmitAssertion() { flickerRule.checkPresubmitAssertions() } - @Postsubmit + @FlakyTest @Test fun runPostsubmitAssertion() { flickerRule.checkPostsubmitAssertions() @@ -115,11 +114,16 @@ class ChangeAppRotationTest( flickerRule.checkFlakyAssertions() } - /** {@inheritDoc} */ + /** + * Windows maybe recreated when rotated. Checks that the focus does not change or if it does, + * focus returns to [testApp] + */ @FlakyTest(bugId = 190185577) @Test - override fun focusDoesNotChange() { - super.focusDoesNotChange() + fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges(testApp.`package`) + } } /** diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index ce2347d7c1f2..d1bdeed81b78 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -129,17 +129,6 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) open fun entireScreenCovered() = testSpec.entireScreenCovered() /** - * Checks that the focus doesn't change during animation - */ - @Presubmit - @Test - open fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } - } - - /** * Checks that [testApp] layer covers the entire screen at the start of the transition */ @Presubmit diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index 3ca60e3233cd..e44bee644ceb 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -146,15 +146,6 @@ class SeamlessAppRotationTest( } } - /** {@inheritDoc} */ - @Presubmit - @Test - override fun focusDoesNotChange() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) - super.focusDoesNotChange() - } - /** * Checks that [testApp] layer covers the entire screen during the whole transition */ @@ -196,6 +187,19 @@ class SeamlessAppRotationTest( } } + /** + * Checks that the focus doesn't change during animation + */ + @Presubmit + @Test + fun focusDoesNotChange() { + // This test doesn't work in shell transitions because of b/206101151 + assumeFalse(isShellTransitionsEnabled) + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + /** {@inheritDoc} */ @FlakyTest @Test diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java index 7ea2a62d7494..d4bc2a6d3317 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java @@ -42,7 +42,7 @@ public class ColoredRectsActivity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final RectsView hwBothView = new RectsView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java index 7173a85f73e7..584ab596836c 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java @@ -42,7 +42,7 @@ public class Lines2Activity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final LinesView hwBothView = new LinesView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java new file mode 100644 index 000000000000..65a3436a4c5e --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; + +/** Tests for {@link Parcelling}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ParcellingTests { + + private Parcel mParcel = Parcel.obtain(); + + @Test + public void forInstant_normal() { + testForInstant(Instant.ofEpochSecond(500L, 10)); + } + + @Test + public void forInstant_minimum() { + testForInstant(Instant.MIN); + } + + @Test + public void forInstant_maximum() { + testForInstant(Instant.MAX); + } + + @Test + public void forInstant_null() { + testForInstant(null); + } + + private void testForInstant(Instant instant) { + Parcelling<Instant> parcelling = new ForInstant(); + parcelling.parcel(instant, mParcel, 0); + mParcel.setDataPosition(0); + + Instant created = parcelling.unparcel(mParcel); + + if (instant == null) { + assertNull(created); + } else { + assertEquals(instant, created); + } + } + +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 937f9dc60a72..15de226cdc40 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -119,7 +119,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -131,7 +131,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testNewNetworkTriggersMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -143,7 +143,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection @Test public void testSameNetworkDoesNotTriggerMigration() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -203,7 +203,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection triggerChildOpened(); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); getChildSessionCallback() .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index d1f3a210d870..3c70759a2fa6 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -64,7 +64,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); @@ -76,7 +76,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testNewNetworkTriggersReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio @Test public void testSameNetworkDoesNotTriggerReconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 2056eea42ce6..f3eb82f46de7 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -78,7 +78,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNetworkChangesTriggerStateTransitions() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -89,7 +89,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testNullNetworkDoesNotTriggerStateTransition() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index 1c859790a2fe..6568cdd44377 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -58,7 +58,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNewNetworkTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); mTestLooper.dispatchAll(); @@ -72,7 +72,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testSameNetworkDoesNotTriggerRetry() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); mTestLooper.dispatchAll(); @@ -86,7 +86,7 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect @Test public void testNullNetworkTriggersDisconnect() throws Exception { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); mTestLooper.dispatchAll(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 2b0037eaf8eb..b9dfda38a01c 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -59,7 +59,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import org.junit.Before; import org.junit.Test; @@ -238,14 +238,14 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { } @Test - public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkController() { verifyWakeLockSetUp(); final TelephonySubscriptionSnapshot updatedSnapshot = mock(TelephonySubscriptionSnapshot.class); mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); - verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + verify(mUnderlyingNetworkController).updateSubscriptionSnapshot(eq(updatedSnapshot)); verifyWakeLockAcquired(); mTestLooper.dispatchAll(); @@ -256,13 +256,13 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { @Test public void testNonNullUnderlyingNetworkRecordUpdateCancelsAlarm() { mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(null); verifyDisconnectRequestAlarmAndGetCallback(false /* expectCanceled */); mGatewayConnection - .getUnderlyingNetworkTrackerCallback() + .getUnderlyingNetworkControllerCallback() .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); verify(mDisconnectRequestAlarm).cancel(); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 64d0bca15ce9..8a0af2dff8c8 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -16,7 +16,6 @@ package com.android.server.vcn; -import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; @@ -62,6 +61,8 @@ import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscription import com.android.server.vcn.Vcn.VcnGatewayStatusCallback; import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback; import com.android.server.vcn.VcnGatewayConnection.VcnWakeLock; +import com.android.server.vcn.routeselection.UnderlyingNetworkController; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -137,7 +138,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final VcnGatewayConnectionConfig mConfig; @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull protected final VcnGatewayConnection.Dependencies mDeps; - @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + @NonNull protected final UnderlyingNetworkController mUnderlyingNetworkController; @NonNull protected final VcnWakeLock mWakeLock; @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @@ -158,7 +159,7 @@ public class VcnGatewayConnectionTestBase { mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class); mDeps = mock(VcnGatewayConnection.Dependencies.class); - mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + mUnderlyingNetworkController = mock(UnderlyingNetworkController.class); mWakeLock = mock(VcnWakeLock.class); mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); @@ -176,9 +177,9 @@ public class VcnGatewayConnectionTestBase { doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); - doReturn(mUnderlyingNetworkTracker) + doReturn(mUnderlyingNetworkController) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any(), any()); + .newUnderlyingNetworkController(any(), any(), any(), any()); doReturn(mWakeLock) .when(mDeps) .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java index 5af69b5d1bf2..c954cb84df7f 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package com.android.server.vcn; +package com.android.server.vcn.routeselection; import static com.android.server.vcn.VcnTestUtils.setupSystemService; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -48,10 +50,11 @@ import android.telephony.TelephonyManager; import android.util.ArraySet; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkListener; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; -import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; +import com.android.server.vcn.VcnContext; +import com.android.server.vcn.VcnNetworkProvider; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.NetworkBringupCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkControllerCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkController.UnderlyingNetworkListener; import org.junit.Before; import org.junit.Test; @@ -64,7 +67,7 @@ import java.util.Arrays; import java.util.Set; import java.util.UUID; -public class UnderlyingNetworkTrackerTest { +public class UnderlyingNetworkControllerTest { private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int INITIAL_SUB_ID_1 = 1; private static final int INITIAL_SUB_ID_2 = 2; @@ -102,14 +105,14 @@ public class UnderlyingNetworkTrackerTest { @Mock private TelephonyManager mTelephonyManager; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; - @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; + @Mock private UnderlyingNetworkControllerCallback mNetworkControllerCb; @Mock private Network mNetwork; @Captor private ArgumentCaptor<UnderlyingNetworkListener> mUnderlyingNetworkListenerCaptor; private TestLooper mTestLooper; private VcnContext mVcnContext; - private UnderlyingNetworkTracker mUnderlyingNetworkTracker; + private UnderlyingNetworkController mUnderlyingNetworkController; @Before public void setUp() { @@ -140,12 +143,9 @@ public class UnderlyingNetworkTrackerTest { when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); - mUnderlyingNetworkTracker = - new UnderlyingNetworkTracker( - mVcnContext, - SUB_GROUP, - mSubscriptionSnapshot, - mNetworkTrackerCb); + mUnderlyingNetworkController = + new UnderlyingNetworkController( + mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); } private void resetVcnContext() { @@ -181,11 +181,8 @@ public class UnderlyingNetworkTrackerTest { mVcnNetworkProvider, true /* isInTestMode */); - new UnderlyingNetworkTracker( - vcnContext, - SUB_GROUP, - mSubscriptionSnapshot, - mNetworkTrackerCb); + new UnderlyingNetworkController( + vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); verify(cm) .registerNetworkCallback( @@ -233,7 +230,7 @@ public class UnderlyingNetworkTrackerTest { mock(TelephonySubscriptionSnapshot.class); when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS); - mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate); + mUnderlyingNetworkController.updateSubscriptionSnapshot(subscriptionUpdate); // verify that initially-filed bringup requests are unregistered (cell + wifi) verify(mConnectivityManager, times(INITIAL_SUB_IDS.size() + 3)) @@ -255,7 +252,7 @@ public class UnderlyingNetworkTrackerTest { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) + .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) .build(); } @@ -264,7 +261,7 @@ public class UnderlyingNetworkTrackerTest { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) .setSubscriptionIds(netCapsSubIds) - .setSignalStrength(UnderlyingNetworkTracker.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) + .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) .build(); } @@ -304,7 +301,7 @@ public class UnderlyingNetworkTrackerTest { @Test public void testTeardown() { - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); // Expect 5 NetworkBringupCallbacks to be unregistered: 1 for WiFi, 2 for Cellular (1x for // each subId), and 1 for each of the Wifi signal strength thresholds @@ -368,7 +365,7 @@ public class UnderlyingNetworkTrackerTest { networkCapabilities, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); return cb; } @@ -384,7 +381,7 @@ public class UnderlyingNetworkTrackerTest { UPDATED_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -399,7 +396,7 @@ public class UnderlyingNetworkTrackerTest { INITIAL_NETWORK_CAPABILITIES, UPDATED_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -414,11 +411,13 @@ public class UnderlyingNetworkTrackerTest { SUSPENDED_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -434,11 +433,13 @@ public class UnderlyingNetworkTrackerTest { INITIAL_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb, times(1)) + .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -453,7 +454,7 @@ public class UnderlyingNetworkTrackerTest { INITIAL_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, true /* isBlocked */); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); } @Test @@ -462,7 +463,7 @@ public class UnderlyingNetworkTrackerTest { cb.onLost(mNetwork); - verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null); } @Test @@ -471,20 +472,20 @@ public class UnderlyingNetworkTrackerTest { cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES); - // Verify no more calls to the UnderlyingNetworkTrackerCallback when the + // Verify no more calls to the UnderlyingNetworkControllerCallback when the // UnderlyingNetworkRecord does not actually change - verifyNoMoreInteractions(mNetworkTrackerCb); + verifyNoMoreInteractions(mNetworkControllerCb); } @Test public void testRecordTrackerCallbackNotifiedAfterTeardown() { UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback(); - mUnderlyingNetworkTracker.teardown(); + mUnderlyingNetworkController.teardown(); cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES); // Verify that the only call was during onAvailable() - verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); + verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); } // TODO (b/187991063): Add tests for network prioritization diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 740b44e0ce28..bd0a4bc44e18 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -170,16 +170,6 @@ cc_library_host_static { } // ========================================================== -// Build the host shared library: aapt2_jni -// ========================================================== -cc_library_host_shared { - name: "libaapt2_jni", - srcs: toolSources + ["jni/aapt2_jni.cpp"], - static_libs: ["libaapt2"], - defaults: ["aapt2_defaults"], -} - -// ========================================================== // Build the host tests: aapt2_tests // ========================================================== cc_test_host { diff --git a/tools/aapt2/jni/ScopedUtfChars.h b/tools/aapt2/jni/ScopedUtfChars.h deleted file mode 100644 index a8c4b136dcf6..000000000000 --- a/tools/aapt2/jni/ScopedUtfChars.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2010 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 SCOPED_UTF_CHARS_H_included -#define SCOPED_UTF_CHARS_H_included - -#include <string.h> -#include <jni.h> - -#include "android-base/logging.h" - -// This file was copied with some minor modifications from libnativehelper. -// As soon as libnativehelper can be compiled for Windows, this file should be -// replaced with libnativehelper's implementation. -class ScopedUtfChars { - public: - ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { - CHECK(s != nullptr); - utf_chars_ = env->GetStringUTFChars(s, nullptr); - } - - ScopedUtfChars(ScopedUtfChars&& rhs) : - env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) { - rhs.env_ = nullptr; - rhs.string_ = nullptr; - rhs.utf_chars_ = nullptr; - } - - ~ScopedUtfChars() { - if (utf_chars_) { - env_->ReleaseStringUTFChars(string_, utf_chars_); - } - } - - ScopedUtfChars& operator=(ScopedUtfChars&& rhs) { - if (this != &rhs) { - // Delete the currently owned UTF chars. - this->~ScopedUtfChars(); - - // Move the rhs ScopedUtfChars and zero it out. - env_ = rhs.env_; - string_ = rhs.string_; - utf_chars_ = rhs.utf_chars_; - rhs.env_ = nullptr; - rhs.string_ = nullptr; - rhs.utf_chars_ = nullptr; - } - return *this; - } - - const char* c_str() const { - return utf_chars_; - } - - size_t size() const { - return strlen(utf_chars_); - } - - const char& operator[](size_t n) const { - return utf_chars_[n]; - } - - private: - JNIEnv* env_; - jstring string_; - const char* utf_chars_; - - DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); -}; - -#endif // SCOPED_UTF_CHARS_H_included diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp deleted file mode 100644 index ec3c5431c7a3..000000000000 --- a/tools/aapt2/jni/aapt2_jni.cpp +++ /dev/null @@ -1,143 +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. - */ - -#include "com_android_tools_aapt2_Aapt2Jni.h" - -#include <algorithm> -#include <memory> -#include <utility> -#include <vector> - -#include "android-base/logging.h" -#include "ScopedUtfChars.h" - -#include "Diagnostics.h" -#include "cmd/Compile.h" -#include "cmd/Link.h" -#include "util/Util.h" - -using android::StringPiece; - -/* - * Converts a java List<String> into C++ vector<ScopedUtfChars>. - */ -static std::vector<ScopedUtfChars> list_to_utfchars(JNIEnv *env, jobject obj) { - std::vector<ScopedUtfChars> converted; - - // Call size() method on the list to know how many elements there are. - jclass list_cls = env->GetObjectClass(obj); - jmethodID size_method_id = env->GetMethodID(list_cls, "size", "()I"); - CHECK(size_method_id != 0); - jint size = env->CallIntMethod(obj, size_method_id); - CHECK(size >= 0); - - // Now, iterate all strings in the list - // (note: generic erasure means get() return an Object) - jmethodID get_method_id = env->GetMethodID(list_cls, "get", "(I)Ljava/lang/Object;"); - CHECK(get_method_id != 0); - for (jint i = 0; i < size; i++) { - // Call get(i) to get the string in the ith position. - jobject string_obj_uncast = env->CallObjectMethod(obj, get_method_id, i); - CHECK(string_obj_uncast != nullptr); - jstring string_obj = static_cast<jstring>(string_obj_uncast); - converted.push_back(ScopedUtfChars(env, string_obj)); - } - - return converted; -} - -/* - * Extracts all StringPiece from the ScopedUtfChars instances. - * - * The returned pieces can only be used while the original ones have not been - * destroyed. - */ -static std::vector<StringPiece> extract_pieces(const std::vector<ScopedUtfChars> &strings) { - std::vector<StringPiece> pieces; - - std::for_each( - strings.begin(), strings.end(), - [&pieces](const ScopedUtfChars &p) { pieces.push_back(p.c_str()); }); - - return pieces; -} - -class JniDiagnostics : public aapt::IDiagnostics { - public: - JniDiagnostics(JNIEnv* env, jobject diagnostics_obj) - : env_(env), diagnostics_obj_(diagnostics_obj) { - mid_ = NULL; - } - - void Log(Level level, aapt::DiagMessageActual& actual_msg) override { - jint level_value; - switch (level) { - case Level::Error: - level_value = 3; - break; - - case Level::Warn: - level_value = 2; - break; - - case Level::Note: - level_value = 1; - break; - } - jstring message = env_->NewStringUTF(actual_msg.message.c_str()); - jstring path = env_->NewStringUTF(actual_msg.source.path.c_str()); - jlong line = -1; - if (actual_msg.source.line) { - line = actual_msg.source.line.value(); - } - if (!mid_) { - jclass diagnostics_cls = env_->GetObjectClass(diagnostics_obj_); - mid_ = env_->GetMethodID(diagnostics_cls, "log", "(ILjava/lang/String;JLjava/lang/String;)V"); - } - env_->CallVoidMethod(diagnostics_obj_, mid_, level_value, path, line, message); - } - - private: - JNIEnv* env_; - jobject diagnostics_obj_; - jmethodID mid_; - DISALLOW_COPY_AND_ASSIGN(JniDiagnostics); -}; - -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile( - JNIEnv* env, jclass aapt_obj, jobject arguments_obj, jobject diagnostics_obj) { - std::vector<ScopedUtfChars> compile_args_jni = - list_to_utfchars(env, arguments_obj); - std::vector<StringPiece> compile_args = extract_pieces(compile_args_jni); - JniDiagnostics diagnostics(env, diagnostics_obj); - return aapt::CompileCommand(&diagnostics).Execute(compile_args, &std::cerr); -} - -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv* env, - jclass aapt_obj, - jobject arguments_obj, - jobject diagnostics_obj) { - std::vector<ScopedUtfChars> link_args_jni = - list_to_utfchars(env, arguments_obj); - std::vector<StringPiece> link_args = extract_pieces(link_args_jni); - JniDiagnostics diagnostics(env, diagnostics_obj); - return aapt::LinkCommand(&diagnostics).Execute(link_args, &std::cerr); -} - -JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping( - JNIEnv *env, jclass aapt_obj) { - // This is just a no-op method to see if the library has been loaded. -} diff --git a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h b/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h deleted file mode 100644 index 3cd98658fab2..000000000000 --- a/tools/aapt2/jni/com_android_tools_aapt2_Aapt2Jni.h +++ /dev/null @@ -1,37 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include <jni.h> -/* Header for class com_android_tools_aapt2_Aapt2Jni */ - -#ifndef _Included_com_android_tools_aapt2_Aapt2Jni -#define _Included_com_android_tools_aapt2_Aapt2Jni -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: ping - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping - (JNIEnv *, jclass); - -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: nativeCompile - * Signature: (Ljava/util/List;Lcom/android/tools/aapt2/Aapt2JniDiagnostics;)I - */ -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeCompile(JNIEnv*, jclass, jobject, - jobject); - -/* - * Class: com_android_tools_aapt2_Aapt2Jni - * Method: nativeLink - * Signature: (Ljava/util/List;Lcom/android/tools/aapt2/Aapt2JniDiagnostics;)I - */ -JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv*, jclass, jobject, - jobject); - -#ifdef __cplusplus -} -#endif -#endif |