Merge "[MEP] Change mIccId to getIccId API in the UiccCardInfo toString"
diff --git a/Android.bp b/Android.bp
index 731b9f6..778aa55 100644
--- a/Android.bp
+++ b/Android.bp
@@ -326,8 +326,12 @@
"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",
@@ -380,6 +384,9 @@
"//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 392df73..1620983 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 @@
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 @@
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 ef442f0..3da508d 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 @@
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 b44178f..9cae8645 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 @@
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 @@
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 7d12b95..d9c4632 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 788bfe4..9749c80 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.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 @@
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 @@
}
@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 @@
// 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 3bfffbcd..aff3204 100644
--- a/apex/media/framework/java/android/media/MediaTranscodingManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodingManager.java
@@ -949,6 +949,8 @@
*
* @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 @@
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 5f27cc7..82269d4 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -2899,10 +2899,8 @@
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>(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/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;->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/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$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;->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/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/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$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;->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/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 002d42d..522e88f 100644
--- a/boot/hiddenapi/hiddenapi-unsupported.txt
+++ b/boot/hiddenapi/hiddenapi-unsupported.txt
@@ -124,10 +124,8 @@
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 32865ad..c0d313d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -129,6 +129,7 @@
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";
@@ -7248,8 +7249,8 @@
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);
@@ -8742,6 +8743,7 @@
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);
@@ -9036,11 +9038,15 @@
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 {
@@ -9354,7 +9360,8 @@
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[]);
@@ -9948,6 +9955,16 @@
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);
@@ -9960,6 +9977,7 @@
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);
}
@@ -9995,20 +10013,26 @@
}
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 {
@@ -11301,6 +11325,7 @@
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";
@@ -12554,6 +12579,7 @@
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;
@@ -16509,11 +16535,13 @@
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);
@@ -20682,6 +20710,7 @@
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);
@@ -32370,6 +32399,7 @@
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";
@@ -32406,13 +32436,16 @@
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
@@ -35594,7 +35627,7 @@
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";
@@ -51152,6 +51185,7 @@
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);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fb325a2..dd1760c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -256,7 +256,8 @@
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 +268,11 @@
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 +728,10 @@
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);
@@ -2000,6 +2006,8 @@
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 +2024,13 @@
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 +2358,34 @@
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 +2471,14 @@
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 +6343,7 @@
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 +6379,7 @@
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);
}
@@ -6518,6 +6555,7 @@
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 +6648,7 @@
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 +7707,15 @@
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 +7724,31 @@
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 +7787,35 @@
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;
+ @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;
}
- 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 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 +7842,13 @@
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 +12351,7 @@
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 +12359,13 @@
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 ba0b6aa..c1ab070 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -267,10 +267,6 @@
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 @@
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 @@
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 @@
}
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.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 @@
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 {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 0f852b4..09af72d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -2071,7 +2071,7 @@
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 @@
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 @@
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 @@
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 @@
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/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c89e8b0..15f67d0 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.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 @@
@UnsupportedAppUsage
private ContextImpl mSystemContext;
- private ContextImpl mSystemUiContext;
+ private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>();
@UnsupportedAppUsage
static volatile IPackageManager sPackageManager;
@@ -1301,8 +1302,11 @@
}
@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 @@
}
}
- 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 @@
}
}
+ 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 @@
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 @@
}
@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 @@
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 7a545f6..565f690 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -9469,23 +9469,23 @@
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 1943f9d..d1b7145 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -920,16 +920,16 @@
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 @@
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 756833a..4a7361e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1882,6 +1882,14 @@
"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 @@
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 @@
// 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 @@
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 @@
* 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 @@
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 @@
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 @@
* @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 @@
@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 b324fb6..78759db 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -21,8 +21,8 @@
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 @@
*
* @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 183e714..8f904b5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -332,6 +332,8 @@
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.")
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 0e42a79..1714229 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -108,7 +108,7 @@
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 1038530..e220627 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 @@
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 @@
/** 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 0145747..be6a31e 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -33,9 +33,12 @@
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 @@
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST);
+ clearStartForegroundServiceStackTrace();
} catch (RemoteException ex) {
}
}
@@ -782,6 +786,7 @@
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0, foregroundServiceType);
+ clearStartForegroundServiceStackTrace();
} catch (RemoteException ex) {
}
}
@@ -956,4 +961,34 @@
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 0000000..ec058f8
--- /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 74e2858..089c269 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -196,9 +196,12 @@
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 @@
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 @@
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/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 828b171..58ded71 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -638,7 +638,7 @@
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 57b3196..603c7cf 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3471,7 +3471,10 @@
* 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 @@
* 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 @@
/**
* 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());
}
@@ -9279,7 +9288,7 @@
throwIfParentInstance("getPermittedInputMethodsForCurrentUser");
if (mService != null) {
try {
- return mService.getPermittedInputMethodsForCurrentUser();
+ return mService.getPermittedInputMethodsAsUser(UserHandle.myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9288,6 +9297,34 @@
}
/**
+ * 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 cf48594..b9fcdf5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -243,7 +243,7 @@
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 a71cffe..ceab02f 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -96,6 +96,13 @@
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 cf00cbd..20122fb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -51,7 +51,6 @@
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.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 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.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 @@
@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 @@
return mService.getScanMode(mAttributionSource);
}
} catch (RemoteException e) {
- Log.e(TAG, "", e);
+ throw e.rethrowFromSystemServer();
} finally {
mServiceLock.readLock().unlock();
}
@@ -1626,143 +1634,110 @@
}
/**
- * 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.
+ * {@link #ACTION_REQUEST_DISCOVERABLE} 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
+ * @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
+ * @return the duration of the discoverable timeout or null if an error has occurred
+ */
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
+ public @Nullable Duration getDiscoverableTimeout() {
+ if (getState() != STATE_ON) {
+ return null;
+ }
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ long timeout = mService.getDiscoverableTimeout(mAttributionSource);
+ return (timeout == -1) ? null : Duration.ofSeconds(timeout);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return null;
+ }
+
+ /**
+ * 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
*/
- @UnsupportedAppUsage
- @RequiresLegacyBluetoothPermission
+ @SystemApi
@RequiresBluetoothScanPermission
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
- public boolean setScanMode(@ScanMode int mode) {
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_SCAN,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ @ScanModeStatusCode
+ public int setDiscoverableTimeout(@NonNull Duration timeout) {
if (getState() != STATE_ON) {
- return false;
+ return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+ }
+ 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) {
- return mService.setScanMode(mode, getDiscoverableTimeout(), mAttributionSource);
+ return mService.setDiscoverableTimeout(timeout.toSeconds(), mAttributionSource);
}
} catch (RemoteException e) {
- Log.e(TAG, "", e);
+ throw e.rethrowFromSystemServer();
} finally {
mServiceLock.readLock().unlock();
}
- return false;
- }
-
- /** @hide */
- @UnsupportedAppUsage
- @RequiresBluetoothScanPermission
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
- public int getDiscoverableTimeout() {
- 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 -1;
- }
-
- /** @hide */
- @UnsupportedAppUsage
- @RequiresBluetoothScanPermission
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
- public void setDiscoverableTimeout(int timeout) {
- if (getState() != STATE_ON) {
- return;
- }
- try {
- mServiceLock.readLock().lock();
- if (mService != null) {
- mService.setDiscoverableTimeout(timeout, mAttributionSource);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
+ return BluetoothStatusCodes.ERROR_UNKNOWN;
}
/**
@@ -2424,38 +2399,6 @@
}
/**
- * 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 7fe18a0..69525b5 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 @@
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 @@
* 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 3e799de..08e0178 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.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 @@
* 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 1655b62..db5b751 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -18,7 +18,6 @@
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 @@
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 ca01784..5ba7bb1 100644
--- a/core/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/core/java/android/bluetooth/BluetoothStatusCodes.java
@@ -40,7 +40,7 @@
/**
* 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 ab1eb1f..3f02aa2 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 @@
* 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 @@
*
* @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 @@
/**
* @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);
- }
-
- /**
- * @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;
+ public @Nullable MacAddress getDeviceMacAddress() {
+ return mDeviceMacAddress;
}
/** @hide */
- public @NonNull String getDeviceMacAddress() {
- return Objects.requireNonNull(getIdOfType(TYPE_MAC_ADDRESS),
- "MAC address of this device is not specified.");
+ public @Nullable String getDeviceMacAddressAsString() {
+ return mDeviceMacAddress != null ? mDeviceMacAddress.toString().toUpperCase() : null;
}
/**
- * @return the profile of the device.
+ * @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 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 @@
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 @@
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 @@
@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 @@
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 7d1aabc..1dc161c 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 @@
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 @@
/**
* 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 @@
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 @@
*/
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 @@
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 @@
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 @@
* @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 @@
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 @@
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 @@
}
/**
+ * 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 @@
"singleDevice = " + mSingleDevice + ", " +
"deviceFilters = " + mDeviceFilters + ", " +
"deviceProfile = " + mDeviceProfile + ", " +
+ "displayName = " + mDisplayName + ", " +
+ "selfManaged = " + mSelfManaged + ", " +
+ "forceConfirmation = " + mForceConfirmation + ", " +
"callingPackage = " + mCallingPackage + ", " +
"deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " +
"creationTime = " + mCreationTime + ", " +
@@ -419,6 +545,9 @@
&& 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 @@
_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 @@
// 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 @@
// 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 @@
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 @@
};
@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 6719a69..2b12f12 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.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 @@
* <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 @@
"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.
*
- * @param chooserLauncher a {@link IntentSender} to launch the UI for user to select a
- * device
+ * 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 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 @@
}
/**
- * Associate this app with a companion device, selected by user
+ * Request to associate this app with a companion device.
*
- * <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>Note that before creating establishing association the system may need to show UI to
+ * collect user confirmation.</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>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>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>
+ * <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 callback The callback used to notify application when the association is created.
+ * @param handler The handler which will be used to invoke the callback.
*
- * <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>
- *
- * @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
- *
- * @see AssociationRequest
+ * @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 CallbackProxy(request, callback, Handler.mainIfNull(handler)),
- getCallingPackage());
+ 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 AssociationRequestCallbackProxy(executor, callback),
+ mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -182,15 +343,32 @@
* {@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 @@
* {@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.disassociate(deviceMacAddress, getCallingPackage());
+ 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(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -234,12 +440,14 @@
* <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 @@
@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 @@
* 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 @@
}
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 @@
}
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 @@
return featurePresent;
}
- private Activity getActivity() {
- return (Activity) mContext;
- }
+ private static class AssociationRequestCallbackProxy extends IAssociationRequestCallback.Stub {
+ private final Handler mHandler;
+ private final Callback mCallback;
+ private final Executor mExecutor;
- 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 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);
+ public void onAssociationCreated(@NonNull AssociationInfo association) {
+ execute(mCallback::onAssociationCreated, association);
}
- <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);
- }
- });
- }
+ @Override
+ 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));
}
}
+ }
- @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;
- }
+ 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 5deed1a..0000000
--- 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/IFindDeviceCallback.aidl b/core/java/android/companion/IAssociationRequestCallback.aidl
similarity index 66%
copy from core/java/android/companion/IFindDeviceCallback.aidl
copy to core/java/android/companion/IAssociationRequestCallback.aidl
index a3a47a9..8cc2a71 100644
--- a/core/java/android/companion/IFindDeviceCallback.aidl
+++ b/core/java/android/companion/IAssociationRequestCallback.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.
@@ -17,10 +17,13 @@
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 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 71e5b24..702e8db 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 @@
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 101f948..1558db2 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 @@
* @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
similarity index 67%
rename from core/java/android/companion/IFindDeviceCallback.aidl
rename to core/java/android/companion/IOnAssociationsChangedListener.aidl
index a3a47a9..e6794b7 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 25d1d53..2b75022 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3842,6 +3842,8 @@
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 @@
* {@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 @@
/**
* 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 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 2b8681a..a36d532 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2378,6 +2378,14 @@
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).
@@ -6916,7 +6924,6 @@
*/
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 f72288c..18e205f 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -18,6 +18,7 @@
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 @@
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 46f1797..1c82b38 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -69,41 +69,34 @@
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 @@
@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 @@
* 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 @@
* 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 @@
* 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 @@
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 @@
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 @@
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 1586013..80584d1 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,6 +19,13 @@
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.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 @@
@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 @@
* @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 @@
}
}
+ 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 1b51314..c777bf5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -9044,7 +9044,7 @@
}
/**
- * 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 d157768..ff80e61 100644
--- a/core/java/android/content/pm/PackagePartitions.java
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -19,8 +19,11 @@
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 @@
*/
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 @@
}
}
+ /**
+ * 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 final int type;
@NonNull
+ private final String mName;
+
+ @NonNull
private final DeferredCanonicalFile mFolder;
@Nullable
@@ -122,9 +159,10 @@
@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 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 @@
* 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 cc4782a..aa57806 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 ef124c7..b11b38a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -76,7 +76,7 @@
@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 @@
@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 @@
@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 @@
*/
@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 @@
@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 @@
*/
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
* 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 @@
@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 @@
* 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 @@
@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 @@
* 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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 f07f382..d5957a2 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -334,7 +334,7 @@
@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 1ac9739..0334601 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 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 9a800b0..468bff1 100644
--- a/core/java/android/content/pm/pkg/PackageUserStateUtils.java
+++ b/core/java/android/content/pm/pkg/PackageUserStateUtils.java
@@ -34,15 +34,15 @@
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 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 @@
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 @@
&& (!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 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 @@
/**
* 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 8ebb8ec..01bf49e 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2432,27 +2432,10 @@
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 @@
}
/**
+ * @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/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 8864939..9b19fc4 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -110,6 +110,11 @@
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 @@
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 @@
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 7d71984..0b1ed65 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -109,7 +109,8 @@
/** {@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 83e1061..2985c75 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -20,6 +20,7 @@
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;
@@ -380,6 +381,31 @@
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.
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e5d8620..6f0c944 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1016,9 +1016,8 @@
}
/**
- * 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 @@
}
/**
- * 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 5cd4eb5..5e0e1b7 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -35,8 +35,10 @@
/**
* 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 0ba2663..7b8b5c0 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -51,9 +51,13 @@
* 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 @@
* 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 @@
* 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 @@
*/
@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 @@
* 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 ee24084..c906a13 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -70,9 +70,17 @@
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 @@
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 @@
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 @@
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 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 @@
String subscriberId = BackupUtils.readString(in);
String networkId = BackupUtils.readString(in);
- if (!isKnownMatchRule(matchRule)) {
- throw new BackupUtils.BadVersionException(
- "Restored network template contains unknown match rule " + 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;
}
- return new NetworkTemplate(matchRule, subscriberId, networkId);
+ 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, e);
+ }
}
}
diff --git a/core/java/android/net/RssiCurve.java b/core/java/android/net/RssiCurve.java
index 668e966..02cafb5 100644
--- a/core/java/android/net/RssiCurve.java
+++ b/core/java/android/net/RssiCurve.java
@@ -50,8 +50,10 @@
* 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 64b3bf1..a46bdd9a 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -29,8 +29,10 @@
/**
* 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 bc9d8c5..2e4ea89 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -29,8 +29,10 @@
* 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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a4a76a8..53484d2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -7354,9 +7354,11 @@
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 743468a..35b9ccc 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1294,6 +1294,18 @@
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 @@
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 aff55af..5f2c113 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -110,6 +110,20 @@
@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 @@
/** @hide */
@Override
+ public boolean isHapticFeedbackCandidate() {
+ return mEffect.isHapticFeedbackCandidate();
+ }
+
+ /** @hide */
+ @Override
public void validate() {
mEffect.validate();
}
@@ -431,6 +451,17 @@
/** @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 @@
*/
@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 @@
/** @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 50ca9ff..3f42164 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -60,6 +60,8 @@
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 92861fb..1e424d1 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 a828268..d0d6cb7 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -196,9 +196,6 @@
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 e5622a3..c690df2 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -223,9 +223,6 @@
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 cf4ce9b..bc6dbd8 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -308,6 +308,25 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 e986036..9612ca6 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -21,9 +21,6 @@
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 @@
* 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 @@
* 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 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 @@
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 @@
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 @@
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 @@
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 @@
* {@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 a0cbbfe..5758a4e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -53,7 +53,10 @@
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 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 @@
return mRepeatIndex;
}
+ /** @hide */
@Override
public void validate() {
int segmentCount = mSegments.size();
@@ -620,6 +638,37 @@
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 @@
return resolved;
}
+ /** @hide */
@NonNull
@Override
public Composed scale(float scaleFactor) {
@@ -652,6 +702,7 @@
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 75234db..c67c82e 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -493,7 +493,7 @@
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 f57d157..39a2e13 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -56,11 +56,12 @@
* <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 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 @@
return isPrimary() && (getType() == TYPE_PUBLIC);
}
- @UnsupportedAppUsage
- public boolean isVisible() {
- return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
+ private boolean isVisibleForRead() {
+ return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_READ) != 0;
}
- public boolean isVisibleForUser(int userId) {
- if ((type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED)
- && mountUserId == userId) {
- return isVisible();
+ private boolean isVisibleForWrite() {
+ return (mountFlags & MOUNT_FLAG_VISIBLE_FOR_WRITE) != 0;
+ }
+
+ @UnsupportedAppUsage
+ public boolean isVisible() {
+ return isVisibleForRead() || isVisibleForWrite();
+ }
+
+ 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 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 0000000..b54d6bf
--- /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 78b4346..30f5a5c 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -67,17 +67,38 @@
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 @@
return this;
}
+ /** @hide */
@NonNull
@Override
public PrebakedSegment applyEffectStrength(int effectStrength) {
@@ -105,16 +127,17 @@
}
}
+ /** @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 2ef29cb..58ca978 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -67,18 +67,27 @@
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 @@
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 aad87c5..3ec5636 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -87,11 +87,19 @@
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 @@
Preconditions.checkArgumentInRange(mEndAmplitude, 0f, 1f, "endAmplitude");
}
-
+ /** @hide */
@NonNull
@Override
public RampSegment resolve(int defaultAmplitude) {
@@ -108,6 +116,7 @@
return this;
}
+ /** @hide */
@NonNull
@Override
public RampSegment scale(float scaleFactor) {
@@ -121,6 +130,7 @@
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 11209e0..69a381f 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -73,12 +73,20 @@
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 @@
}
}
+ /** @hide */
@NonNull
@Override
public StepSegment resolve(int defaultAmplitude) {
@@ -103,6 +112,7 @@
mDuration);
}
+ /** @hide */
@NonNull
@Override
public StepSegment scale(float scaleFactor) {
@@ -113,6 +123,7 @@
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 5b42845..979c447 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -57,10 +57,26 @@
*/
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 @@
*
* <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 @@
*
* @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 @@
*
* @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 cc95c1f..7979256 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -54,6 +54,7 @@
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 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 @@
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 @@
+ " 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 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.
@@ -5338,6 +5364,7 @@
PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
PUBLIC_SETTINGS.add(VIBRATE_WHEN_RINGING);
+ PUBLIC_SETTINGS.add(APPLY_RAMPING_RINGER);
}
/**
@@ -5875,6 +5902,10 @@
}
/** @hide */
+ public static void getMovedToSystemSettings(Set<String> outKeySet) {
+ }
+
+ /** @hide */
public static void clearProviderForTest() {
sProviderHolder.clearProviderForTest();
sNameValueCache.clearGenerationTrackerForTest();
@@ -10472,7 +10503,9 @@
* 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";
@@ -11981,8 +12014,10 @@
* 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";
@@ -11991,8 +12026,10 @@
* 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";
@@ -12025,8 +12062,10 @@
* {@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";
@@ -12036,8 +12075,10 @@
* 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";
@@ -12047,8 +12088,10 @@
* {@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";
@@ -15251,12 +15294,24 @@
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();
@@ -15288,6 +15343,11 @@
+ " 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);
}
@@ -15452,6 +15512,13 @@
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/security/attestationverification/AttestationProfile.aidl b/core/java/android/security/attestationverification/AttestationProfile.aidl
new file mode 100644
index 0000000..51696a9
--- /dev/null
+++ b/core/java/android/security/attestationverification/AttestationProfile.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 AttestationProfile;
diff --git a/core/java/android/security/attestationverification/AttestationProfile.java b/core/java/android/security/attestationverification/AttestationProfile.java
new file mode 100644
index 0000000..7a43dac
--- /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 0000000..db783ce
--- /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 0000000..26c3051
--- /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 0000000..2fb328c
--- /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 0000000..082ad32
--- /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 0000000..f61c456
--- /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 0000000..666a8b0
--- /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 0000000..ae26823
--- /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 0000000..783d0a1
--- /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 474ceda..4fcb13c 100644
--- a/core/java/android/util/BackupUtils.java
+++ b/core/java/android/util/BackupUtils.java
@@ -37,6 +37,10 @@
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 ece6b35..bab2089 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -242,35 +242,49 @@
*
* @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 9cd8313..de56d3a 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -151,8 +151,7 @@
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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0b4857d..2c766bd 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -855,6 +855,23 @@
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 02b2c5d..d609fb8 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -124,7 +124,12 @@
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 aa1acc1..a6f88a7 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -709,8 +709,8 @@
}
/**
- * 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 @@
}
/**
- * 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 7af77ca..00754af 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -613,7 +613,10 @@
// 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 7fe810a..f69bb6a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4790,6 +4790,16 @@
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 e634d60..94f6333 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -236,6 +236,9 @@
*/
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 bc21488..dc4c59a 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -115,13 +115,13 @@
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,37 @@
@UnsupportedAppUsage()
public static AccessibilityInteractionClient getInstance() {
final long threadId = Thread.currentThread().getId();
- return getInstanceForThread(threadId, true);
+ return getInstanceForThread(threadId);
+ }
+
+ /**
+ * <strong>Note:</strong> We keep one instance per interrogating thread since
+ * the instance contains state which can lead to undesired thread interleavings.
+ * 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.
+ *
+ * @return The client for a given <code>threadId</code>.
+ */
+ public static AccessibilityInteractionClient getInstanceForThread(long threadId) {
+ synchronized (sStaticLock) {
+ AccessibilityInteractionClient client = sClients.get(threadId);
+ if (client == null) {
+ client = new AccessibilityInteractionClient();
+ sClients.put(threadId, client);
+ }
+ return client;
+ }
+ }
+
+ /**
+ * @return The client for the current thread.
+ */
+ public static AccessibilityInteractionClient getInstance(Context context) {
+ final long threadId = Thread.currentThread().getId();
+ if (context != null) {
+ return getInstanceForThread(threadId, context);
+ }
+ return getInstanceForThread(threadId);
}
/**
@@ -162,61 +192,11 @@
* @return The client for a given <code>threadId</code>.
*/
public static AccessibilityInteractionClient getInstanceForThread(long threadId,
- boolean initializeCache) {
- 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);
- }
- sClients.put(threadId, client);
- }
- return client;
- }
- }
-
- /**
- * @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, initializeCache);
- }
-
- /**
- * <strong>Note:</strong> We keep one instance per interrogating thread since
- * the instance contains state which can lead to undesired thread interleavings.
- * 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) {
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 @@
/**
* 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 static void removeConnection(int connectionId) {
synchronized (sConnectionCache) {
sConnectionCache.remove(connectionId);
+ sCaches.remove(connectionId);
}
}
@@ -263,32 +262,21 @@
* 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 @@
*
* @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 @@
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
AccessibilityWindowInfo window;
- if (!bypassCache && sAccessibilityCache != null) {
- window = sAccessibilityCache.getWindow(accessibilityWindowId);
- if (window != null) {
+ 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 (DEBUG) {
- Log.i(LOG_TAG, "Window cache hit");
+ Log.i(LOG_TAG, "Window cache miss");
}
- if (shouldTraceClient()) {
- logTraceClient(connection, "getWindow cache",
- "connectionId=" + connectionId + ";accessibilityWindowId="
- + accessibilityWindowId + ";bypassCache=false");
- }
- 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 @@
+ 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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
}
}
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 bb3f4e5..dc61727 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -121,6 +121,8 @@
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 @@
@UnsupportedAppUsage(trackingBug = 123768939L)
boolean mIsHighTextContrastEnabled;
+ boolean mIsAudioDescriptionByDefaultRequested;
+
// accessibility tracing state
int mAccessibilityTracingState = 0;
@@ -1293,15 +1297,19 @@
(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 @@
}
}
+ /**
+ * 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/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 078ab25..4e8d2da 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -98,4 +98,6 @@
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 ddf68fc..67d9667 100644
--- a/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
+++ b/core/java/android/view/accessibility/IWindowMagnificationConnection.aidl
@@ -38,9 +38,14 @@
* 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 827d033..46f80f3 100644
--- a/core/java/android/widget/RemoteViewsListAdapter.java
+++ b/core/java/android/widget/RemoteViewsListAdapter.java
@@ -89,8 +89,7 @@
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/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 5aa6233..17b675f 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,13 +19,10 @@
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 @@
* @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 @@
* {@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 @@
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 @@
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 f3e3859..b331a9e 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -21,6 +21,7 @@
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.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 @@
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 @@
}
/**
+ * 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 0047f43..ee4d46d 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.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 @@
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 @@
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 @@
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 bff813e..6a6f60e 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 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 06d68e0..aba43d8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -3522,6 +3522,10 @@
* <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 @@
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 @@
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 92d5a47..6d4b8c5 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -724,9 +724,6 @@
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 @@
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 @@
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 1ab316d..3147c34 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.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 @@
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 6cea8bf..f2bcb02 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -73,6 +73,9 @@
# 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 4c2b114..5e0d9b3 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 @@
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 @@
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 783b0d4..59c01dc 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 @@
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 @@
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 411a392..c5b3d8a 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 <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 <utils/Log.h>
-#include "jni.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/fsverity.h>
-#include <linux/stat.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
-
-#include <android-base/unique_fd.h>
+#include <utils/Log.h>
#include <type_traits>
+#include "jni.h"
+
namespace android {
namespace {
@@ -73,18 +73,31 @@
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;
}
- 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 (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 891e9fc..5fdcfdf 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 @@
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 @@
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 83e26f6..db5d49d 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -601,7 +601,7 @@
// 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 f8b5b233..73d6a17 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -242,7 +242,9 @@
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 601280a..e7c2f81 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 -->
@@ -3015,6 +3015,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 +3470,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 +5940,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 +5986,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 22e66d5..8f2658b 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 2b9f952..304affe 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 861e329..34b6a54 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 a36785f..2b830b4 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
@@ -5045,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 faa9902..45d9a36 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 409025b..8eb6ebc 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -587,7 +587,8 @@
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 @@
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 207671e..60d48b2 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 @@
}
@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 0000000..570b713
--- /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 06b5d18..508856b 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationTest.java
@@ -125,6 +125,12 @@
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 @@
}
@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 6cbfffc..781564b 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 @@
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 0ac8f08..bdd76a5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -223,7 +223,7 @@
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 @@
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 @@
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 de80812..a0e1f43 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 @@
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 538655b..a690553 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 @@
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 174b4a7..5f80d2a 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 @@
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 79529b8..fdce86a 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 @@
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 33c6a4b..e689b5d3 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 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 @@
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 7e1e7f4..3e061d2 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 @@
@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 @@
@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 @@
*/
@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 @@
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 a6e351d..52cb9f3 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 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 @@
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 @@
}
@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 @@
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 0000000..b2a45d2
--- /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 0000000..6ce1ee4
--- /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 0000000..2540f59
--- /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 103b836..f83f401 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -422,6 +422,11 @@
<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 8ccf02ca..6bfbd8d 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 0f356a6..2f56b18 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -74,6 +74,10 @@
* 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 @@
*/
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 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 @@
if (foregroundDrawable != null) {
addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
}
+ if (monochromeDrawable != null) {
+ addLayer(MONOCHROME_ID, createChildDrawable(monochromeDrawable));
+ }
}
/**
@@ -227,9 +247,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 7354c90..b843589 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -79,7 +79,7 @@
* 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 @@
* 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 @@
* 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 df5b3f5..a34d0ab 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -287,7 +287,7 @@
*
* @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 @@
*
* {@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/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
new file mode 100644
index 0000000..329e5b9
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<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 75bc461..8e98b82 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.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 @@
}
/**
+ * 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 @@
/** @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 @@
@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 @@
}
}
+ @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 @@
}
}
+ /**
+ * 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 @@
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 c8449a3..c807f66 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 @@
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/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 300319a..b40021e 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.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 @@
// 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 @@
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 @@
// 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 @@
mExpandedAnimationController.dragBubbleOut(
v, viewInitialX + dx, viewInitialY + dy);
} else {
- if (mStackEduView != null) {
+ if (isStackEduShowing()) {
mStackEduView.hide(false /* fromExpansion */);
}
mStackAnimationController.moveStackFromTouch(
@@ -646,6 +657,10 @@
|| (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 @@
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 @@
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 @@
* 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 @@
* 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 @@
* @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 @@
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 @@
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
- if (mStackEduView != null) {
+ if (isStackEduShowing()) {
mStackEduView.hide(true /* fromExpansion */);
}
beforeExpandedViewAnimation();
@@ -2390,7 +2418,7 @@
if (flyoutMessage == null
|| flyoutMessage.message == null
|| !bubble.showFlyout()
- || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE)
+ || isStackEduShowing()
|| isExpanded()
|| mIsExpansionAnimating
|| mIsGestureInProgress
@@ -2512,7 +2540,7 @@
* 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/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index f6a90b7..3846de7 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 @@
* @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 @@
*/
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 d07fff3..3579bf4 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 @@
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 9ceed24..54ce6bb 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.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 @@
@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
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 46c7b50..f562fd9c 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 @@
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 0000000..edeff6e3
--- /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 d2b4711..101295d 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.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 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 @@
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 @@
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 @@
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 @@
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 efc9ed0..20d8054 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 @@
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 @@
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 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 @@
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 0000000..2f47af5
--- /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 5c8e7d0..52ff21b 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.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 @@
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/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 8d9ad4d..caa1f01 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 @@
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 6cc5f09..854fc60e 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 @@
* 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 @@
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
+ mTaskOrganizer.addFocusListener(this);
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
}
@@ -772,6 +773,11 @@
}
@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 @@
} 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 @@
}
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 5687f4d..eb512af 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 @@
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.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 @@
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 PhonePipMenuController(Context context, PipBoundsState pipBoundsState,
PipMediaController mediaController, SystemWindows systemWindows,
+ Optional<SplitScreenController> splitScreenOptional,
ShellExecutor mainExecutor, Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
@@ -152,6 +157,7 @@
mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
+ mSplitScreenController = splitScreenOptional;
}
public boolean isMenuVisible() {
@@ -180,7 +186,8 @@
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 @@
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 b209699..82e8273 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.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.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 @@
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 @@
private AnimatorSet mMenuContainerAnimator;
private PhonePipMenuController mController;
+ private Optional<SplitScreenController> mSplitScreenControllerOptional;
private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@@ -144,12 +151,14 @@
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 @@
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 @@
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 3d3a630..4a99097 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 @@
* 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/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 6b42ed7..d2e341d 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 @@
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 @@
@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 a3726d4..8ad0f20 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 @@
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 @@
/** 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 @@
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 @@
/** 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 @@
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 @@
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 @@
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 @@
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/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 5f48c73..014f02b 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 @@
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 @@
|| 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 @@
// 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 a9c81b3..73f65b0 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b62360e..487eb70 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 @@
}
/**
+ * 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 1d463d5..e6c2f38e 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 @@
@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 10cf0b7..1a3e42c 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 @@
@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 722ec34..5c78b29 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 @@
@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 38c008c..251d92d 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 @@
@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 13824b8..d47057f 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 @@
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 c003084..097867a 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 @@
}
}
- @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 1605d80..a928bbd 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 @@
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 @@
}
}
- @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 a8f17a75..64636be 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 @@
}
}
- @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 3885155..ef7d65e 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 @@
splitScreenApp.component, secondaryApp.component,
FlickerComponentName.SNAPSHOT)
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -94,11 +93,11 @@
}
}
- @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 079a6ef..c1fba7d 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 @@
}
}
- @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 a787f2b..77fb101 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 @@
@Test
fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
- @Postsubmit
+ @FlakyTest
@Test
fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
- @Postsubmit
+ @FlakyTest
@Test
fun layerBecomesInvisible() {
testSpec.assertLayers {
@@ -136,7 +136,7 @@
}
}
- @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 030e040..34c0f849 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.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 @@
@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 @@
}
}
+ @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 2def979..1b7220e 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.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 @@
@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 @@
}
}
+ @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 9191d0e..40be21a 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 @@
}
}
- @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 @@
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 9c26105..a940a7f 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 @@
}
}
- @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 @@
@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 @@
super.statusBarLayerRotatesScales()
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
@@ -118,7 +119,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAppShowsOnTop() {
testSpec.assertWmEnd {
@@ -126,7 +127,7 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
@@ -134,13 +135,13 @@
}
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAlwaysVisible() = testSpec.assertWm {
this.isAppWindowVisible(pipApp.component)
}
- @FlakyTest
+ @Postsubmit
@Test
fun pipAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
@@ -148,7 +149,7 @@
}
}
- @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 73eebad..453050f 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 @@
}
@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 bfa2c92..9f74520 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.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 @@
@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 70b7c67..d92b12e 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.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 @@
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 @@
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 @@
// 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 d17c328..bbd4c81 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -641,22 +641,25 @@
}
// 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 @@
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 @@
}
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 491f5f5..a2d0103 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -107,6 +107,8 @@
target: {
android: {
shared_libs: [
+ "android.hardware.graphics.common-V3-ndk",
+ "android.hardware.graphics.common@1.2",
"liblog",
"libcutils",
"libutils",
@@ -126,9 +128,11 @@
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 0b3b393..a5c0924 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 @@
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 @@
// 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 @@
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::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 da8041f..9a4c550 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -91,7 +91,7 @@
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 0789344..47eb5d3 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -96,6 +96,12 @@
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 @@
*/
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 1439656..553b08f 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 @@
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 @@
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/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..79b2155
--- /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/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 2916b01..337b45c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -62,6 +62,7 @@
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 @@
}
/**
+ * 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 bac44ad..e979a1b5 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -163,6 +163,14 @@
* {@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 4daedfc..939b679 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5106,7 +5106,6 @@
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 @@
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 @@
"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 @@
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 6077f04..4d8eda1 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -22,12 +22,21 @@
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 @@
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 64c884e..fe4e8b7 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -46,6 +46,10 @@
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 0000000..0abc7ec
--- /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 0000000..3690d05
--- /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 0461f0a..bafb03b 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -657,13 +657,6 @@
*/
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 @@
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 0dd64b8..39c438a 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 @@
* @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 104efe6..25e1ace 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -19,6 +19,7 @@
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 @@
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 2f165f0..1dee9cc 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -18,6 +18,7 @@
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 @@
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 0afa971..440b3d30 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 @@
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 0873aad..d308463 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 @@
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 093e1be..ae35edc 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.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 @@
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 @@
}
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 @@
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 @@
* @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 Session(IBinder token, ITvIAppManager service, int userId, int seq,
- SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+ private IBinder mToken;
+ private TvInputEventSender mSender;
+ private InputChannel mInputChannel;
+
+ 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 @@
}
/**
+ * 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 @@
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 @@
}
});
}
+
+ 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 78b8173..fe087ca 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.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 @@
}
@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 @@
* @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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 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 @@
}
}
+ /**
+ * 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 @@
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 @@
* @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 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 @@
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 @@
}
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 8bcf3d2..ed04754 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 @@
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 @@
mIsPassthrough = isPassthrough;
mAudioStreamType = audioStreamType;
mVideoStreamType = videoStreamType;
+ mUseSecureMemory = useSecureMemory;
}
/**
@@ -223,6 +225,16 @@
}
/**
+ * 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 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 @@
/**
* Sets whether it's passthrough.
+ *
+ * <p>Default value is {@code false}.
*/
@NonNull
public Builder setPassthrough(boolean isPassthrough) {
@@ -263,6 +278,8 @@
* <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 @@
* <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 @@
}
/**
+ * 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/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index cd70365..d34581d 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.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 @@
/** @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 @@
* 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 @@
/**
* 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 fc6451f..ff8f796 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 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.hardware.tv.tuner.Constant;
/**
* Table information for Section Filter.
@@ -26,6 +27,12 @@
*/
@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 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 9526c52..4c1ed45 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -693,6 +693,10 @@
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 @@
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 @@
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 @@
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;
}
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index bd283dc..8568383 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -346,4 +346,13 @@
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 7ebe7bc7..20e5610 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -33,6 +33,7 @@
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;
@@ -152,6 +153,11 @@
*/
Result freeSharedFilterToken(const string& filterToken);
+ /**
+ * Set a filter delay hint.
+ */
+ Result setDelayHint(const FilterDelayHint& hint);
+
private:
Result getFilterMq();
int64_t copyData(int8_t* buffer, int64_t size);
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
index ae63462..0c8e431 100644
--- 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
@@ -41,5 +41,5 @@
interface ISecureElementService {
String[] getReaders();
android.se.omapi.ISecureElementReader getReader(in String reader);
- boolean[] isNFCEventAllowed(in 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/omapi/aidl/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
index 61ae481..13707ec 100644
--- a/omapi/aidl/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
@@ -52,7 +52,7 @@
* Checks if the application defined by the package name is allowed to
* receive NFC transaction events for the defined AID.
*/
- boolean[] isNFCEventAllowed(in 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/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 84fb0e6..0000000
--- 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 1d841b4..db2dd2f 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.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 c3cb335..0000000
--- 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 7c4082c..0000000
--- 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 0d43a19..0000000
--- 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 a85b2e4..0000000
--- 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 9f07317..126b823 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.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 @@
AssociationRequest mRequest;
List<DeviceFilterPair> mDevicesFound;
DeviceFilterPair mSelectedDevice;
- IFindDeviceCallback mFindCallback;
+ IAssociationRequestCallback mApplicationCallback;
AndroidFuture<String> mServiceCallback;
boolean mIsScanning = false;
@@ -104,13 +104,13 @@
@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 @@
//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 801b490..a3eb0ecc 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -251,7 +251,7 @@
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 b8ad321..0b436a9 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 @@
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 @@
}
}
return btClass != null
- && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP);
+ && doesClassMatch(btClass, BluetoothClass.PROFILE_OPP);
}
}
@@ -151,7 +152,7 @@
}
}
return btClass != null
- && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU);
+ && doesClassMatch(btClass, BluetoothClass.PROFILE_PANU);
}
}
@@ -165,7 +166,12 @@
}
}
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 1df1bce..389892e 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 @@
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 253629c..c9af4d5 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 @@
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 @@
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 @@
}
}
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 @@
}
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 912b468..a901160 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 @@
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 @@
}
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 80b03a4..e7a6b32 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 @@
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 void setDiscoverableTimeout(int timeout) {
- mAdapter.setDiscoverableTimeout(timeout);
+ mAdapter.setDiscoverableTimeout(Duration.ofSeconds(timeout));
}
public long getDiscoveryEndMillis() {
@@ -156,7 +158,9 @@
}
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 9d4669a..cd5c78d 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 @@
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 949b245..c34f65c 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 androidx.annotation.VisibleForTesting;
import com.android.settingslib.R;
-import com.android.settingslib.bluetooth.BluetoothUtils;
import java.util.List;
@@ -61,7 +60,7 @@
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 b6c0b30..1139d33 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 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 Drawable getIcon() {
final Drawable drawable = getIconWithoutBackground();
setColorFilter(drawable);
- return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
+ return drawable;
}
@Override
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index a944bf5..71accc4 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 @@
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,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 63acffb..84e9d28 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -117,6 +117,7 @@
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);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 8c669d2..86ee3b3 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
contentUri,
movedToGlobal,
movedToSecure,
+ movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
settingsToPreserve);
@@ -737,8 +746,9 @@
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 @@
contentUri,
movedToGlobal,
movedToSecure,
+ movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
settingsToPreserve);
@@ -760,8 +771,9 @@
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 @@
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 @@
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 5d75d4f..a67b565 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 @@
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 @@
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 ce7517f..11e4916 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 @@
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 @@
if (sGlobalMovedToSecureSettings.contains(name)) {
table = TABLE_SECURE;
}
+
+ if (sGlobalMovedToSystemSettings.contains(name)) {
+ table = TABLE_SYSTEM;
+ }
}
return table;
@@ -3594,7 +3604,7 @@
}
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 @@
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 @@
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 4aee164..aa6661b 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 @@
}
// 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 @@
}
// 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 f5334fb..433aac7 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 @@
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 abd010d..aefe3b7 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" />
@@ -297,9 +296,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 8c70112..ee9d430 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -150,6 +150,7 @@
// 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 @@
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 @@
}
/**
+ * 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) {
+ private void stopProgressLocked(int id, boolean cancelNotification) {
if (mBugreportInfos.indexOfKey(id) < 0) {
Log.w(TAG, "ID not watched: " + id);
} else {
@@ -821,8 +844,13 @@
}
// 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 @@
}
/**
- * 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
}
synchronized (mLock) {
// ... and stop watching this process.
- stopProgressLocked(id);
+ stopProgressLocked(id, cancelNotificationWhenStoppingProgress);
}
}
@@ -1240,12 +1311,6 @@
// 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 @@
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 @@
}
/**
+ * 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 ecd1369..a44e236 100644
--- a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -54,9 +54,11 @@
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 @@
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 9aad278..e5726b0 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.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 @@
// 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 @@
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 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 @@
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)
+
+ 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,
+
+ // We give it the size and gravity of its original dialog window.
+ FrameLayout.LayoutParams(
+ originalDialogWindow.attributes.width,
+ originalDialogWindow.attributes.height,
+ originalDialogWindow.attributes.gravity
+ )
+ )
}
- // 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,
+ val dialogContentWithBackground = this.dialogContentWithBackground!!
- // We give it the size of its original dialog window.
- FrameLayout.LayoutParams(
- originalDialog.window.attributes.width,
- originalDialog.window.attributes.height,
- Gravity.CENTER
- )
- )
+ // Make the dialog and its background invisible for now, to make sure it's not drawn yet.
+ dialogContentWithBackground.visibility = View.INVISIBLE
- // Make the dialog view parent invisible for now, to make sure it's not drawn yet.
- dialogContentParent.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 @@
oldRight: Int,
oldBottom: Int
) {
- dialogContentParent.removeOnLayoutChangeListener(this)
+ dialogContentWithBackground.removeOnLayoutChangeListener(this)
isOriginalDialogViewLaidOut = true
maybeStartLaunchAnimation()
@@ -523,6 +550,25 @@
})
}
+ 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 @@
// 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 @@
(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 @@
) {
// 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 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/docs/dialogs.md b/packages/SystemUI/docs/dialogs.md
new file mode 100644
index 0000000..e5b4edd
--- /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 51f8516..4bfe734 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/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
index e09bf7e..625ce1f 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 e09bf7e..4daa648 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 6176f7c..6194aa0 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 0000000..7ef5299
--- /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 0000000..3a228d5
--- /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 0000000..86f8b42
--- /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 0000000..8c23659
--- /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 0000000..09dee95
--- /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 0000000..4e17e48
--- /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 0000000..81fb92c
--- /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/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml
new file mode 100644
index 0000000..d034f4e
--- /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 98518c2..4a5b637 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -185,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">
@@ -227,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"
@@ -249,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">
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index 868331e..f6a2136 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/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index a64ef3e..4e2b4a6 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 a5a7efa..2f5f8d6 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 ee4530c..9368a6a 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/tile_service_request_dialog.xml b/packages/SystemUI/res/layout/tile_service_request_dialog.xml
index b431d44..3a8a69c 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/colors.xml b/packages/SystemUI/res/values/colors.xml
index c5b47d0..2b16ec2 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 56464e4..8b59d86 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 aee1f43..a437ae6 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>
@@ -1291,4 +1295,6 @@
<!-- ************************************************************************* -->
<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 21b4a42..300cb2d3 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 b6cdd1b..2c79919 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -373,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">
@@ -454,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>
@@ -654,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" />
@@ -847,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">
@@ -984,4 +979,14 @@
<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 ed29bc7..5fdb497 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 07ad0c8..8aa3aba 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 @@
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 @@
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/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 3128ffd..675dc9b5 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.Parcelable;
import android.view.ViewDebug;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.PrintWriter;
@@ -50,6 +51,7 @@
@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 @@
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 @@
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 cbf7397..857cc462 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.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.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 @@
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 @@
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 @@
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 @@
}
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);
+ final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
+ mWindowManager.addView(mKeyButtonContainer, layoutParams);
- 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();
-
- updateTranslation(mPosition, /* animate */ false);
-
- mWindowManager.addView(mKeyButtonContainer, lp);
if (mAnimatedDrawable != null) {
mAnimatedDrawable.reset();
mAnimatedDrawable.start();
@@ -232,6 +239,53 @@
}
}
+ /**
+ * 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 c5f8fc1..a4b6451 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 @@
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 @@
}
}
+ @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 2dbd5de..78867f7 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 @@
* 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 3ebd652..986f296 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -479,9 +479,7 @@
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 abd89b9..172c7f6 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.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.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.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.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 @@
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 @@
// 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 @@
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
+ private GlobalSettings mGlobalSettings;
private AlertDialog mAlertDialog;
private boolean mSwipeUpToRetry;
@@ -110,10 +124,8 @@
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 @@
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;
+
+ 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();
}
- /** Returns whether this security container is in one-handed mode. */
- public boolean isOneHandedMode() {
- return mOneHandedMode;
+ 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");
+ }
+ }
+
+ private void finishSetup() {
+ if (mSecurityViewFlipper == null || mGlobalSettings == null) return;
+
+ mModeLogic.init(this, mGlobalSettings, mSecurityViewFlipper);
+ }
+
+ @Mode int getMode() {
+ return mCurrentMode;
}
/**
- * 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.
+ * 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.
*/
- public void setOneHandedModeLeftAligned(boolean leftAligned, boolean animate) {
- mIsSecurityViewLeftAligned = leftAligned;
- updateSecurityViewLocation(animate);
+ void updatePositionByTouchX(float x) {
+ mModeLogic.updatePositionByTouchX(x);
}
/** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
public boolean isOneHandedModeLeftAligned() {
- return mIsSecurityViewLeftAligned;
- }
-
- 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);
- }
-
- /**
- * 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 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);
- }
- });
-
- mRunningOneHandedAnimator.start();
- } else {
- mSecurityViewFlipper.setTranslationX(targetTranslation);
- }
+ return mCurrentMode == MODE_ONE_HANDED
+ && ((OneHandedModeLogic) mModeLogic).isLeftAligned();
}
public void onPause() {
@@ -526,7 +428,7 @@
}
} else {
if (!mIsDragging) {
- handleTap(event);
+ mModeLogic.handleTap(event);
}
}
}
@@ -541,36 +443,6 @@
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 void onFinishInflate() {
super.onFinishInflate();
mSecurityViewFlipper = findViewById(R.id.view_flipper);
+
+ finishSetup();
}
@Override
@@ -685,20 +559,15 @@
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 @@
// 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 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 d4d3d5b..4035229 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.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.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 @@
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 @@
// 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 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 @@
SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
- FalsingCollector falsingCollector) {
+ FalsingCollector falsingCollector,
+ GlobalSettings globalSettings) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -245,6 +247,7 @@
mConfigurationController = configurationController;
mLastOrientation = getResources().getConfiguration().orientation;
mFalsingCollector = falsingCollector;
+ mGlobalSettings = globalSettings;
}
@Override
@@ -324,7 +327,7 @@
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 @@
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();
+ private boolean canDisplayUserSwitcher() {
+ return getResources().getBoolean(R.bool.bouncer_display_user_switcher);
+ }
- mView.setOneHandedMode(oneHandedMode);
-
- 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 @@
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 @@
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final ConfigurationController mConfigurationController;
private final FalsingCollector mFalsingCollector;
+ private final GlobalSettings mGlobalSettings;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -622,7 +618,8 @@
KeyguardStateController keyguardStateController,
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
- FalsingCollector falsingCollector) {
+ FalsingCollector falsingCollector,
+ GlobalSettings globalSettings) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -634,6 +631,7 @@
mSecurityViewFlipperController = securityViewFlipperController;
mConfigurationController = configurationController;
mFalsingCollector = falsingCollector;
+ mGlobalSettings = globalSettings;
}
public KeyguardSecurityContainerController create(
@@ -642,7 +640,7 @@
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 69328cd..bacd29f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -94,13 +94,4 @@
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/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 68132f4..b2ecc614 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -18,6 +18,7 @@
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.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 @@
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 8a99728..251c1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -121,7 +121,8 @@
.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 @@
.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 c941d66..e4e0da6 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 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 794b9dd5..a10efa9 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 @@
@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 1bfa9c1..dc1e005 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 @@
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 @@
}
/**
- * 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 @@
*/
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 @@
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 @@
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 92cd8b1..2133da2 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 @@
}
@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 2507004..b064ba9 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 @@
@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 @@
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 @@
// 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 @@
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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
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 cff6cf1..cc5a792 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 @@
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- init();
+ mIsKeyguardVisible = false;
+ mIsAccessibilityManagerServiceReady = false;
}
/**
@@ -124,9 +125,8 @@
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 0ff3dbc..3f077f5 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 @@
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 @@
mConfigurationController = configurationController;
mSystemClock = systemClock;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mLowTick = lowTick();
+ mTick = lowTick();
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -607,22 +610,31 @@
}
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 @@
mVibrator.vibrate(
Process.myUid(),
mContext.getOpPackageName(),
- mLowTick,
+ mTick,
"udfps-onStart-tick",
VIBRATION_ATTRIBUTES);
}
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 0000000..b5d81f2
--- /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 5fdf026..38e4d78 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.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.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 @@
@BindsInstance
Builder setSizeCompatUI(Optional<SizeCompatUI> s);
+ @BindsInstance
+ Builder setDragAndDrop(Optional<DragAndDrop> d);
+
SysUIComponent build();
}
@@ -126,6 +131,8 @@
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 @@
*/
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 a64351f..1d17fd8 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.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.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 @@
@Module(includes = {
AppOpsModule.class,
AssistModule.class,
+ BiometricsModule.class,
ClockModule.class,
CommunalModule.class,
DreamModule.class,
@@ -127,7 +128,6 @@
},
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 543ba8f..90a3ad2 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.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 @@
@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 0000000..42f3512
--- /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 0000000..159ed39
--- /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 0000000..2874929
--- /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 a1413f9..458cdc1f 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 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 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 2f7c8ba..ff14064 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 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.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.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.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.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 @@
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 @@
@Main Handler handler,
PackageManager packageManager,
Optional<StatusBar> statusBarOptional,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -377,6 +377,7 @@
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBarOptional = statusBarOptional;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -435,11 +436,14 @@
}
/**
- * 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 @@
mDialog.dismiss();
mDialog = null;
} else {
- handleShow();
+ handleShow(view);
}
}
@@ -483,7 +487,7 @@
}
}
- protected void handleShow() {
+ protected void handleShow(@Nullable View view) {
awakenIfNecessary();
mDialog = createDialog();
prepareDialog();
@@ -493,8 +497,13 @@
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 @@
}
}
- protected void onRotate() {
+ protected void onRefresh() {
// re-allocate actions between main and overflow lists
this.createActionItems();
}
@@ -667,7 +676,7 @@
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 @@
}
@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 @@
mDialog.refreshDialog();
}
}
+
/**
* Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is
* called when the quick access wallet requests dismissal.
@@ -1363,6 +1365,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
}
@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 @@
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 @@
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 @@
message.animate()
.alpha(0f)
.setDuration(TOAST_FADE_TIME)
- .setStartDelay(visibleTime);
+ .setStartDelay(visibleTime)
+ .setListener(null);
}
});
}
@@ -2423,122 +2449,32 @@
@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();
- });
- }
+ dismissOverflow();
+ dismissPowerOptions();
- 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);
- }
-
- 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 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 c4508e0..96ae646 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 @@
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/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 47ef5e4..d54b151 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 @@
}
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 @@
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 57ac9df..237d077 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 @@
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 113ba59..5fa66cd 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 868193b..dc4aaa9 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.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 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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
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 26ce645..91d0b49 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.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 @@
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 @@
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 @@
// 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 @@
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 6da4d48..eef5fb0 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 @@
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 @@
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 eca8ac9..7696a1f 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 @@
}
@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 a201c07..104ddf9 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 @@
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 @@
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 1300400..f1c6601 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 @@
/**
* 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 @@
}
@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 0000000..85e5b33
--- /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 0000000..03bc935
--- /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 52103d3..25337b6 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 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.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 @@
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 @@
}
};
+ /**
+ * @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 @@
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 @@
}
/**
+ * @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 e0da9a0..8026df7 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.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.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 @@
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 @@
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/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 680cc5c..7c8c3e0 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 @@
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 @@
}
}
- @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 @@
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 @@
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 3d58a5a..8fb394c 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.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;
@@ -192,7 +191,7 @@
mEdgeBackGestureHandler.onNavigationModeChanged(
mNavigationModeController.addListener(this));
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
mEdgeBackGestureHandler.onNavBarAttached();
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
@@ -265,7 +264,8 @@
@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) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index fce0c0c..e10e4d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -133,7 +133,7 @@
}
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
- globalActionsDialog.showOrHideDialog(false, true)
+ globalActionsDialog.showOrHideDialog(false, true, v)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index e42c47b..ee59ae6 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 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 6d1bbee..48255b5 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.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 @@
/** */
@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 baf3018..11c4949 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 @@
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 @@
*/
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 @@
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 0000000..e0f9cc2
--- /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 210ee93..73d6b97 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 @@
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 @@
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 @@
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 16be998..ac95bf5 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.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 @@
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 @@
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 @@
mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
mQRCodeScannerTileProvider = qrCodeScannerTileProvider;
mOneHandedModeTileProvider = oneHandedModeTileProvider;
+ mFgsManagerTileProvider = fgsManagerTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -233,6 +237,8 @@
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 b1cd03c..106a1b6 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 @@
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 09fad30..6bb79867 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 @@
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 @@
"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/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
new file mode 100644
index 0000000..75cf4d1
--- /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 521dbe71..7e17124 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 @@
@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 883552a..033fe1e 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
@@ -130,6 +130,7 @@
private boolean mCanConfigMobileData;
// Wi-Fi entries
+ private int mWifiNetworkHeight;
@VisibleForTesting
protected WifiEntry mConnectedWifiEntry;
@VisibleForTesting
@@ -187,6 +188,9 @@
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);
@@ -335,9 +339,6 @@
mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
mWiFiToggle.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
- if (isChecked) {
- mWifiScanNotifyLayout.setVisibility(View.GONE);
- }
buttonView.setChecked(isChecked);
mWifiManager.setWifiEnabled(isChecked);
});
@@ -390,6 +391,8 @@
array.recycle();
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+ mMobileToggleDivider.setVisibility(
+ mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
}
}
@@ -427,9 +430,26 @@
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/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index a7f8bca..7bcaf5f 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 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 @@
private GestureDetector mSwipeDetector;
private SwipeDismissHandler mSwipeDismissHandler;
private InputMonitorCompat mInputMonitor;
+ private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private boolean mShowScrollablePreview;
private String mPackageName = "";
@@ -302,8 +304,8 @@
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 @@
mInputMonitor.dispose();
mInputMonitor = null;
}
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
}
@Override // ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 190c773e..f23a7ca 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 @@
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 5635f65..2b5453a 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.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.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 @@
}
@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 ecde001..00e7a03 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.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 @@
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 void setUpWithPresenter(NotificationPresenter presenter,
+ NotifStackController stackController,
NotificationListContainer listContainer) {
mPresenter = presenter;
+ mStackController = stackController;
mListContainer = listContainer;
if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
mDynamicPrivacyController.addListener(this);
@@ -328,6 +335,7 @@
mTmpChildOrderMap.clear();
updateRowStatesInternal();
+ updateNotifStats();
mListContainer.onNotificationViewUpdateFinished();
@@ -335,6 +343,56 @@
}
/**
+ * 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 @@
@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 cde3b0e..31ab6bd 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.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 @@
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/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0fb9fc8..1432f78 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 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 @@
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/NotificationFadeAware.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java
new file mode 100644
index 0000000..8d2e3c9
--- /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 0000000..1f2d0fe
--- /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 0ea6857..918843c 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 @@
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 @@
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 52c5c3e..6be8a49 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 @@
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 2d6522e..f8f1279 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 @@
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 @@
}
}
+ /**
+ * 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 4f3c287..4daed77 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 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 9ae9fe5..6fbed9a8 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.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 @@
* 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 @@
}
/**
+ * 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 @@
/**
* 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 @@
/**
* 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 72cd951..748c69d 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.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 @@
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 @@
@Inject
public ShadeListBuilder(
SystemClock systemClock,
+ NotifPipelineFlags flags,
ShadeListBuilderLogger logger,
DumpManager dumpManager,
NotificationInteractionTracker interactionTracker
@@ -126,6 +129,7 @@
Assert.isMainThread();
mSystemClock = systemClock;
mLogger = logger;
+ mAlwaysLogList = flags.isDevLoggingEnabled();
mInteractionTracker = interactionTracker;
dumpManager.registerDumpable(TAG, this);
@@ -253,6 +257,9 @@
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 @@
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 d013261..e9b7caa5 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 @@
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 @@
@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 @@
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 0000000..82b1268
--- /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 dae76f8..a16b565 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 @@
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 @@
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 @@
}
// 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 644f248..bbb97d1 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 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 @@
* 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 @@
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 @@
}
};
- private final OnBeforeFinalizeFilterListener mOnBeforeFinalizeFilterListener =
- entries -> inflateAllRequiredViews(entries);
-
private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
TAG + "InflationError") {
/**
@@ -256,7 +252,6 @@
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 @@
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 c60ebcd..57fd197 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.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 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 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 @@
@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 3397815..2608c30 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.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.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 @@
/** 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 0000000..c8f7360
--- /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 0000000..38f11fc
--- /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 c59f184..d98e7f7 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 @@
* 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 3290cdf..497691d 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 @@
* 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 66ec30d..f50038c 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 @@
@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 a8f3730..f9358eb 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.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 @@
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 @@
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 void initialize(
NotificationListener notificationService,
NotificationRowBinderImpl rowBinder,
- NotificationListContainer listContainer) {
+ NotificationListContainer listContainer,
+ NotifStackController stackController) {
mDumpManager.registerDumpable("NotifPipeline", this);
@@ -94,8 +101,11 @@
// 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 0000000..20cd6dd
--- /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 0000000..b1a4b65
--- /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 0000000..b5a0f7a
--- /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 b4389b9..7302de5 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 @@
})
}
+ 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 b9aa26f..86d263a 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 java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -91,7 +92,7 @@
@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 8182e73..f59e4ab 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 @@
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 0000000..e2edc01
--- /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 0000000..c10e401
--- /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 0000000..b6278d1
--- /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 c79f59b..fd91d5a 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 @@
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 @@
*/
@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 li
+ return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
}
- fun registerViewForEntry(entry: ListEntry, controller: NodeController) {
+ fun requireGroupController(entry: NotificationEntry): NotifGroupController {
if (DEBUG) {
- Log.d(TAG, "registerViewForEntry: $entry.key")
+ Log.d(TAG, "requireGroupController: ${entry.key}")
+ }
+ return rowMap[entry.key] ?: error("No view has been registered for entry: ${entry.key}")
+ }
+
+ 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: NotifViewController) {
+ if (DEBUG) {
+ 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 0000000..11d4e83
--- /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 0000000..1ea574b
--- /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 0000000..6e7f415
--- /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 0000000..a9c3987
--- /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 b582a24..1a8d720 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.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 @@
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 @@
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 2f496dd..a59d421 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.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 @@
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 999ef9c..212c342e 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.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
@@ -85,6 +86,7 @@
bubblesOptional: Optional<Bubbles>,
presenter: NotificationPresenter,
listContainer: NotificationListContainer,
+ stackController: NotifStackController,
notificationActivityStarter: NotificationActivityStarter,
bindRowCallback: NotificationRowBinderImpl.BindRowCallback
) {
@@ -112,7 +114,8 @@
newNotifPipeline.get().initialize(
notificationListener,
notificationRowBinder,
- listContainer)
+ listContainer,
+ stackController)
}
if (notifPipelineFlags.isNewPipelineEnabled()) {
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 eadf5ac..1c9af11 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.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 @@
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 9f10322..2fbd212 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.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.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 @@
* 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 @@
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 @@
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 @@
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
+ resetChildSystemExpandedStates();
}
}
}
@@ -2774,17 +2786,112 @@
// 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 @@
}
@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 0b29ae5..3d35d0e 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.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.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 @@
* 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 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 @@
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 fa2c1ee..4b3d6f7 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.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 @@
}
@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 b27a40a..e8e6e31 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.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 @@
}
@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 caba3ac..c661408 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 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 @@
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 bc2adac3..c0d85a6 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.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 @@
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 727f0e5..438992e 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.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 @@
* 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 @@
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 12e94cb..bb43b95 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 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 @@
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 @@
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 @@
}
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 3ef5b665..e136055 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.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 @@
)
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 @@
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 @@
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 4c9c2f9..fdff12d 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 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 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 8c6fa02..3159539 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.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 @@
}
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 7630191..6c3e0d2 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.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 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 a472710..1875124 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.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 @@
/**
* 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 @@
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 @@
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 @@
});
row.setSystemChildExpanded(false);
+ row.setNotificationFaded(false);
row.setUserLocked(false);
if (!row.isRemoved()) {
mGroupingUtil.restoreChildNotification(row);
@@ -473,7 +478,8 @@
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 @@
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 a97a54a..44e3718 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.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 @@
// 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 @@
}
}
- /**
- * 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 @@
@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 @@
}
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 @@
}
@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 @@
.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 @@
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 f7a3e3c..41a80c7 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.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.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 @@
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
+ private final NotifStackController mNotifStackController =
+ new NotifStackControllerImpl();
@Nullable
private NotificationActivityStarter mNotificationActivityStarter;
@@ -294,6 +299,8 @@
}
};
+ private NotifStats mNotifStats = NotifStats.getEmpty();
+
private void updateResources() {
mNotificationDragDownMovement = mResources.getDimensionPixelSize(
R.dimen.lockscreen_shade_notification_movement);
@@ -866,6 +873,14 @@
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 int getVisibleNotificationCount() {
- return mView.getVisibleNotificationCount();
+ return mNotifStats.getNumActiveNotifs();
}
public int getIntrinsicContentHeight() {
@@ -1183,7 +1198,7 @@
public void updateShowEmptyShadeView() {
mShowEmptyShadeView = mBarState != KEYGUARD
&& (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
- && mView.getVisibleNotificationCount() == 0;
+ && getVisibleNotificationCount() == 0;
mView.updateEmptyShadeView(
mShowEmptyShadeView,
@@ -1246,29 +1261,22 @@
}
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 @@
return mNotificationListContainer;
}
+ public NotifStackController getNotifStackController() {
+ return mNotifStackController;
+ }
+
public void resetCheckSnoozeLeavebehind() {
mView.resetCheckSnoozeLeavebehind();
}
@@ -1394,17 +1406,6 @@
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 @@
}
}
}
+
+ 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 6d82a45..83bea84 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.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 @@
} 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/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 927b4c8..8a7cf36 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.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.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 @@
@VisibleForTesting
float mExpandedHeight;
@VisibleForTesting
- boolean mIsExpanded;
- @VisibleForTesting
float mAppearFraction;
private ExpandableNotificationRow mTrackedChild;
private boolean mShown;
@@ -83,7 +88,7 @@
Point mPoint;
private KeyguardStateController mKeyguardStateController;
-
+ @Inject
public HeadsUpAppearanceController(
NotificationIconAreaController notificationIconAreaController,
HeadsUpManagerPhone headsUpManager,
@@ -92,11 +97,15 @@
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 @@
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 @@
// 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 @@
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
mNotificationIconAreaController.setIsolatedIconLocation(
- mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
+ mView.getIconDrawingRect(), requireStateUpdate);
}
@Override
@@ -184,20 +206,20 @@
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 @@
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 @@
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 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 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 @@
}
}
+ private boolean isExpanded() {
+ return mExpandedHeight > 0;
+ }
+
private void updateHeadsUpHeaders() {
mHeadsUpManager.getAllEntries().forEach(entry -> {
updateHeader(entry);
@@ -376,22 +402,13 @@
@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/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 3f33281..68ab077 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 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 16aac4d..8931874 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -480,9 +480,13 @@
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;
@@ -3198,18 +3202,24 @@
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 +3456,7 @@
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/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 9cefded..bf54677 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 @@
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 9e53378..84ef079 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.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.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 @@
private QSPanelController mQSPanelController;
private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
- private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory;
KeyguardIndicationController mKeyguardIndicationController;
private View mReportRejectedTouch;
@@ -666,7 +667,6 @@
private boolean mNoAnimationOnNextBarModeChange;
private final SysuiStatusBarStateController mStatusBarStateController;
- private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
protected StatusBarNotificationPresenter mPresenter;
@@ -737,6 +737,7 @@
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
+ AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -768,7 +769,6 @@
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
- PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
@@ -808,7 +808,6 @@
mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManagerPhone;
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
- mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory;
mKeyguardIndicationController = keyguardIndicationController;
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -842,6 +841,7 @@
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mNavigationBarController = navigationBarController;
+ mAccessibilityFloatingMenuController = accessibilityFloatingMenuController;
mAssistManagerLazy = assistManagerLazy;
mConfigurationController = configurationController;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -1057,6 +1057,8 @@
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 @@
}
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 @@
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 @@
mBubblesOptional,
mPresenter,
mStackScrollerController.getNotificationListContainer(),
+ mStackScrollerController.getNotifStackController(),
mNotificationActivityStarter,
mPresenter);
}
@@ -1909,10 +1893,6 @@
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) {
@@ -2318,7 +2298,8 @@
}
@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);
@@ -2359,14 +2340,12 @@
}
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() + "";
@@ -3839,6 +3818,7 @@
private final DeviceProvisionedController mDeviceProvisionedController;
private final NavigationBarController mNavigationBarController;
+ private final AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
// UI-specific methods
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 0d23d66..e2bf0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -555,12 +555,13 @@
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());
}
}
@@ -568,12 +569,13 @@
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());
}
}
@@ -1015,17 +1017,6 @@
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/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 9682c60..c8e1cdc 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 @@
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/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b3f59b4..33171b2 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.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.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 @@
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
NavigationBarController navigationBarController,
+ AccessibilityFloatingMenuController accessibilityFloatingMenuController,
Lazy<AssistManager> assistManagerLazy,
ConfigurationController configurationController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -217,7 +218,6 @@
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
- PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
PhoneStatusBarPolicy phoneStatusBarPolicy,
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
@@ -289,6 +289,7 @@
visualStabilityManager,
deviceProvisionedController,
navigationBarController,
+ accessibilityFloatingMenuController,
assistManagerLazy,
configurationController,
notificationShadeWindowController,
@@ -319,7 +320,6 @@
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 8f11819..beed60b 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
@@ -69,7 +69,7 @@
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";
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 2e3893a..2762b4c 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
@@ -329,8 +329,8 @@
}
protected int adjustDisableFlags(int state) {
- boolean headsUpVisible = mStatusBarOptionalLazy.get()
- .map(StatusBar::headsUpShouldBeVisible).orElse(false);
+ boolean headsUpVisible =
+ mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
if (headsUpVisible) {
state |= DISABLE_CLOCK;
}
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 47c1875..3656ed1 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 @@
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 @@
// 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 @@
@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 969361b..d244558 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 @@
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 @@
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 530da43..ef0a5b4 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 @@
private val uiEventLogger: UiEventLogger
) : RemoteInputViewController {
- private object Token
private val onSendListeners = ArraySet<OnSendRemoteInputListener>()
private val resources get() = view.resources
@@ -179,8 +178,8 @@
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 0000000..c6dbdb1
--- /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 0000000..d44d365
--- /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 85add6c..a537b2a 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 @@
private void clearLayoutLineCount(View view) {
if (view instanceof TextView) {
((TextView) view).nullLayouts();
+ view.forceLayout();
}
}
@@ -264,18 +265,29 @@
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 @@
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 @@
}
lp.show = true;
+ lp.mNoShowReason = "n/a";
displayedChildCount++;
if (lp.mButtonType == SmartButtonType.ACTION) {
numShownActions++;
@@ -349,6 +363,7 @@
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 @@
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 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.show = false;
lp.squeezeStatus = LayoutParams.SQUEEZE_STATUS_NONE;
+ lp.mNoShowReason = "reset";
}
}
@@ -590,6 +608,9 @@
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 @@
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 364c931..cdbdb23 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 @@
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 9f33c27..f952476 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
@@ -19,39 +19,15 @@
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))
- }
-}
-
-/**
- * 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.
- */
-fun PrintWriter.withIndenting(consumer: Consumer<IndentingPrintWriter>) {
- if (this is IndentingPrintWriter) {
- this.withIncreasedIndent { consumer.accept(this) }
- } else {
- consumer.accept(IndentingPrintWriter(this))
- }
-}
+fun PrintWriter.asIndenting(): IndentingPrintWriter =
+ (this as? IndentingPrintWriter) ?: IndentingPrintWriter(this)
/**
* Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on
@@ -66,6 +42,19 @@
}
}
+/**
+ * Run some code inside a block, with [IndentingPrintWriter.increaseIndent] having been called on
+ * the given argument, and calling [IndentingPrintWriter.decreaseIndent] after completion.
+ */
+fun IndentingPrintWriter.withIncreasedIndent(runnable: Runnable) {
+ this.increaseIndent()
+ try {
+ runnable.run()
+ } finally {
+ this.decreaseIndent()
+ }
+}
+
/** Return a readable string for the visibility */
fun visibilityString(@View.Visibility visibility: Int): String = when (visibility) {
View.GONE -> "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 63ca94c..6dd6d6d 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.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 @@
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 @@
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
Optional<ShellCommandHandler> shellCommandHandler,
Optional<SizeCompatUI> sizeCompatUIOptional,
+ Optional<DragAndDrop> dragAndDropOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -167,6 +170,7 @@
mProtoTracer = protoTracer;
mShellCommandHandler = shellCommandHandler;
mSizeCompatUIOptional = sizeCompatUIOptional;
+ mDragAndDropOptional = dragAndDropOptional;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -182,6 +186,7 @@
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi);
+ mDragAndDropOptional.ifPresent(this::initDragAndDrop);
}
@VisibleForTesting
@@ -396,6 +401,20 @@
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 6f2c565..ac1a83c 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 @@
// 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 @@
// 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 @@
// 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 64bdc2e..030464a 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 @@
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.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.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 @@
private Resources mResources;
@Mock
private FalsingCollector mFalsingCollector;
+ @Mock
+ private GlobalSettings mGlobalSettings;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -140,7 +144,7 @@
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
- mConfigurationController, mFalsingCollector)
+ mConfigurationController, mFalsingCollector, mGlobalSettings)
.create(mSecurityCallback);
}
@@ -178,30 +182,13 @@
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 @@
@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 @@
}
@Test
- public void showSecurityScreen_oneHandedMode_bothFlagsDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* 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_deviceFlagDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */false,
- /* 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(false);
+ verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings);
}
@Test
- public void showSecurityScreen_oneHandedMode_sysUiFlagDisabled_noOneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* 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_bothFlagsEnabled_oneHandedMode() {
- setUpKeyguardFlags(
- /* deviceConfigCanUseOneHandedKeyguard= */true,
- /* sysuiResourceCanUseOneHandedKeyguard= */true);
-
- when(mKeyguardSecurityViewFlipperController.getSecurityView(
- eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class)))
- .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
-
- mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
- verify(mView).setOneHandedMode(true);
- }
-
- @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 2efd369..c751081 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 @@
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 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 @@
@Mock
private WindowInsetsController mWindowInsetsController;
-
@Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
+ @Mock
+ private GlobalSettings mGlobalSettings;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@@ -83,7 +88,7 @@
@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 @@
@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 @@
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 @@
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 @@
}
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 @@
}
@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/systemui/InstanceIdSequenceFake.kt b/packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt
new file mode 100644
index 0000000..6fbe3ad
--- /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 326d902..796af11 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 @@
@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 8bb9d42..44770fa 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 @@
}
/**
- * 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 854fc33..3cc177d 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.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 java.util.concurrent.atomic.AtomicReference;
-
@Ignore
@LargeTest
@RunWith(AndroidTestingRunner.class)
@@ -74,6 +79,8 @@
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 @@
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 @@
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 @@
});
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 @@
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 @@
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 @@
});
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 @@
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 @@
});
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 9a30465..8fdcadd 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 @@
@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 @@
@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 @@
mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState);
mInstrumentation.runOnMainSync(() -> {
- controller.enableWindowMagnification(Float.NaN, Float.NaN,
+ controller.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
@@ -195,7 +195,7 @@
@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 @@
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 @@
@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 @@
}).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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 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 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 @@
@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 @@
@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 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 @@
@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 @@
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 cdf40a1..8ca17b9 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 @@
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/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 3b1c5f3..bf5522c 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.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 @@
@Mock private UserContextProvider mUserContextProvider;
@Mock private StatusBar mStatusBar;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
private TestableLooper mTestableLooper;
@@ -159,8 +161,8 @@
mHandler,
mPackageManager,
Optional.of(mStatusBar),
- mKeyguardUpdateMonitor
- );
+ mKeyguardUpdateMonitor,
+ mDialogLaunchAnimator);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
@@ -218,7 +220,7 @@
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 @@
// 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/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 52173c1..1b5e5eb 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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 053851e..4dac6d5 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.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 @@
private MediaOutputController mMediaOutputController;
private int mHeaderIconRes;
private IconCompat mIconCompat;
+ private Drawable mAppSourceDrawable;
private CharSequence mHeaderTitle;
private CharSequence mHeaderSubtitle;
@@ -173,6 +175,11 @@
}
@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 09ec4ca..d71d98e 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 @@
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 @@
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 ca5d570..2c883a7 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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..efb4931
--- /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 734faec..a445d6f 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 @@
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.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.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
import dagger.Lazy;
@RunWith(AndroidJUnit4.class)
@@ -80,10 +84,10 @@
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 @@
@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 @@
@Test
public void assistantCallbacksFiredAfterConnecting() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -133,7 +137,7 @@
@Test
public void a11yCallbacksFiredAfterModeChange() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -146,7 +150,7 @@
@Test
public void assistantCallbacksFiredAfterNavModeChange() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -159,7 +163,7 @@
@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/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index e038b6e..5003013 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.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.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.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
import java.util.Optional;
@@ -135,7 +138,6 @@
EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
@Mock
EdgeBackGestureHandler mEdgeBackGestureHandler;
- @Mock
NavBarHelper mNavBarHelper;
@Mock
private LightBarController mLightBarController;
@@ -179,6 +181,12 @@
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/external/TileRequestDialogEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
new file mode 100644
index 0000000..64796f1
--- /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 d49673d..f56a185 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 @@
}
@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 70e971c..a1c60a6 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 @@
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index b32b4d4..339d5bb 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 @@
}
@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 @@
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 @@
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/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index b0f2a89..4213b07 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.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 @@
@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 @@
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 3972f14..83f1d87 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.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 @@
@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 @@
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/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 8e6bcb01..41163bf 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 @@
}
@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 @@
}
@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 @@
// 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 cf7174e..287f50c 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 @@
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 @@
@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 b254ed4..82cd9fa 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.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 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 @@
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 @@
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 0000000..929c3d4
--- /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 c3e10aa..f70330d 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 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 @@
@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 @@
@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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
@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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 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 @@
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 abe33aa..f4d8405 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 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.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 @@
@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 @@
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
- mAdjustmentProvider,
+ mSectionClassifier,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -99,6 +100,7 @@
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 0000000..52fce13
--- /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 0000000..70266e4
--- /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 6313d3a..5271745 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 @@
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 0000000..70d309b
--- /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 185d9cd..9be2837 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 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 @@
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 @@
}
@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 @@
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 @@
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/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index bafbccd..db5fd26 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 @@
}
@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 @@
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 @@
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/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 1df576e..a34d2f5 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.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 @@
@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 @@
@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 @@
mVisualStabilityManager,
mDeviceProvisionedController,
mNavigationBarController,
+ mAccessibilityFloatingMenuController,
() -> mAssistManager,
configurationController,
mNotificationShadeWindowController,
@@ -446,7 +448,6 @@
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 609d69c..526f5b7 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.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,6 +47,7 @@
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;
@@ -62,10 +62,11 @@
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 org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -82,7 +83,6 @@
// Set in instantiate()
private StatusBarIconController mStatusBarIconController;
private NetworkController mNetworkController;
- private StatusBarStateController mStatusBarStateController;
private KeyguardStateController mKeyguardStateController;
private final StatusBar mStatusBar = mock(StatusBar.class);
@@ -90,8 +90,16 @@
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 +107,35 @@
@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 +147,21 @@
}
@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 +173,7 @@
@Test
public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
- mFragments.dispatchResume();
- processAllMessages();
- CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -199,9 +187,7 @@
@Test
public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
- mFragments.dispatchResume();
- processAllMessages();
- CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -214,9 +200,7 @@
@Test
public void disable_ongoingCallEnded_chipHidden() {
- mFragments.dispatchResume();
- processAllMessages();
- CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mOngoingCallController.hasOngoingCall()).thenReturn(true);
@@ -234,41 +218,95 @@
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;
-
- fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
-
- Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE));
-
- reset(mStatusBarStateController);
+ public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
+ CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ 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());
+
+ 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,7 +332,7 @@
new StatusBarHideIconsForBouncerManager(
mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()),
mKeyguardStateController,
- mock(NotificationPanelViewController.class),
+ mNotificationPanelViewController,
mNetworkController,
mStatusBarStateController,
() -> Optional.of(mStatusBar),
@@ -306,6 +344,13 @@
mOperatorNameViewControllerFactory);
}
+ private void setUpDaggerComponent() {
+ when(mStatusBarFragmentComponentFactory.create(any()))
+ .thenReturn(mStatusBarFragmentComponent);
+ when(mStatusBarFragmentComponent.getHeadsUpAppearanceController())
+ .thenReturn(mHeadsUpAppearanceController);
+ }
+
private void setUpNotificationIconAreaController() {
mMockNotificationAreaController = mock(NotificationIconAreaController.class);
@@ -324,4 +369,18 @@
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 de2012a..25fafa1 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 @@
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 ae7afce..1e15d2a 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.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 @@
@Mock ShellCommandHandler mShellCommandHandler;
@Mock SizeCompatUI mSizeCompatUI;
@Mock ShellExecutor mSysUiMainExecutor;
+ @Mock DragAndDrop mDragAndDrop;
@Before
public void setUp() {
@@ -87,6 +89,7 @@
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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 572cfdc..52a6dc1 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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
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 @@
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 e9f5870..bcb3413 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 @@
@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 8c3ca34..9324e3e 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 @@
private String mTargetAssignedToAccessibilityButton;
private boolean mBindInstantServiceAllowed;
+ private boolean mIsAudioDescriptionByDefaultRequested;
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
@@ -411,6 +412,10 @@
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 @@
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 @@
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 946d22e4..86777a2 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 @@
// 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 327f087..42a81e1 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_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 @@
private final PointF mTempPoint = new PointF();
private final Object mLock;
private final Context mContext;
+ @GuardedBy("mLock")
private final SparseArray<DisableMagnificationCallback>
mMagnificationEndRunnableSparseArray = new SparseArray();
@@ -208,7 +211,7 @@
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 {
@@ -218,6 +221,77 @@
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;
@@ -267,7 +341,8 @@
// 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);
}
}
@@ -282,6 +357,7 @@
mLastActivatedMode = mActivatedMode;
}
logMagnificationModeWithImeOnIfNeeded();
+ disableWindowMagnificationIfNeeded(displayId);
} else {
logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
@@ -293,6 +369,14 @@
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) {
@@ -518,15 +602,17 @@
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
@@ -544,7 +630,9 @@
applyMagnificationModeLocked(mTargetMode);
}
updateMagnificationButton(mDisplayId, mTargetMode);
- mTransitionCallBack.onResult(mDisplayId, success);
+ if (mTransitionCallBack != null) {
+ mTransitionCallBack.onResult(mDisplayId, success);
+ }
}
}
@@ -569,7 +657,9 @@
setExpiredAndRemoveFromListLocked();
applyMagnificationModeLocked(mCurrentMode);
updateMagnificationButton(mDisplayId, mCurrentMode);
- mTransitionCallBack.onResult(mDisplayId, true);
+ if (mTransitionCallBack != null) {
+ mTransitionCallBack.onResult(mDisplayId, true);
+ }
}
}
@@ -580,9 +670,13 @@
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 2324a5a..dda1c4f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -98,6 +98,10 @@
*/
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);
@@ -114,6 +118,21 @@
}
/**
+ * 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.
*
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 5277425..25dcc2a 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 @@
}
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 7d8f545..820be28 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 @@
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 @@
final DetectingState mDetectingState;
@VisibleForTesting
final PanningScalingGestureState mObservePanningScalingState;
+ @VisibleForTesting
+ final ViewportDraggingState mViewportDraggingState;
@VisibleForTesting
State mCurrentState;
@@ -105,6 +108,7 @@
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 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 @@
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 @@
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 @@
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 @@
}
}
+
+ /**
+ * 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 @@
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 @@
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 d34b4a9..9162064 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -20,12 +20,14 @@
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;
@@ -44,6 +46,9 @@
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
@@ -58,6 +63,25 @@
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
@@ -281,20 +305,60 @@
}
/**
- * 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.
+ */
+ public boolean enableWindowMagnification(int displayId, float scale, float centerX,
+ float centerY, @Nullable MagnificationAnimationCallback animationCallback,
+ @WindowPosition int windowPosition) {
final boolean enabled;
boolean previousEnabled;
synchronized (mLock) {
@@ -307,7 +371,7 @@
}
previousEnabled = magnifier.mEnabled;
enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
- animationCallback);
+ animationCallback, windowPosition);
}
if (enabled && !previousEnabled) {
@@ -662,6 +726,8 @@
// 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;
@@ -669,14 +735,17 @@
@GuardedBy("mLock")
boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
- @Nullable MagnificationAnimationCallback animationCallback) {
+ @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;
@@ -685,6 +754,19 @@
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) {
@@ -768,9 +850,15 @@
}
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/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index fd573d5..594140e 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.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 @@
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 @@
* 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 @@
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 @@
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 @@
* 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
- mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
+ mTransportConnectionManager.disposeOfTransportClient(transportConnection, callerLogString);
return result;
}
@@ -695,7 +697,7 @@
}
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 0000000..a3f6eb6
--- /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 ab87080..0000000
--- 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/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
deleted file mode 100644
index 72b1ee7..0000000
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.transport;
-
-import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
-import static com.android.server.backup.transport.TransportUtils.formatMessage;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.TransportManager;
-import com.android.server.backup.transport.TransportUtils.Priority;
-
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.function.Function;
-
-/**
- * Manages the creation and disposal of {@link TransportClient}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";
-
- 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 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}.
- */
- private static Intent getRealTransportIntent(ComponentName transportComponent) {
- 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,
- TransportStats transportStats) {
- this(userId, context, transportStats, TransportClientManager::getRealTransportIntent);
- }
-
- private TransportClientManager(@UserIdInt int userId, Context context,
- TransportStats transportStats, Function<ComponentName, Intent> intentFunction) {
- mUserId = userId;
- mContext = context;
- mTransportStats = transportStats;
- mIntentFunction = intentFunction;
- }
-
- /**
- * Retrieves a {@link TransportClient} 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
- * details.
- * @return A {@link TransportClient}.
- */
- public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
- return getTransportClient(transportComponent, null, caller);
- }
-
- /**
- * Retrieves a {@link TransportClient} 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
- * details.
- * @return A {@link TransportClient}.
- */
- public TransportClient getTransportClient(
- ComponentName transportComponent, @Nullable Bundle extras, String caller) {
- Intent bindIntent = mIntentFunction.apply(transportComponent);
- if (extras != null) {
- bindIntent.putExtras(extras);
- }
- return getTransportClient(transportComponent, caller, bindIntent);
- }
-
- private TransportClient getTransportClient(
- ComponentName transportComponent, String caller, Intent bindIntent) {
- synchronized (mTransportClientsLock) {
- TransportClient transportClient =
- new TransportClient(
- mUserId,
- mContext,
- mTransportStats,
- bindIntent,
- transportComponent,
- Integer.toString(mTransportClientsCreated),
- caller);
- mTransportClientsCallerMap.put(transportClient, caller);
- mTransportClientsCreated++;
- TransportUtils.log(
- Priority.DEBUG,
- TAG,
- formatMessage(null, caller, "Retrieving " + transportClient));
- return transportClient;
- }
- }
-
- /**
- * Disposes of the {@link TransportClient}.
- *
- * @param transportClient The {@link TransportClient} to be disposed of.
- * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
- * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
- * details.
- */
- public void disposeOfTransportClient(TransportClient transportClient, String caller) {
- transportClient.unbind(caller);
- transportClient.markAsDisposed();
- synchronized (mTransportClientsLock) {
- TransportUtils.log(
- Priority.DEBUG,
- TAG,
- formatMessage(null, caller, "Disposing of " + transportClient));
- mTransportClientsCallerMap.remove(transportClient);
- }
- }
-
- public void dump(PrintWriter pw) {
- 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()) {
- pw.println(" " + logEntry);
- }
- }
- }
- }
-}
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
similarity index 94%
rename from services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index 0eb3ea3..da77eba 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.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 @@
*
* @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 @@
@GuardedBy("mStateLock")
private volatile IBackupTransport mTransport;
- TransportClient(
+ TransportConnection(
@UserIdInt int userId,
Context context,
TransportStats transportStats,
@@ -127,7 +128,7 @@
}
@VisibleForTesting
- TransportClient(
+ TransportConnection(
@UserIdInt int userId,
Context context,
TransportStats transportStats,
@@ -144,7 +145,7 @@
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 @@
* 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 @@
}
/**
- * 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 @@
* 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 @@
// 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 1ccffd0..03d35e4 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 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/TransportConnectionManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionManager.java
new file mode 100644
index 0000000..16acb18
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionManager.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+import static com.android.server.backup.transport.TransportUtils.formatMessage;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Function;
+
+/**
+ * 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 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<TransportConnection, String> mTransportClientsCallerMap = new WeakHashMap<>();
+ private final Function<ComponentName, Intent> mIntentFunction;
+
+ /**
+ * Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link
+ * ComponentName}.
+ */
+ private static Intent getRealTransportIntent(ComponentName transportComponent) {
+ return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+ }
+
+ public TransportConnectionManager(@UserIdInt int userId, Context context,
+ TransportStats transportStats) {
+ this(userId, context, transportStats, TransportConnectionManager::getRealTransportIntent);
+ }
+
+ private TransportConnectionManager(@UserIdInt int userId, Context context,
+ TransportStats transportStats, Function<ComponentName, Intent> intentFunction) {
+ mUserId = userId;
+ mContext = context;
+ mTransportStats = transportStats;
+ mIntentFunction = intentFunction;
+ }
+
+ /**
+ * 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 TransportConnection#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportConnection}.
+ */
+ public TransportConnection getTransportClient(ComponentName transportComponent, String caller) {
+ return getTransportClient(transportComponent, null, caller);
+ }
+
+ /**
+ * 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 TransportConnection#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportConnection}.
+ */
+ public TransportConnection getTransportClient(
+ ComponentName transportComponent, @Nullable Bundle extras, String caller) {
+ Intent bindIntent = mIntentFunction.apply(transportComponent);
+ if (extras != null) {
+ bindIntent.putExtras(extras);
+ }
+ return getTransportClient(transportComponent, caller, bindIntent);
+ }
+
+ private TransportConnection getTransportClient(
+ ComponentName transportComponent, String caller, Intent bindIntent) {
+ synchronized (mTransportClientsLock) {
+ TransportConnection transportConnection =
+ new TransportConnection(
+ mUserId,
+ mContext,
+ mTransportStats,
+ bindIntent,
+ transportComponent,
+ Integer.toString(mTransportClientsCreated),
+ caller);
+ mTransportClientsCallerMap.put(transportConnection, caller);
+ mTransportClientsCreated++;
+ TransportUtils.log(
+ Priority.DEBUG,
+ TAG,
+ formatMessage(null, caller, "Retrieving " + transportConnection));
+ return transportConnection;
+ }
+ }
+
+ /**
+ * Disposes of the {@link TransportConnection}.
+ *
+ * @param transportConnection The {@link TransportConnection} to be disposed of.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportConnection#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ */
+ public void disposeOfTransportClient(TransportConnection transportConnection, String caller) {
+ transportConnection.unbind(caller);
+ transportConnection.markAsDisposed();
+ synchronized (mTransportClientsLock) {
+ TransportUtils.log(
+ Priority.DEBUG,
+ TAG,
+ formatMessage(null, caller, "Disposing of " + transportConnection));
+ mTransportClientsCallerMap.remove(transportConnection);
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("Transport clients created: " + mTransportClientsCreated);
+ synchronized (mTransportClientsLock) {
+ pw.println("Current transport clients: " + mTransportClientsCallerMap.size());
+ 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 c08eb7f..83b2782 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 @@
/**
* 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 bd84782..c67a5b65 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.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 1a5d91c..452adb2 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.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 @@
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 @@
}
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 @@
@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 @@
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 @@
}
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 @@
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 @@
// 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 @@
+ newTransportName
+ " not available: current token = 0"));
}
- mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+ mTransportManager.disposeOfTransportClient(transportConnection, callerLogString);
} else {
Slog.w(
TAG,
@@ -3946,9 +3946,9 @@
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 @@
mWakelock.acquire();
OnTaskFinishedListener listener = caller -> {
- mTransportManager.disposeOfTransportClient(transportClient, caller);
+ mTransportManager.disposeOfTransportClient(transportConnection, caller);
mWakelock.release();
};
@@ -3984,7 +3984,7 @@
Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE);
msg.obj =
RestoreParams.createForRestoreAtInstall(
- transportClient,
+ transportConnection,
/* observer */ null,
/* monitor */ null,
restoreSet,
@@ -4006,9 +4006,9 @@
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 @@
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 @@
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 @@
}
@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 @@
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 a4d47d4..1c86091 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.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 @@
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 @@
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 @@
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 @@
boolean userInitiated, BackupEligibilityRules backupEligibilityRules) {
super(observer);
this.mUserBackupManagerService = backupManagerService;
- mTransportClient = transportClient;
+ mTransportConnection = transportConnection;
mUpdateSchedule = updateSchedule;
mLatch = latch;
mJob = runningJob;
@@ -299,7 +301,7 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 1cb7c11..3b3bf8c6 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.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.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 @@
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 @@
OnTaskFinishedListener listener =
caller ->
transportManager
- .disposeOfTransportClient(transportClient, caller);
+ .disposeOfTransportClient(transportConnection,
+ caller);
KeyValueBackupTask.start(
backupManagerService,
- transportClient,
+ transportConnection,
transport.transportDirName(),
queue,
oldJournal,
@@ -240,7 +240,7 @@
}
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 @@
PerformUnifiedRestoreTask task =
new PerformUnifiedRestoreTask(
backupManagerService,
- params.transportClient,
+ params.mTransportConnection,
params.observer,
params.monitor,
params.token,
@@ -344,7 +344,7 @@
Runnable task =
new PerformClearTask(
backupManagerService,
- params.transportClient,
+ params.mTransportConnection,
params.packageInfo,
params.listener);
task.run();
@@ -365,7 +365,7 @@
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 @@
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 e417f06..30de509 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 @@
* 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 5ffa795..80bd604 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 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 @@
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 6b78fbf..7636ef6 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.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 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 @@
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 7267cdf..bdb2e6f 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.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 @@
* 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 static KeyValueBackupTask start(
UserBackupManagerService backupManagerService,
- TransportClient transportClient,
+ TransportConnection transportConnection,
String transportDirName,
List<String> queue,
@Nullable DataChangedJournal dataChangedJournal,
@@ -227,7 +227,7 @@
KeyValueBackupTask task =
new KeyValueBackupTask(
backupManagerService,
- transportClient,
+ transportConnection,
transportDirName,
queue,
dataChangedJournal,
@@ -245,7 +245,7 @@
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 @@
@VisibleForTesting
public KeyValueBackupTask(
UserBackupManagerService backupManagerService,
- TransportClient transportClient,
+ TransportConnection transportConnection,
String transportDirName,
List<String> queue,
@Nullable DataChangedJournal journal,
@@ -314,7 +314,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
}
}
- 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 @@
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 @@
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 @@
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 8002570..c8ed00b 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.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 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 dc3bba0..bc3b79b 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 @@
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 914e9ea..dbd06ee 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 @@
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 a08a1f8..1795a3cb 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.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 @@
* No kill after restore.
*/
public static RestoreParams createForSinglePackage(
- TransportClient transportClient,
+ TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
@@ -52,7 +49,7 @@
OnTaskFinishedListener listener,
BackupEligibilityRules eligibilityRules) {
return new RestoreParams(
- transportClient,
+ transportConnection,
observer,
monitor,
token,
@@ -68,7 +65,7 @@
* Kill after restore.
*/
public static RestoreParams createForRestoreAtInstall(
- TransportClient transportClient,
+ TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
@@ -78,7 +75,7 @@
BackupEligibilityRules backupEligibilityRules) {
String[] filterSet = {packageName};
return new RestoreParams(
- transportClient,
+ transportConnection,
observer,
monitor,
token,
@@ -94,14 +91,14 @@
* 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 @@
* 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 @@
OnTaskFinishedListener listener,
BackupEligibilityRules backupEligibilityRules) {
return new RestoreParams(
- transportClient,
+ transportConnection,
observer,
monitor,
token,
@@ -139,7 +136,7 @@
}
private RestoreParams(
- TransportClient transportClient,
+ TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
@@ -149,7 +146,7 @@
@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 d0a8881..8b1d561 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 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.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 @@
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 @@
// 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 @@
* 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 @@
// 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 f07bac8..8c786d5 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.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 @@
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 @@
PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
mListener = null;
mAgentTimeoutParameters = null;
- mTransportClient = null;
+ mTransportConnection = null;
mTransportManager = null;
mEphemeralOpToken = 0;
mUserId = 0;
@@ -181,7 +181,7 @@
// about releasing it.
public PerformUnifiedRestoreTask(
UserBackupManagerService backupManagerService,
- TransportClient transportClient,
+ TransportConnection transportConnection,
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long restoreSetToken,
@@ -198,7 +198,7 @@
mState = UnifiedRestoreState.INITIAL;
mStartRealtime = SystemClock.elapsedRealtime();
- mTransportClient = transportClient;
+ mTransportConnection = transportConnection;
mObserver = observer;
mMonitor = monitor;
mToken = restoreSetToken;
@@ -386,7 +386,8 @@
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 @@
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 @@
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 @@
try {
IBackupTransport transport =
- mTransportClient.connectOrThrow(
+ mTransportConnection.connectOrThrow(
"PerformUnifiedRestoreTask.initiateOneRestore()");
// Run the transport's restore pass
@@ -939,7 +940,7 @@
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 @@
// 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 @@
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 bfb6f65..652386f 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 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 @@
* </ol>
*/
public boolean appIsRunningAndEligibleForBackupWithTransport(
- @Nullable TransportClient transportClient,
+ @Nullable TransportConnection transportConnection,
String packageName) {
try {
PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageName,
@@ -236,10 +235,10 @@
|| 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 2645f3f..a0a00f7 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.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 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 @@
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);
- mFindDeviceCallback = callback;
- mRequest = request;
- mCallingPackage = callingPackage;
- request.setCallingPackage(callingPackage);
+ // 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;
+ }
- if (mayAssociateWithoutPrompt(callingPackage, userId)) {
+ // 2b. Launch the UI.
+ synchronized (mService.mLock) {
+ if (mRequest != null) {
+ Slog.w(TAG, "CDM is already processing another AssociationRequest.");
+
+ 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(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 @@
}
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 @@
}));
}
- 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);
- }
- }
+ private boolean willAddRoleHolder(@NonNull AssociationRequest request,
+ @NonNull String packageName, @UserIdInt int userId) {
+ final String deviceProfile = request.getDeviceProfile();
+ if (deviceProfile == null) return false;
- 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();
- }
- }
+ final boolean isRoleHolder = Binder.withCleanCallingIdentity(
+ () -> isRoleHolder(mContext, userId, packageName, deviceProfile));
- 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.");
- }
-
- if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
- throw new IllegalArgumentException("Unsupported device profile: " + 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 @@
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 @@
// 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 @@
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 d5357dc..049018cf 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.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.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.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.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.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 @@
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private PowerWhitelistManager mPowerWhitelistManager;
private IAppOpsService mAppOpsManager;
- private RoleManager mRoleManager;
private BluetoothAdapter mBluetoothAdapter;
private UserManager mUserManager;
@@ -202,7 +208,6 @@
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 @@
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 @@
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);
+ final int userId = getChangingUserId();
+ Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName);
- mCompanionDevicePresenceController.unbindDevicePresenceListener(
- packageName, userId);
+ clearAssociationForPackage(userId, packageName);
+ }
+
+ @Override
+ 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) {
- Slog.d(LOG_TAG, "onPackageModified(packageName = " + packageName + ")");
- int userId = getChangingUserId();
- forEach(getAllAssociations(userId, packageName), association -> {
- updateSpecialAccessPermissionForAssociatedPackage(association);
- });
- }
+ 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 @@
@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 @@
}
try {
- Set<AssociationInfo> associations = getAllAssociations(userId);
+ Set<AssociationInfo> associations = getAllAssociationsForUser(userId);
if (associations == null) {
continue;
}
@@ -325,67 +396,92 @@
}
@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 void stopScan(AssociationRequest request,
- IFindDeviceCallback callback,
- String callingPackage) {
- Slog.i(LOG_TAG, "stopScan(request = " + request + ")");
- mAssociationRequestsProcessor.stopScan(request, callback, callingPackage);
- }
-
- @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()));
- }
-
- @Override
- public List<AssociationInfo> getAssociationsForUser(int userId) {
- if (!callerCanManageCompanionDevices()) {
- throw new SecurityException("Caller must hold "
- + android.Manifest.permission.MANAGE_COMPANION_DEVICES);
+ 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);
}
- return new ArrayList<>(getAllAssociations(userId, null /* packageFilter */));
- }
+ 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());
+ }
- //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);
- }
-
- private boolean callerCanManageCompanionDevices() {
- return getContext().checkCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_COMPANION_DEVICES)
- == PERMISSION_GRANTED;
+ return new ArrayList<>(
+ CompanionDeviceManagerService.this.getAssociations(userId, packageName));
}
@Override
- public PendingIntent requestNotificationAccess(ComponentName component)
+ public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");
+
+ return new ArrayList<>(
+ CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
+ }
+
+ @Override
+ public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
+ int userId) {
+ enforceCallerCanInteractWithUserId(getContext(), userId);
+ enforceCallerCanManagerCompanionDevice(getContext(),
+ "addOnAssociationsChangedListener");
+
+ //TODO: Implement.
+ }
+
+ @Override
+ public void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
+ int userId) {
+ //TODO: Implement.
+ }
+
+ @Override
+ 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).");
+ }
+
+ disassociateInternal(userId, association.getId());
+ }
+
+ @Override
+ 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");
+ }
+
+ disassociateInternal(association.getUserId(), associationId);
+ }
+
+ @Override
+ 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 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 @@
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 @@
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 @@
}
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 @@
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 @@
}
}
- void checkCallerIsSystemOr(String pkg) throws RemoteException {
- checkCallerIsSystemOr(pkg, getCallingUserId());
+ /**
+ * @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);
}
- 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");
- }
-
- 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 @@
// 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 @@
}
}
- 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 @@
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 @@
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 @@
@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 @@
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 @@
}
}
- @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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 a79db2c..3e00846 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 @@
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 @@
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 e143f5e..5cb3079 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 @@
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 @@
switch (cmd) {
case "list": {
forEach(
- mService.getAllAssociations(getNextArgInt()),
+ mService.getAllAssociationsForUser(getNextArgInt()),
a -> getOutPrintWriter()
.println(a.getPackageName() + " "
+ a.getDeviceMacAddress()));
@@ -48,13 +49,19 @@
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 0000000..0d38fa3
--- /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 5b8d7e5..87558df 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.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 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 @@
* 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 @@
* <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 @@
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 @@
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 @@
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 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 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 @@
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 @@
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 0000000..76340fc
--- /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 9f22489..9351415 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -137,6 +137,7 @@
],
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 6ac015b..f3fad84 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -18,6 +18,7 @@
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 @@
// 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 @@
* @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 @@
* 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* 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 @@
* 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 @@
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 @@
* @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 @@
/**
* 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 0000000..9a9379e
--- /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 197321f..263ff18 100644
--- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -35,6 +35,7 @@
* 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 @@
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 f62935a..8860a81 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.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 @@
}
}
} 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 @@
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 3642e4d..e5854c9 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.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 @@
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 @@
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 @@
case BluetoothProfile.HEARING_AID:
mHearingAid = (BluetoothHearingAid) proxy;
break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudio = (BluetoothLeAudio) proxy;
+ break;
default:
break;
}
@@ -75,6 +81,9 @@
case BluetoothProfile.HEARING_AID:
mHearingAid = null;
break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudio = null;
+ break;
default:
break;
}
@@ -82,8 +91,8 @@
};
@VisibleForTesting
- public boolean isA2dpOrHearingAidConnected() {
- return isA2dpConnected() || isHearingAidConnected();
+ public boolean isMediaProfileConnected() {
+ return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected();
}
@VisibleForTesting
@@ -142,4 +151,12 @@
}
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 6f009b6..1929df8 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1223,7 +1223,7 @@
}
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 @@
|| 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 @@
&& 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 @@
} 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 @@
// 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 @@
}
@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 @@
// 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 @@
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 b068f86..0c990ec 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -141,7 +141,7 @@
* | 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/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6decdb9..7993936 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.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 @@
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 @@
}
}
- 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 02a16fc..8ec82bd 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.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 @@
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 @@
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 @@
@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 @@
synchronized(this) {
mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId,
- message, force, exceptionTypeId);
+ message, force, exceptionTypeId, extras);
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 0dfdfe9..6c1a00d 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.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.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 @@
* @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 @@
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 7b5f2cd..259dd8ec 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 @@
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 592abbb..91d6488 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 @@
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;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index eba02f10..c830554 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.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 @@
* 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 @@
}
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/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0bab023..e79cba1 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.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 @@
// 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
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 61b8ded..7341e74 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 @@
"Successful background authentication!");
}
- mAlreadyDone = true;
+ markAlreadyDone();
if (mTaskStackListener != null) {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
@@ -327,7 +327,7 @@
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 9764a16..b73e911 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 @@
// 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 @@
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 @@
}
}
+ /**
+ * 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 @@
// 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 @@
@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 361ec40..a358bc2 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 @@
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 74a21a7..beb4d5b 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 @@
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);
+ }
- // Display independent values
- float[] luxLevels = getLuxLevels(resources.getIntArray(
- com.android.internal.R.array.config_autoBrightnessLevels));
+ /**
+ * 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, 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 @@
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 0000000..4aafd14
--- /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 2ae5cbb..a9e1647 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 @@
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 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.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 @@
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 @@
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 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 @@
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 @@
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 @@
+ ", 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 @@
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 @@
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 1c62699..a36a1a91 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1120,50 +1120,211 @@
}
}
- private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
+ 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 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");
+ }
+ }
+
+ 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) {
- 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;
- }
-
- 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);
+ 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;
}
@@ -2726,116 +2887,8 @@
@Override // Binder call
public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
- return createVirtualDisplay(virtualDisplayConfig, callback, projection, packageName,
- null /* controller */);
- }
-
- public int createVirtualDisplay(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");
- }
- }
-
- 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, controller);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection,
+ packageName, null /* controller */);
}
@Override // Binder call
@@ -3265,60 +3318,6 @@
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) {
@@ -3655,6 +3654,14 @@
}
@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);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5f79f72..7f78cac 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.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 @@
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 @@
// 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 @@
}
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 b6d13e0..300f59e 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 @@
: 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 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 @@
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 1fa6241..7e71589 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 @@
// 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 d6ac25a..b23395f 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.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 @@
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 @@
// 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 @@
HdmiCecLocalDevicePlayback(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
+
+ mDelayedStandbyHandler = new Handler(service.getServiceLooper());
}
@Override
@@ -195,9 +205,33 @@
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 69f7af2..6dd9aa0 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.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.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 @@
private PowerManagerWrapper mPowerManager;
@Nullable
+ private PowerManagerInternalWrapper mPowerManagerInternal;
+
+ @Nullable
private Looper mIoLooper;
+ @Nullable
+ private DisplayManager mDisplayManager;
+
@HdmiControlManager.HdmiCecVersion
private int mCecVersion;
@@ -676,6 +684,11 @@
}, 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 @@
@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 @@
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 @@
assertRunOnServiceThread();
Slog.v(TAG, "onPendingActionsCleared");
- if (!mPowerStatusController.isPowerStatusTransientToStandby()) {
- return;
+ 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);
+ }
}
- mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY);
- for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) {
- device.onStandby(mStandbyMessageReceived, standbyAction);
- }
+
+ // 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 0000000..59671a8
--- /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 b8bb399..f70ad05 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 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 b676f28..efd3037 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.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.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 @@
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 @@
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 74630d1..9078f3f 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 @@
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 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 @@
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 7db234a..5fe7710 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 @@
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 @@
}
/**
+ * 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 @@
}
}
+ 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 6c1df7f..f114184 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.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 @@
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 @@
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 8460d67..1781588 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 @@
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 @@
@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 443a6c0a..dce9a12 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 @@
*/
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 34ce96f..90daf8c 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 5cb360b..4838752 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 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 @@
/**
* 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 @@
* 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 @@
* 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) {
- 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);
+ 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)) {
+ mHighPowerAttributions.put(identity, 1);
+ }
+ } else {
+ mHighPowerAttributions.put(identity, count + 1);
}
}
@@ -134,16 +105,18 @@
* 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 17efeb3..0ce24dd 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 @@
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
+ mLocationAttributionHelper.reportLocationStart(getIdentity());
}
onHighPowerUsageChanged();
@@ -413,7 +413,7 @@
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
+ mLocationAttributionHelper.reportLocationStop(getIdentity());
}
onProviderListenerInactive();
@@ -488,10 +488,10 @@
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 df1eb6d..d17dbde 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 @@
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 64f72c5..bf50db8 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 @@
? 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 f4b72a1..c876d41 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.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 @@
@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 e117cc6..99fdb2d 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 @@
}
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 5199ef6..be5f219 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 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/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index dca8654..6ec3405 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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
}
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 @@
// 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 @@
}
@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 @@
@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 @@
}
private final ArrayMap<ComponentName, ParsedProvider> mProviders = new ArrayMap<>();
- private int mFlags;
+ private long mFlags;
}
private static final class ServiceIntentResolver
@@ -1969,7 +1969,7 @@
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 @@
}
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 @@
// 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 a9df4ba..3e849f8 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 @@
}
@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 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 887dfff..2d61773 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.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.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 @@
@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 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 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 @@
}
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 @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 @@
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 @@
* 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 @@
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 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 @@
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 @@
* 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 @@
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 @@
}
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 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 @@
}
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 @@
}
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 @@
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 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 @@
* 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 @@
}
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
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 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 @@
* 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 @@
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 @@
// 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 @@
}
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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
// 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 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 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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
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 @@
// 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 @@
@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 @@
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 @@
@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 @@
@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 @@
}
@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 @@
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 801aaef..f180d19 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.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 @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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 ca17d66..a3cd092 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 @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 @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 @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 @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 @@
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 @@
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 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 @@
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 @@
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 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 @@
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 @@
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 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 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 @@
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 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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 6622a77..5ca0618 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 @@
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 @@
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);
@@ -3129,10 +3132,12 @@
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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index de1c2ad..4767d3a 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 8f14cf8..dbbc163 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.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.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 @@
}
@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(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 747bbfa5..9f5adcb 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.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 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 @@
}
@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 @@
}
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 @@
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 @@
// 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 @@
| 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 @@
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 @@
}
@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 @@
}
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 @@
}
@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 @@
.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 @@
* 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 @@
/**
* 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 @@
* 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 @@
}
@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 @@
* 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 @@
}
@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 @@
@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 @@
*/
@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 @@
@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 @@
}
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 @@
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
}
@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 @@
@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 @@
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 @@
@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 @@
}
@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 @@
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
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 6d031dd..3b643b5 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.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.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 @@
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 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 @@
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 @@
// 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 cb97e2a..8c91b16 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 @@
}
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 @@
// 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 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/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 70855a9..0ee1f89 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 @@
* 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 @@
}
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 @@
// 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 @@
}
- 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 @@
}
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 @@
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/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4a41047..6a163b2 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.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.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 void forceCurrent() {
sdkVersion = Build.VERSION.SDK_INT;
databaseVersion = CURRENT_DATABASE_VERSION;
- fingerprint = Build.FINGERPRINT;
+ fingerprint = PackagePartitions.FINGERPRINT;
}
}
@@ -4149,7 +4149,7 @@
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 @@
@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 @@
}
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 6b88081..1433abd 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 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 @@
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 @@
}
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 d54acb7..a5a8d5c 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 40d8845..bc4c8b0 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.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 @@
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 @@
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 @@
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 @@
}
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 @@
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 7f42374..328a55f 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 @@
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 4dbc5d5..6d68139 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 @@
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/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 0ab1d36..bcb5e72 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 @@
*/
@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 @@
* @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 @@
*/
@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 @@
*/
@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 @@
*/
@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 @@
*/
@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 @@
*/
@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 @@
*/
@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 @@
*/
@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 @@
// 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 @@
@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 @@
@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 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 @@
*/
@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 61fd5ee..32b1e5d 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 @@
* 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 6744ff5..09b9d31 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 @@
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 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 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 1f024ea..471f38a 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 @@
*/
@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 0fb8475..661e67d 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 @@
@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 246810f..12cce0d 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 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 131e587..f61b562 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -521,7 +521,6 @@
private boolean mPendingKeyguardOccluded;
private boolean mKeyguardOccludedChanged;
- private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
Intent mHomeIntent;
Intent mCarDockIntent;
Intent mDeskDockIntent;
@@ -1819,9 +1818,6 @@
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);
@@ -4488,7 +4484,6 @@
if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
if (displayId == DEFAULT_DISPLAY) {
- updateScreenOffSleepToken(true);
mRequestedOrSleepingDefaultDisplay = false;
mDefaultDisplayPolicy.screenTurnedOff();
synchronized (mLock) {
@@ -4521,7 +4516,6 @@
if (displayId == DEFAULT_DISPLAY) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
- updateScreenOffSleepToken(false);
mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
mBootAnimationDismissable = false;
@@ -5042,15 +5036,6 @@
}
}
- // 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() {
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 5ec2f83..4571cf3 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_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 @@
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 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/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
new file mode 100644
index 0000000..f519ced
--- /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 3182290..eb69ff7 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -64,6 +64,8 @@
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;
@@ -222,6 +224,7 @@
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -3413,9 +3416,12 @@
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);
@@ -3426,7 +3432,8 @@
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;
@@ -3439,6 +3446,34 @@
}
}
+ @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() {
int tagId = FrameworkStatsLog.EXTERNAL_STORAGE_INFO;
mStatsManager.setPullAtomCallback(
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index b00540f..2923148 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 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 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 @@
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 @@
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 477ebf6..d24a3df 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 @@
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 @@
"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 1a5945e..65f077e 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -39,6 +39,7 @@
private final boolean mTelephonyDetectionSupported;
private final boolean mGeoDetectionSupported;
+ private final boolean mTelephonyFallbackSupported;
private final boolean mAutoDetectionEnabledSetting;
private final @UserIdInt int mUserId;
private final boolean mUserConfigAllowed;
@@ -48,6 +49,7 @@
private ConfigurationInternal(Builder builder) {
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
mGeoDetectionSupported = builder.mGeoDetectionSupported;
+ mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
mUserId = builder.mUserId;
@@ -71,6 +73,14 @@
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 mAutoDetectionEnabledSetting;
@@ -216,6 +226,7 @@
&& mUserConfigAllowed == that.mUserConfigAllowed
&& mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
&& mGeoDetectionSupported == that.mGeoDetectionSupported
+ && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
&& mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
&& mLocationEnabledSetting == that.mLocationEnabledSetting
&& mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -224,8 +235,8 @@
@Override
public int hashCode() {
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
- mGeoDetectionSupported, mAutoDetectionEnabledSetting, mLocationEnabledSetting,
- mGeoDetectionEnabledSetting);
+ mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
+ mLocationEnabledSetting, mGeoDetectionEnabledSetting);
}
@Override
@@ -235,6 +246,7 @@
+ ", mUserConfigAllowed=" + mUserConfigAllowed
+ ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
+ ", mGeoDetectionSupported=" + mGeoDetectionSupported
+ + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+ ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+ ", mLocationEnabledSetting=" + mLocationEnabledSetting
+ ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -251,6 +263,7 @@
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
+ private boolean mTelephonyFallbackSupported;
private boolean mAutoDetectionEnabledSetting;
private boolean mLocationEnabledSetting;
private boolean mGeoDetectionEnabledSetting;
@@ -269,6 +282,7 @@
this.mUserId = toCopy.mUserId;
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
+ this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
@@ -300,6 +314,15 @@
}
/**
+ * 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 setAutoDetectionEnabledSetting(boolean enabled) {
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 0000000..62092ec
--- /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 0000000..8c9bd3b
--- /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 ec620b5..0ec8826c 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 @@
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 f8ba0d2..f156f8c 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,7 +46,7 @@
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;
@@ -89,16 +85,16 @@
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. */
@@ -111,6 +107,11 @@
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 getUserLocationEnabledSetting() {
return mConfigurationInternal.getLocationEnabledSetting();
@@ -149,30 +150,27 @@
}
/**
- * 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
@@ -208,14 +206,6 @@
+ '}';
}
- private static byte[] suggestionProtoBytes(
- @Nullable MetricsTimeZoneSuggestion suggestion) {
- if (suggestion == null) {
- return null;
- }
- return suggestion.toBytes();
- }
-
@Nullable
private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
@NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
@@ -259,10 +249,11 @@
}
/**
- * 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;
@@ -276,42 +267,20 @@
}
@NonNull
- public static MetricsTimeZoneSuggestion createCertain(
+ 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 984b9ba..692b0cc 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 @@
* 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 be4b6bd..02ea433 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 @@
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 @@
* See also {@link #resetVolatileTestConfig()}.
*/
@GuardedBy("this")
- private boolean mRecordProviderStateChanges;
+ private boolean mRecordStateChangesForTests;
private ServiceConfigAccessorImpl(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
@@ -294,6 +295,7 @@
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
+ .setTelephonyFallbackSupported(isTelephonyFallbackSupported())
.setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setUserConfigAllowed(isUserConfigAllowed(userId))
.setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -451,13 +453,13 @@
}
@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 @@
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 e0c39ad..14784cf 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 @@
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 @@
// 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 @@
}
/**
+ * 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 a4a46a3..2b912ad 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 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 @@
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 @@
}
}
+ private int runEnableTelephonyFallback() {
+ mInterface.enableTelephonyFallback();
+ return 1;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -190,6 +199,13 @@
+ "\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 @@
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 ede52ba..6b04adf 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 @@
* 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 @@
*/
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 3b6c1ea..92dddac 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_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.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 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 @@
* 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 @@
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 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 @@
// 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 @@
}
@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 @@
// 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 @@
* 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 @@
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 @@
+ 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 @@
return mLatestGeoLocationSuggestion.get();
}
+ @VisibleForTesting
+ public synchronized boolean isTelephonyFallbackEnabledForTests() {
+ return mTelephonyTimeZoneFallbackEnabled.getValue();
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@@ -652,4 +794,8 @@
+ '}';
}
}
+
+ 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 b9da2eb..0000000
--- 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 ddbeac4..b23f11a 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 @@
/** 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 @@
// 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 @@
* 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 @@
testPrimaryProviderPackageName);
mServiceConfigAccessor.setTestSecondaryLocationTimeZoneProviderPackageName(
testSecondaryProviderPackageName);
- mServiceConfigAccessor.setRecordProviderStateChanges(recordProviderStateChanges);
+ mServiceConfigAccessor.setRecordStateChangesForTests(recordStateChanges);
startOnDomainThread();
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -278,19 +277,32 @@
return;
}
- if (mLocationTimeZoneDetectorController == null) {
+ if (mLocationTimeZoneProviderController == null) {
LocationTimeZoneProvider primary = mPrimaryProviderConfig.createProvider();
LocationTimeZoneProvider secondary = mSecondaryProviderConfig.createProvider();
+ 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.
+ }
+ };
- ControllerImpl controller =
- new ControllerImpl(mThreadingDomain, primary, secondary);
- ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl(
- mThreadingDomain, mServiceConfigAccessor, controller);
- ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
+ 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 @@
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 @@
mThreadingDomain.postAndWait(() -> {
synchronized (mSharedLock) {
- if (mLocationTimeZoneDetectorController != null) {
- mLocationTimeZoneDetectorController.clearRecordedProviderStates();
+ if (mLocationTimeZoneProviderController != null) {
+ mLocationTimeZoneProviderController.clearRecordedStates();
}
}
}, BLOCKING_OP_WAIT_DURATION_MILLIS);
@@ -357,10 +369,10 @@
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 @@
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 @@
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 113926a..1f752f4 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 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 @@
/** 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 @@
@Override
public String toString() {
return "LocationTimeZoneManagerServiceState{"
- + "mLastSuggestion=" + mLastSuggestion
+ + "mControllerState=" + mControllerState
+ + ", mLastSuggestion=" + mLastSuggestion
+ + ", mControllerStates=" + mControllerStates
+ ", mPrimaryProviderStates=" + mPrimaryProviderStates
+ ", mSecondaryProviderStates=" + mSecondaryProviderStates
+ '}';
@@ -66,17 +82,31 @@
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 6c9e174..60bbea7 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.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 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 @@
outputStream.end(lastSuggestionToken);
}
+ writeControllerStates(outputStream, state.getControllerStates());
writeProviderStates(outputStream, state.getPrimaryProviderStates(),
"primary_provider_states",
LocationTimeZoneManagerServiceStateProto.PRIMARY_PROVIDER_STATES);
@@ -256,6 +266,37 @@
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 4dff02e..5d7730a 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 @@
*
* <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 @@
* 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 {
- @NonNull protected final ThreadingDomain mThreadingDomain;
- @NonNull protected final Object mSharedLock;
+ // 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 {}
- LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain) {
+ /** 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;
+
+ @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);
+
+ /** 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 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
similarity index 82%
rename from services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 46eaad0..0c751aa 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 @@
/**
* 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
similarity index 90%
rename from services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index 33cdc5f..e7d16c8 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 @@
/**
* 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 e69acc3..59b6a08 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 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 dfb0752..c7b6421 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.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.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 @@
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 @@
}
// 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 @@
}
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ @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 1c46ac8..be13168 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.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 @@
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 @@
@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 @@
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 @@
cancelRetryTimeoutAlarm();
cancelSafeModeAlarm();
- mUnderlyingNetworkTracker.teardown();
+ mUnderlyingNetworkController.teardown();
mGatewayStatusCallback.onQuit();
}
@@ -764,12 +768,13 @@
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 @@
+ (mNetworkAgent == null ? null : mNetworkAgent.getNetwork()));
pw.println();
- mUnderlyingNetworkTracker.dump(pw);
+ mUnderlyingNetworkController.dump(pw);
pw.println();
pw.decreaseIndent();
@@ -2276,8 +2281,8 @@
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
- return mUnderlyingNetworkTrackerCallback;
+ UnderlyingNetworkControllerCallback getUnderlyingNetworkControllerCallback() {
+ return mUnderlyingNetworkControllerCallback;
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
@@ -2356,17 +2361,14 @@
/** 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 0000000..bea8ae9
--- /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
similarity index 60%
rename from services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
rename to services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 7ddd135..071c7a6 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.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 @@
/**
* 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 @@
@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 @@
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 @@
* 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 @@
}
/**
- * 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 @@
private void reevaluateNetworks() {
if (mIsQuitting || mRouteSelectionCallback == null) {
- return; // UnderlyingNetworkTracker has quit.
+ return; // UnderlyingNetworkController has quit.
}
TreeSet<UnderlyingNetworkRecord> sorted =
@@ -424,22 +371,6 @@
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 @@
}
}
- 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 @@
/** 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 @@
}
/** 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 0000000..65c69de
--- /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 71a6b22..f82f99d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -186,7 +186,7 @@
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));
@@ -366,7 +366,7 @@
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(USAGE_TOUCH));
mHardwareFeedbackIntensity = getSystemSetting(
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5d40c23..9717201 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 @@
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 @@
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 @@
* 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 2a1bb0b..ffc70da 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 @@
/** 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;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 73a783e..bb7434d 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 @@
// 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 7fa9861..e38e9c1 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 @@
}
/**
- * 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 0a85ba1..e4ed04de 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 @@
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 @@
}
}
- @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 @@
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 8788225..721907c 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 @@
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 55acfb6..68eff9a 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_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_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.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;
@@ -317,11 +321,6 @@
*/
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
@@ -665,7 +664,7 @@
/** 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;
@@ -1640,11 +1639,6 @@
// 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
@@ -2833,8 +2827,14 @@
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"
@@ -2891,6 +2891,13 @@
/** 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
@@ -4288,7 +4295,7 @@
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));
@@ -4659,12 +4666,9 @@
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) {
@@ -4676,18 +4680,12 @@
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> {
@@ -4817,7 +4815,10 @@
// 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} */
@@ -4938,6 +4939,12 @@
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 */);
}
}
@@ -5472,29 +5479,44 @@
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 09de6b3..cbb9d5d 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 @@
: 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 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 @@
* @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 @@
// 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 @@
// 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/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 83fd3fa..4225f21 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 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 @@
&& !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 @@
// 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 @@
// 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 fee9884..a843909d 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 @@
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 @@
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 7c35a21..49d30cd 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 @@
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 ccee458..117b22a 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 @@
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 @@
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 @@
// 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 bfb1a8e..5af1c8e 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 @@
}
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
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 600545e..796a90a 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 @@
// 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 @@
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 929f221..e497b53 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 @@
/** 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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9865506..7e84dbb 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 @@
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 @@
}
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 bc53041..cc52713 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 @@
*
* <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 @@
* 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 @@
/**
* 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 @@
* @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 @@
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 @@
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 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 @@
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 @@
/** 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 @@
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 @@
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client.");
}
+ mHasPendingConfiguration = false;
}
@Override
@@ -283,7 +306,7 @@
// 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 4720d7c..23f9c58 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 @@
@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 @@
}
}
+ @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 81b241e..bae5465 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_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 @@
outFrame.set(0, 0, mWindowFrames.mCompatFrame.width(), mWindowFrames.mCompatFrame.height());
}
- @Override
- public WindowManager.LayoutParams getAttrs() {
+ WindowManager.LayoutParams getAttrs() {
return mAttrs;
}
@@ -2687,10 +2685,9 @@
}
}
- 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 @@
if (mInvGlobalScale != 1.f) {
region.scale(mInvGlobalScale);
}
-
- return flags;
}
/**
@@ -3414,7 +3409,10 @@
}
// 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 @@
* {@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 @@
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 @@
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 6204824..ca834c5 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 @@
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 e4c8871..2ccef9a 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -56,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;
@@ -85,7 +85,6 @@
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;
@@ -115,7 +114,6 @@
static jmethodID method_correctionPlaneAzimDeg;
static jmethodID method_reportNfwNotification;
static jmethodID method_isInEmergencySession;
-static jmethodID method_gnssNavigationMessageCtor;
static jmethodID method_gnssPowerStatsCtor;
static jmethodID method_setSubHalPowerIndicationCapabilities;
@@ -233,7 +231,6 @@
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;
@@ -242,6 +239,7 @@
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"
@@ -910,50 +908,6 @@
}
/*
- * GnssNavigationMessageCallback interface implements the callback methods
- * required by the IGnssNavigationMessage interface.
- */
-struct GnssNavigationMessageCallback : public IGnssNavigationMessageCallback {
- /*
- * Methods from ::android::hardware::gps::V1_0::IGnssNavigationMessageCallback
- * follow.
- */
- Return<void> gnssNavigationMessageCb(
- const IGnssNavigationMessageCallback::GnssNavigationMessage& message) override;
-};
-
-Return<void> GnssNavigationMessageCallback::gnssNavigationMessageCb(
- const IGnssNavigationMessageCallback::GnssNavigationMessage& 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 Void();
- }
-
- 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 Void();
-}
-
-/*
* MeasurementCorrectionsCallback implements callback methods of interface
* IMeasurementCorrectionsCallback.hal.
*/
@@ -1264,10 +1218,6 @@
"(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");
@@ -1337,14 +1287,11 @@
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);
}
@@ -1431,12 +1378,20 @@
}
}
- if (gnssHal != nullptr) {
+ 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 (!gnssNavigationMessage.isOk()) {
- ALOGD("Unable to get a handle to GnssNavigationMessage");
- } else {
- gnssNavigationMessageIface = gnssNavigationMessage;
+ if (checkHidlReturn(gnssNavigationMessage,
+ "Unable to get a handle to GnssNavigationMessage interface.")) {
+ gnssNavigationMessageIface =
+ std::make_unique<gnss::GnssNavigationMessageHidl>(gnssNavigationMessage);
}
}
@@ -2624,20 +2579,8 @@
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,
@@ -2646,9 +2589,7 @@
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 090166a..6c6b304 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -29,6 +29,8 @@
"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 0000000..75aee74
--- /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 0000000..e3a1e4a
--- /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 0000000..1779c95
--- /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 0000000..fe76fc7
--- /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 429edf1..2f4dd57 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 ad18602..5b2b87c 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 @@
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 3839a9f..be74ed8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10399,14 +10399,23 @@
}
@Override
- public List<String> getPermittedInputMethodsForCurrentUser() {
+ public @Nullable List<String> getPermittedInputMethodsAsUser(@UserIdInt int userId) {
final CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
Preconditions.checkCallAuthorization(canManageUsers(caller));
+ 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 +10430,7 @@
// 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 +10596,12 @@
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 +10610,6 @@
}
}
- 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 2f9e334..a72cf3a 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.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 @@
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 @@
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 @@
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 42115d4..b7f8c00 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 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 @@
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 @@
transportManager.registerTransports();
- verify(mTransportClientManager)
+ verify(mTransportConnectionManager)
.getTransportClient(
eq(mTransportA1.getTransportComponent()),
argThat(
@@ -433,10 +433,10 @@
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 @@
null,
null);
- TransportClient transportClient =
+ TransportConnection transportConnection =
transportManager.getTransportClient(mTransportA1.transportName, "caller");
- assertThat(transportClient).isNull();
+ assertThat(transportConnection).isNull();
}
@Test
@@ -471,9 +471,10 @@
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 @@
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 @@
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 @@
.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 06d51a4..297538a 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 @@
assertThat(result).isTrue();
verify(mTransportManager)
- .disposeOfTransportClient(eq(transportMock.transportClient), any());
+ .disposeOfTransportClient(eq(transportMock.mTransportConnection), any());
}
/**
@@ -282,7 +282,7 @@
assertThat(filtered).asList().containsExactly(PACKAGE_1);
verify(mTransportManager)
- .disposeOfTransportClient(eq(transportMock.transportClient), any());
+ .disposeOfTransportClient(eq(transportMock.mTransportConnection), any());
}
/**
@@ -357,7 +357,7 @@
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 @@
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 a14cc51..bf4eeae 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.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 @@
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 @@
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 @@
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 @@
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 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 7d17109..fd295c0 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 @@
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 5883c1c..9eb99ae 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 @@
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 @@
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 @@
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 @@
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 @@
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 7dd5be5..ce44f06 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 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 @@
|| 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 @@
|| 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 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 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
similarity index 80%
rename from services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
rename to services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionManagerTest.java
index f033af8..102f594 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 @@
@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 @@
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 @@
@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 @@
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 @@
@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
similarity index 79%
rename from services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
rename to services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index 392f2ca..de4aec6 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 @@
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 @@
@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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
// 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 @@
@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 @@
@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 @@
@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 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 void testFinalize_whenNotMarkedAsDisposed() throws Throwable {
ShadowCloseGuard.setUp();
- mTransportClient.finalize();
+ mTransportConnection.finalize();
assertThat(ShadowCloseGuard.hasReported()).isTrue();
}
@@ -514,10 +515,10 @@
@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 adf892a..5dc048b 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 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 @@
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 @@
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 566b0e1..9a74977 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 @@
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 @@
@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 fd51df7..06b7fb7 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.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 @@
@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 5161070..71010a9 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 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 @@
@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 98634b2..ac6b679 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 @@
@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 a9099ae..5885470 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 @@
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 b2847ce..783971a 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 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 @@
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 0000000..3f69f1b
--- /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 b17ff53b..95912b2 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.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 @@
}
}
- 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 @@
}
@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 e2e7f5d..94dcdf9 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 @@
@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);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
- mHelper.reportLocationStart(caller1, "gps", key2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, 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, "gps", key3);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
+ 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, "gps", key4);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, 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, "gps", key2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
- mHelper.reportLocationStop(caller1, "gps", key1);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1);
+ 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, "gps", key3);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
- mHelper.reportLocationStop(caller2, "gps", key4);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2);
+ 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);
+ 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, "gps", key2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 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, "gps", key3);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 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.reportHighPowerLocationStart(caller2, "gps", key4);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 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, "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(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, "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.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 d0b2eda..890a549 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 @@
}
@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 63416c9..0e5640a 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.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 @@
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 @@
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 fe23c14..9666758 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.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 @@
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 @@
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 @@
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/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
index 3ace3f4..a1d4c20 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 @@
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 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 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/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 2fd2816..205c3da 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -22,6 +22,7 @@
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;
@@ -316,6 +317,34 @@
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());
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 8a521d8..ec1a0c2 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.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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
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 @@
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 2a53504..0659a60 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 @@
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 @@
}
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 1638563..3822dc3 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 @@
@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 1b8aff5..b807c11 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 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 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 @@
== 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 @@
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 @@
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 @@
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 02c0aca..85512f3 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 @@
* new connection.
*/
@Test
- public void
- setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath()
+ public void setSecondConnectionAndFormerConnectionBinderDead_hasWrapperAndNotCallUnlinkToDeath()
throws RemoteException {
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
MockWindowMagnificationConnection secondConnection =
@@ -177,7 +176,7 @@
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 @@
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);
}
@@ -411,6 +411,34 @@
}
@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/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 1c49e6e..e40f543 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.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 @@
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 @@
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 205ff30..aa7d6aa 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 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 @@
@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 @@
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 @@
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 @@
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 @@
@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 @@
// 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 bae11eb..0000000
--- 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 e3e3900..d192697 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 @@
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 @@
@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 @@
// 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 @@
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 @@
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 @@
return false;
}
+ @Override
+ public void destroy() {
+ mDestroyed = true;
+ super.destroy();
+ }
+
+ @Override
public void cancel() {
mNumCancels++;
super.cancel();
@@ -595,6 +578,7 @@
@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 2777bdf..3c809f9 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 @@
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 @@
// 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 @@
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 fe0df58..b8824c3 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 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 @@
doReturn(ai).when(mServices.ipackageManager).getApplicationInfo(
eq(admin.getPackageName()),
- anyInt(),
+ anyLong(),
eq(UserHandle.getUserId(packageUid)));
// Set up queryBroadcastReceivers().
@@ -248,7 +247,7 @@
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 285806b..c675726 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 @@
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 @@
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 @@
};
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 @@
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 @@
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 @@
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 @@
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 cf4bdf6..b588db6 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 @@
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 4ff7c669..ff01cb1 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 @@
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 c6bb914..a44a5cd 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 @@
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 0000000..968a75c
--- /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 17f827d..a411392 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 @@
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 055459c..2d13e69 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 @@
private int mPlaybackLogicalAddress;
private boolean mWokenUp;
private boolean mActiveMediaSessionsPaused;
+ private FakePowerManagerInternalWrapper mPowerManagerInternal =
+ new FakePowerManagerInternalWrapper();
@Before
public void setUp() {
@@ -146,6 +148,7 @@
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 @@
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 b3a513f..ddc58b2 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 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 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 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 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 f45c869..3722ba4 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.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 @@
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 764c504..6245f82 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.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 @@
* 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 b228c83..54ab133 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.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.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 e811c1f..3cb5d5f 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 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
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.nullable;
import static org.mockito.Mockito.doAnswer;
@@ -14,7 +15,6 @@
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 @@
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 @@
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 9631863..6b6d84a 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 @@
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.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 6a9ef8a..9ea7907 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 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 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 6a85c8b..b81a4ef 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.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 @@
// 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 @@
@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 @@
@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 @@
// 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 @@
@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 @@
// 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 @@
@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 @@
// 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 @@
}
}
}
+
+ @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 ab37e9b..a9a3469 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.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.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 b6d4b31..7e4474f 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 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.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 c9f3cb2..828d419c 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.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 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 1fff4f0..ecf7803 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 @@
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 b73c9ea..e7adf7b 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 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 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 ec5228f..32a88bd 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.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 @@
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 e92c849..57ada9b 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.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 @@
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 c8a4052..98fa2d6 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.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 @@
*
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 bc2d256..bcd216d 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 @@
@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 90a1277..408d2c5 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.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 @@
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 ba26f79..43e527c 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 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 @@
/**
* 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 7546c43..11a2a8a 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 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 @@
});
});
}
-}
\ 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 203b2ca..400d3a8 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.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 @@
* 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 63df4bc..6c10bfd 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 b21b049..b2fd8aa 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.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 @@
*
* 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 58e00f2..2293808 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.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 @@
* - 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 55b4b93..a47a8df 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.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 @@
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 826a8d4..4af91c6 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.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 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 0000000..85a73bb
--- /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 7916bd3..a4afe09 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.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 @@
* 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 35c513f..fdf94be 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.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 @@
* 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 b0423bf..1f4c9f8 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 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 @@
* -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 6c1c019..34b40c7 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.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 dfc25e0..92fddc7 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.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 @@
* 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 f1acc66..971b036 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.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 @@
*
* <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 b76c279..cf6165f 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.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 javax.annotation.concurrent.GuardedBy;
/** Test {@link UserManager} functionality. */
+@Postsubmit
@RunWith(AndroidJUnit4.class)
public final class UserManagerTest {
// Taken from UserManagerService
@@ -207,6 +209,65 @@
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 ddf0cd0..07a5303 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 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 @@
-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 b11bb85..ba7a103 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.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 @@
* 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 b2c3002..95af1e1 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 android.content.ComponentName;
import android.content.IntentFilter;
+import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -29,6 +30,7 @@
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 fdb6e9f5..a16ecb1 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 @@
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 @@
*
* 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 2a7a2ff..b7b55ba 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.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.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 bc84e35..d5893c8 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.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 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 34cefec..1dcb0b7 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 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.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 7992ba3..d55f967 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.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.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 3450710..c98e7c3 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.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.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 f4cdc8c..e075379 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 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.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 51c268e..4059a49 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 @@
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 df8786f..122661e 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.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 @@
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 @@
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 @@
}
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 @@
.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 @@
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 c4aa862..f530421 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.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 @@
*
* 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 3261dfa..3551af8 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.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.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 809b2f9..6ee6020c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -48,6 +48,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -110,6 +111,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -174,6 +176,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -236,6 +239,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -288,4 +292,18 @@
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 9d1c74b..a97ad8c 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 @@
}
@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 ee3195e..2d0dca2 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 @@
}
@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 97b8360..97095c4 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.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 @@
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 ce3b78e..193b2e3 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 @@
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());
@@ -375,6 +378,7 @@
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(autoDetectionEnabled)
.setLocationEnabledSetting(geoDetectionEnabled)
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 91e8d16..ef1b4f5 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 @@
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,6 +90,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -99,6 +101,7 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(true)
@@ -109,6 +112,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -119,6 +123,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(false)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(false)
@@ -128,6 +133,7 @@
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -138,6 +144,7 @@
new ConfigurationInternal.Builder(USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setUserConfigAllowed(true)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
@@ -147,9 +154,6 @@
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();
@@ -753,6 +757,204 @@
}
@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";
@@ -835,6 +1037,8 @@
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 @@
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
return GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mElapsedRealtimeMillis++, null);
+ mFakeEnvironment.elapsedRealtimeMillis(), null);
}
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@@ -874,7 +1078,7 @@
GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mElapsedRealtimeMillis++, Arrays.asList(zoneIds));
+ mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
suggestion.addDebugInfo("Test suggestion");
return suggestion;
}
@@ -883,16 +1087,25 @@
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 @@
void commitAllChanges() {
mTimeZoneId.commitLatest();
}
+
+ @Override
+ @ElapsedRealtimeLong
+ public long elapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
}
/**
@@ -949,6 +1168,16 @@
return this;
}
+ Script initializeClock(long elapsedRealtimeMillis) {
+ mFakeEnvironment.initializeClock(elapsedRealtimeMillis);
+ return this;
+ }
+
+ Script simulateIncrementClock() {
+ mFakeEnvironment.incrementClock();
+ return this;
+ }
+
/**
* Simulates the user / user's configuration changing.
*/
@@ -1009,6 +1238,15 @@
}
/**
+ * 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 @@
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
similarity index 68%
rename from services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index 463ac52..d54e1f1 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.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 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.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 @@
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 @@
// 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 @@
}
@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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
// 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 @@
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 @@
.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 @@
}
}
+ 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 e3da90e..a2df3130 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
@@ -46,6 +46,7 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
+ .setTelephonyFallbackSupported(false)
.setAutoDetectionEnabledSetting(true)
.setLocationEnabledSetting(true)
.setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
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 3716507..7eb6c97 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 @@
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 9e46e1f..949ee01 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.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 @@
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 a9cbad2..beee2a7 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -126,7 +126,7 @@
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);
}
@@ -141,7 +141,7 @@
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, VIBRATION_INTENSITY_OFF);
setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
@@ -220,7 +220,7 @@
@Test
public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
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(USAGE_RINGTONE));
@@ -238,7 +238,7 @@
@Test
public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
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(USAGE_RINGTONE));
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 be83efb..c0f7596 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 @@
new VibrationAttributes.Builder().setUsage(
VibrationAttributes.USAGE_RINGTONE).build();
- @Rule public MockitoRule rule = MockitoJUnit.rule();
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @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;
+ @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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index cdb7230..2f054b00 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 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 @@
service.isComponentEnabledForCurrentProfiles(
unapprovedAdditionalComponent));
verify(mIpm, never()).getServiceInfo(
- eq(unapprovedAdditionalComponent), anyInt(), anyInt());
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
}
}
@@ -953,7 +954,7 @@
service.isComponentEnabledForCurrentProfiles(
unapprovedAdditionalComponent));
verify(mIpm, never()).getServiceInfo(
- eq(unapprovedAdditionalComponent), anyInt(), anyInt());
+ eq(unapprovedAdditionalComponent), anyLong(), anyInt());
}
}
@@ -1702,14 +1703,14 @@
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 ea3a4cd..837850f 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.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.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 @@
// 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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 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 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 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 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 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 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 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 @@
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 @@
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 @@
// 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 @@
// 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 29ef339..2e5cf3c 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.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 @@
// 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 4cdae88..5800400 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 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.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 @@
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 @@
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 @@
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 @@
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 ae24785..7a133ea 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 @@
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 0cab911..e2f0658f 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.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 @@
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 6fa306b..5062706 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 @@
}
@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 08be15e..d7a0ab3 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 @@
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 @@
}
@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 a8ede13..407f9cf 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.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 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 @@
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 @@
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 6737b1a..730275c 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 @@
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 @@
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 c0ae8a5..3065e7d 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 @@
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/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index e5eba57..646647f 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 @@
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 @@
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 @@
assertEquals(container, listener.getWindowContainer());
}
+ @UseTestDisplay
@Test
public void testRegisterWindowContextListenerClientConfigPropagation() {
final TestWindowTokenClient clientToken = new TestWindowTokenClient();
@@ -107,7 +124,7 @@
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 @@
.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 @@
// 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 @@
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 996b4b2..92fd682 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 @@
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 722aee7..e5dc557 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 @@
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 @@
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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c7e5aaf..6f92c31 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5116,13 +5116,11 @@
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 @@
* 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 @@
*
* "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 @@
"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",
@@ -5952,8 +5958,9 @@
"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=mms|supl|cbs, retry_interval=2000",
- "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, "
- + "backoff=true, maximum_retries=13",
+ "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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 88b21e0..ae2facd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3465,15 +3465,39 @@
* @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 @@
/**
* 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 @@
* @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 @@
* 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 @@
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/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 43ad982..c1d16a9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -18,6 +18,7 @@
import static android.telephony.data.ApnSetting.ProtocolType;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,6 +71,12 @@
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;
mTrafficDescriptor = builder.mTrafficDescriptor;
@@ -101,6 +108,7 @@
mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
mPreferred = source.readBoolean();
+ mSetupTimestamp = source.readLong();
}
/**
@@ -397,6 +405,24 @@
}
}
+ /**
+ * 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;
@@ -405,8 +431,8 @@
@NonNull
@Override
public String toString() {
- return "DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
- + mPreferred;
+ return "[DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
+ + mPreferred + "]";
}
@Override
@@ -415,6 +441,7 @@
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 dbc6cb6..6d094cb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2134,9 +2134,9 @@
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 0bb6198..22320fd 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -27,9 +27,8 @@
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 @@
"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 4420a44..0000000
--- 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 0000000..a4741eed
--- /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 0000000..c42bde9
--- /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 0000000..1325760
--- /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 0000000..48bfd6f
--- /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 3c9ba20..ca735031 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 @@
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 b1bdb31..39d2518 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 @@
}
}
- @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 ac90752..61fe02e 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 @@
@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 cc5d9d2..1c14916 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 @@
}
}
- @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 429841b..61fe07a 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 @@
* 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 @@
}
/** {@inheritDoc} */
- @FlakyTest
+ @Postsubmit
@Test
override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
@@ -211,7 +211,7 @@
}
/** {@inheritDoc} */
- @FlakyTest
+ @Postsubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
@@ -227,7 +227,7 @@
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 5b0372d..0a64939 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 @@
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 @@
/**
* Checks that the transition starts with [testApp2] being the top window.
*/
- @Postsubmit
+ @Presubmit
@Test
fun startsWithApp2WindowBeingOnTop() {
testSpec.assertWmStart {
@@ -284,7 +285,7 @@
/**
* Checks that the navbar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
@@ -293,21 +294,21 @@
*
* 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 99bc115..5b63376 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 @@
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 @@
/**
* Checks that the navbar layer is visible throughout the entire transition.
*/
- @Postsubmit
+ @Presubmit
@Test
fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
@@ -307,21 +308,21 @@
*
* 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 a97a48e..c18798f 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 @@
}
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPresubmitAssertion() {
flickerRule.checkPresubmitAssertions()
}
- @Postsubmit
+ @FlakyTest
@Test
fun runPostsubmitAssertion() {
flickerRule.checkPostsubmitAssertions()
@@ -115,11 +114,16 @@
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 ce2347d..d1bdeed 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 @@
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 3ca60e3..e44bee6 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 @@
}
}
- /** {@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 @@
}
}
+ /**
+ * 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 7ea2a62d..d4bc2a6 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 @@
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 7173a85..584ab59 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 @@
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 0000000..65a3436
--- /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 937f9dc..15de226 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 @@
@Test
public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(null);
mTestLooper.dispatchAll();
@@ -131,7 +131,7 @@
@Test
public void testNewNetworkTriggersMigration() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
mTestLooper.dispatchAll();
@@ -143,7 +143,7 @@
@Test
public void testSameNetworkDoesNotTriggerMigration() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
mTestLooper.dispatchAll();
@@ -203,7 +203,7 @@
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 d1f3a21..3c70759 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 @@
@Test
public void testNullNetworkTriggersDisconnect() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(null);
mTestLooper.dispatchAll();
@@ -76,7 +76,7 @@
@Test
public void testNewNetworkTriggersReconnect() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
mTestLooper.dispatchAll();
@@ -89,7 +89,7 @@
@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 2056eea..f3eb82f 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 @@
@Test
public void testNetworkChangesTriggerStateTransitions() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
mTestLooper.dispatchAll();
@@ -89,7 +89,7 @@
@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 1c85979..6568cdd 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 @@
@Test
public void testNewNetworkTriggerRetry() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
mTestLooper.dispatchAll();
@@ -72,7 +72,7 @@
@Test
public void testSameNetworkDoesNotTriggerRetry() throws Exception {
mGatewayConnection
- .getUnderlyingNetworkTrackerCallback()
+ .getUnderlyingNetworkControllerCallback()
.onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
mTestLooper.dispatchAll();
@@ -86,7 +86,7 @@
@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 2b0037e..b9dfda3 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.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 @@
}
@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 @@
@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 64d0bca..8a0af2d 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.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 @@
@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 @@
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 @@
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
similarity index 86%
rename from tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
rename to tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 5af69b5..c954cb8 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.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.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 @@
@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 @@
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 @@
mVcnNetworkProvider,
true /* isInTestMode */);
- new UnderlyingNetworkTracker(
- vcnContext,
- SUB_GROUP,
- mSubscriptionSnapshot,
- mNetworkTrackerCb);
+ new UnderlyingNetworkController(
+ vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
verify(cm)
.registerNetworkCallback(
@@ -233,7 +230,7 @@
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 @@
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 @@
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 @@
@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 @@
networkCapabilities,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
- verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
return cb;
}
@@ -384,7 +381,7 @@
UPDATED_NETWORK_CAPABILITIES,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
- verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
@@ -399,7 +396,7 @@
INITIAL_NETWORK_CAPABILITIES,
UPDATED_LINK_PROPERTIES,
false /* isBlocked */);
- verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
@@ -414,11 +411,13 @@
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 @@
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 @@
INITIAL_NETWORK_CAPABILITIES,
INITIAL_LINK_PROPERTIES,
true /* isBlocked */);
- verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@Test
@@ -462,7 +463,7 @@
cb.onLost(mNetwork);
- verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+ verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(null);
}
@Test
@@ -471,20 +472,20 @@
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 740b44e..bd0a4bc 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -170,16 +170,6 @@
}
// ==========================================================
-// 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 a8c4b13..0000000
--- 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 ec3c543..0000000
--- 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 3cd9865..0000000
--- 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