Merge "New Pipeline: Numerous bug and crash fixes:"
diff --git a/Android.bp b/Android.bp
index d52f08f..88c01e7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -82,6 +82,7 @@
":framework-mca-filterpacks-sources",
":framework-media-sources",
":framework-mms-sources",
+ ":framework-omapi-sources",
":framework-opengl-sources",
":framework-rs-sources",
":framework-sax-sources",
@@ -273,6 +274,7 @@
"android.hardware.vibrator-V1.2-java",
"android.hardware.vibrator-V1.3-java",
"android.hardware.vibrator-V2-java",
+ "android.se.omapi-V1-java",
"android.system.suspend.control.internal-java",
"devicepolicyprotosnano",
@@ -324,8 +326,12 @@
"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",
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/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/core/api/current.txt b/core/api/current.txt
index 9d74a8c..4d94dc1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3255,6 +3255,28 @@
method public boolean willContinue();
}
+ public final class MagnificationConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public float getCenterX();
+ method public float getCenterY();
+ method public int getMode();
+ method public float getScale();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
+ field public static final int DEFAULT_MODE = 0; // 0x0
+ field public static final int FULLSCREEN_MODE = 1; // 0x1
+ field public static final int WINDOW_MODE = 2; // 0x2
+ }
+
+ public static final class MagnificationConfig.Builder {
+ ctor public MagnificationConfig.Builder();
+ method @NonNull public android.accessibilityservice.MagnificationConfig build();
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
+ method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(float);
+ }
+
public final class TouchInteractionController {
method public int getDisplayId();
method public int getMaxPointerCount();
@@ -11279,6 +11301,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";
@@ -12532,6 +12555,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;
@@ -51423,14 +51447,18 @@
public static final class AccessibilityNodeInfo.CollectionItemInfo {
ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean);
ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean);
+ ctor public AccessibilityNodeInfo.CollectionItemInfo(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
method public int getColumnIndex();
method public int getColumnSpan();
+ method @Nullable public String getColumnTitle();
method public int getRowIndex();
method public int getRowSpan();
+ method @Nullable public String getRowTitle();
method @Deprecated public boolean isHeading();
method public boolean isSelected();
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+ method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
}
public static final class AccessibilityNodeInfo.ExtraRenderingInfo {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 6d342db..81d1c9e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -271,6 +271,7 @@
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";
@@ -2444,6 +2445,7 @@
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 +6310,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 +6346,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);
}
@@ -6365,14 +6369,14 @@
}
public class Filter implements java.lang.AutoCloseable {
+ method @Nullable public String acquireSharedFilterToken();
method public void close();
method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
- method @Nullable public String createSharedFilter();
method public int flush();
+ method public void freeSharedFilterToken(@NonNull String);
method @Deprecated public int getId();
method public long getIdLong();
method public int read(@NonNull byte[], long, long);
- method public void releaseSharedFilter(@NonNull String);
method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
method public int setMonitorEventMask(int);
method public int start();
@@ -6518,6 +6522,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 +6615,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> {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f83b3a4..ba0b6aa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3250,12 +3250,6 @@
field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
}
- public final class TaskFragmentAppearedInfo implements android.os.Parcelable {
- method @NonNull public android.view.SurfaceControl getLeash();
- method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo();
- field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR;
- }
-
public final class TaskFragmentCreationParams implements android.os.Parcelable {
method @NonNull public android.os.IBinder getFragmentToken();
method @NonNull public android.graphics.Rect getInitialBounds();
@@ -3292,7 +3286,7 @@
ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
method @NonNull public java.util.concurrent.Executor getExecutor();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
- method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo);
+ method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 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/window/TaskFragmentAppearedInfo.aidl b/core/java/android/accessibilityservice/MagnificationConfig.aidl
similarity index 77%
rename from core/java/android/window/TaskFragmentAppearedInfo.aidl
rename to core/java/android/accessibilityservice/MagnificationConfig.aidl
index 3729c09..fe415a8 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/core/java/android/accessibilityservice/MagnificationConfig.aidl
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.accessibilityservice;
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+parcelable MagnificationConfig;
diff --git a/core/java/android/accessibilityservice/MagnificationConfig.java b/core/java/android/accessibilityservice/MagnificationConfig.java
new file mode 100644
index 0000000..8884508
--- /dev/null
+++ b/core/java/android/accessibilityservice/MagnificationConfig.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the magnification config for {@link AccessibilityService} to control the
+ * magnification.
+ *
+ * <p>
+ * When the magnification config uses {@link #DEFAULT_MODE},
+ * {@link AccessibilityService} will be able to control the activated magnifier on the display.
+ * If there is no magnifier activated, it controls the last-activated magnification mode.
+ * If there is no magnifier activated before, it controls full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #FULLSCREEN_MODE}. {@link AccessibilityService} will
+ * be able to control full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * When the magnification config uses {@link #WINDOW_MODE}. {@link AccessibilityService} will be
+ * able to control the activated window magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the other magnification configs, scale centerX and centerY, are not set by the
+ * {@link Builder}, the configs should be current values or default values. And the center
+ * position ordinarily is the center of the screen.
+ * </p>
+ */
+public final class MagnificationConfig implements Parcelable {
+
+ /** The controlling magnification mode. It controls the activated magnifier. */
+ public static final int DEFAULT_MODE = 0;
+ /** The controlling magnification mode. It controls fullscreen magnifier. */
+ public static final int FULLSCREEN_MODE = 1;
+ /** The controlling magnification mode. It controls window magnifier. */
+ public static final int WINDOW_MODE = 2;
+
+ @IntDef(prefix = {"MAGNIFICATION_MODE"}, value = {
+ DEFAULT_MODE,
+ FULLSCREEN_MODE,
+ WINDOW_MODE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MAGNIFICATION_MODE {
+ }
+
+ private int mMode = DEFAULT_MODE;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ private MagnificationConfig() {
+ /* do nothing */
+ }
+
+ private MagnificationConfig(@NonNull Parcel parcel) {
+ mMode = parcel.readInt();
+ mScale = parcel.readFloat();
+ mCenterX = parcel.readFloat();
+ mCenterY = parcel.readFloat();
+ }
+
+ /**
+ * Returns the magnification mode that is the current activated mode or the controlling mode of
+ * the config.
+ *
+ * @return The magnification mode
+ */
+ public int getMode() {
+ return mMode;
+ }
+
+ /**
+ * Returns the magnification scale of the controlling magnifier
+ *
+ * @return the scale If the controlling magnifier is not activated, it returns 1 by default
+ */
+ public float getScale() {
+ return mScale;
+ }
+
+ /**
+ * Returns the screen-relative X coordinate of the center of the magnification viewport.
+ *
+ * @return the X coordinate. If the controlling magnifier is {@link #WINDOW_MODE} but not
+ * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+ * #FULLSCREEN_MODE} but not enabled, it returns 0
+ */
+ public float getCenterX() {
+ return mCenterX;
+ }
+
+ /**
+ * Returns the screen-relative Y coordinate of the center of the magnification viewport.
+ *
+ * @return the Y coordinate If the controlling magnifier is {@link #WINDOW_MODE} but not
+ * enabled, it returns {@link Float#NaN}. If the controlling magnifier is {@link
+ * #FULLSCREEN_MODE} but not enabled, it returns 0
+ */
+ public float getCenterY() {
+ return mCenterY;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder("MagnificationConfig[");
+ stringBuilder.append("mode: ").append(getMode());
+ stringBuilder.append(", ");
+ stringBuilder.append("scale: ").append(getScale());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerX: ").append(getCenterX());
+ stringBuilder.append(", ");
+ stringBuilder.append("centerY: ").append(getCenterY());
+ stringBuilder.append("] ");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mMode);
+ parcel.writeFloat(mScale);
+ parcel.writeFloat(mCenterX);
+ parcel.writeFloat(mCenterY);
+ }
+
+ /**
+ * Builder for creating {@link MagnificationConfig} objects.
+ */
+ public static final class Builder {
+
+ private int mMode = DEFAULT_MODE;
+ private float mScale = Float.NaN;
+ private float mCenterX = Float.NaN;
+ private float mCenterY = Float.NaN;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the magnification mode.
+ *
+ * @param mode The magnification mode
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setMode(@MAGNIFICATION_MODE int mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Sets the magnification scale.
+ *
+ * @param scale The magnification scale
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setScale(float scale) {
+ mScale = scale;
+ return this;
+ }
+
+ /**
+ * Sets the X coordinate of the center of the magnification viewport.
+ *
+ * @param centerX the screen-relative X coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterX(float centerX) {
+ mCenterX = centerX;
+ return this;
+ }
+
+ /**
+ * Sets the Y coordinate of the center of the magnification viewport.
+ *
+ * @param centerY the screen-relative Y coordinate around which to
+ * center and scale, or {@link Float#NaN} to leave unchanged
+ * @return This builder
+ */
+ @NonNull
+ public MagnificationConfig.Builder setCenterY(float centerY) {
+ mCenterY = centerY;
+ return this;
+ }
+
+ /**
+ * Builds and returns a {@link MagnificationConfig}
+ */
+ @NonNull
+ public MagnificationConfig build() {
+ MagnificationConfig magnificationConfig = new MagnificationConfig();
+ magnificationConfig.mMode = mMode;
+ magnificationConfig.mScale = mScale;
+ magnificationConfig.mCenterX = mCenterX;
+ magnificationConfig.mCenterY = mCenterY;
+ return magnificationConfig;
+ }
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final @NonNull Parcelable.Creator<MagnificationConfig> CREATOR =
+ new Parcelable.Creator<MagnificationConfig>() {
+ public MagnificationConfig createFromParcel(Parcel parcel) {
+ return new MagnificationConfig(parcel);
+ }
+
+ public MagnificationConfig[] newArray(int size) {
+ return new MagnificationConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f9cc323..9f8d246 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4082,6 +4082,34 @@
}
/**
+ * Gets the message that is shown when a user is switched from.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public @Nullable String getSwitchingFromUserMessage() {
+ try {
+ return getService().getSwitchingFromUserMessage();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the message that is shown when a user is switched to.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_USERS)
+ public @Nullable String getSwitchingToUserMessage() {
+ try {
+ return getService().getSwitchingToUserMessage();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Uses the value defined by the platform.
*
* @hide
diff --git a/core/java/android/app/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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8bb4059..183e714 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -349,6 +349,8 @@
void setPackageScreenCompatMode(in String packageName, int mode);
@UnsupportedAppUsage
boolean switchUser(int userid);
+ String getSwitchingFromUserMessage();
+ String getSwitchingToUserMessage();
@UnsupportedAppUsage
void setStopUserOnSwitch(int value);
boolean removeTask(int taskId);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 74e2858..0476307 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -196,6 +196,7 @@
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;
@@ -1530,6 +1531,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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index cf00cbd..2c875fe 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;
@@ -82,7 +80,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;
/**
@@ -2424,38 +2421,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/content/Context.java b/core/java/android/content/Context.java
index a5a75a4..05e2e87 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3842,6 +3842,7 @@
UWB_SERVICE,
MEDIA_METRICS_SERVICE,
SUPPLEMENTAL_PROCESS_SERVICE,
+ //@hide: SAFETY_CENTER_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -5871,6 +5872,17 @@
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";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -6544,30 +6556,24 @@
@NonNull Configuration overrideConfiguration);
/**
- * Return a new Context object for the current Context but whose resources
- * are adjusted to match the metrics of the given Display. Each call to this method
- * returns a new instance of a Context object; Context objects are not
- * shared, however common state (ClassLoader, other Resources for the
- * same configuration) may be so the Context itself can be fairly lightweight.
- *
- * To obtain an instance of a {@link WindowManager} (see {@link #getSystemService(String)}) that
- * is configured to show windows on the given display call
- * {@link #createWindowContext(int, Bundle)} on the returned display Context or use an
- * {@link android.app.Activity}.
- *
+ * Returns a new <code>Context</code> object from the current context but with resources
+ * adjusted to match the metrics of <code>display</code>. Each call to this method
+ * returns a new instance of a context object. Context objects are not shared; however,
+ * common state (such as the {@link ClassLoader} and other resources for the same
+ * configuration) can be shared, so the <code>Context</code> itself is lightweight.
* <p>
- * Note that invoking #createDisplayContext(Display) from an UI context is not regarded
- * as an UI context. In other words, it is not suggested to access UI components (such as
- * obtain a {@link WindowManager} by {@link #getSystemService(String)})
- * from the context created from #createDisplayContext(Display).
- * </p>
+ * To obtain an instance of {@link WindowManager} configured to show windows on the given
+ * display, call {@link #createWindowContext(int, Bundle)} on the returned display context,
+ * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the
+ * returned window context.
+ * <p>
+ * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI
+ * context. Do not access UI components or obtain a {@link WindowManager} from the context
+ * created by <code>createDisplayContext(Display)</code>.
*
- * @param display A {@link Display} object specifying the display for whose metrics the
- * Context's resources should be tailored.
+ * @param display The display to which the current context's resources are adjusted.
*
- * @return A {@link Context} for the display.
- *
- * @see #getSystemService(String)
+ * @return A context for the display.
*/
@DisplayContext
public abstract Context createDisplayContext(@NonNull Display display);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a3efbd7..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).
@@ -5522,8 +5530,8 @@
/**
* A boolean extra, when used with {@link #ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD},
- * that specifies whether the system displayed attribution information in the
- * permission usage system UI for the chosen entry.
+ * that specifies whether the permission usage system UI is showing attribution information
+ * for the chosen entry.
*
* <p> The extra can only be true if application has specified attributionsAreUserVisible
* in its manifest. </p>
@@ -6916,7 +6924,6 @@
*/
public 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..516156d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -750,7 +750,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/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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 96a18dc..3c8b6e9 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1323,7 +1323,7 @@
* <p>Maximum flashlight brightness level.</p>
* <p>If this value is greater than 1, then the device supports controlling the
* flashlight brightness level via
- * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.
+ * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.
* If this value is equal to 1, flashlight brightness control is not supported.
* The value for this key will be null for devices with no flash unit.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -1335,7 +1335,7 @@
/**
* <p>Default flashlight brightness level to be set via
- * {android.hardware.camera2.CameraManager#setTorchStrengthLevel}.</p>
+ * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p>
* <p>If flash unit is available this will be greater than or equal to 1 and less
* or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p>
* <p>Setting flashlight brightness above the default level
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 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 fd34fa4..83e1061 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -30,6 +30,7 @@
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.window.DisplayWindowPolicyController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -379,6 +380,14 @@
public abstract void onEarlyInteractivityChange(boolean interactive);
/**
+ * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId}
+ *
+ * @param displayId The id of the display.
+ * @return The associated {@link DisplayWindowPolicyController}.
+ */
+ public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 80e8579..557e41a 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -25,7 +25,6 @@
import android.nfc.INfcFCardEmulation;
import android.nfc.NfcAdapter;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.Log;
import java.util.HashMap;
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index c2b33dd..f8f7dfe 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -237,6 +237,7 @@
public String toString() {
StringBuilder out = new StringBuilder("NfcFService: ");
out.append(getComponent());
+ out.append(", UID: " + mUid);
out.append(", description: " + mDescription);
out.append(", System Code: " + mSystemCode);
if (mDynamicSystemCode != null) {
@@ -257,6 +258,7 @@
NfcFServiceInfo thatService = (NfcFServiceInfo) o;
if (!thatService.getComponent().equals(this.getComponent())) return false;
+ if (thatService.getUid() != this.getUid()) return false;
if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false;
@@ -321,8 +323,9 @@
};
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(" " + getComponent() +
- " (Description: " + getDescription() + ")");
+ pw.println(" " + getComponent()
+ + " (Description: " + getDescription() + ")"
+ + " (UID: " + getUid() + ")");
pw.println(" System Code: " + getSystemCode());
pw.println(" NFCID2: " + getNfcid2());
pw.println(" T3tPmm: " + getT3tPmm());
diff --git a/core/java/android/os/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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 50ca9ff..8308e8e 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -60,6 +60,7 @@
List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userId, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
+ 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/UserManager.java b/core/java/android/os/UserManager.java
index cf4ce9b..e7e0c04 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3854,13 +3854,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 +3883,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
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/provider/Settings.java b/core/java/android/provider/Settings.java
index ae09b45..d08475d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -269,6 +269,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
*/
@@ -4537,6 +4547,25 @@
"haptic_feedback_intensity";
/**
+ * The intensity of haptic feedback vibrations for interaction with hardware components from
+ * the device, like buttons and sensors, if configurable.
+ *
+ * Not all devices are capable of changing their feedback intensity; on these devices
+ * there will likely be no difference between the various vibration intensities except for
+ * intensity 0 (off) and the rest.
+ *
+ * <b>Values:</b><br/>
+ * 0 - Vibration is disabled<br/>
+ * 1 - Weak vibrations<br/>
+ * 2 - Medium vibrations<br/>
+ * 3 - Strong vibrations
+ * @hide
+ */
+ @Readable
+ public static final String HARDWARE_HAPTIC_FEEDBACK_INTENSITY =
+ "hardware_haptic_feedback_intensity";
+
+ /**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 1b38f59..5d84af0 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3584,6 +3584,23 @@
"content://telephony/carriers/enforce_managed");
/**
+ * The {@code content://} style URL for the perferred APN used for internet.
+ *
+ * @hide
+ */
+ public static final Uri PREFERRED_APN_URI = Uri.parse(
+ "content://telephony/carriers/preferapn/subId/");
+
+ /**
+ * The {@code content://} style URL for the perferred APN set id.
+ *
+ * @hide
+ */
+ public static final Uri PREFERRED_APN_SET_URI = Uri.parse(
+ "content://telephony/carriers/preferapnset/subId/");
+
+
+ /**
* The column name for ENFORCE_MANAGED_URI, indicates whether DPC-owned APNs are enforced.
* @hide
*/
diff --git a/core/java/android/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/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index ec613ed..c5bc99d 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -153,11 +153,20 @@
/**
* Invocation of the voice assistant via hardware button.
+ * This is a private constant. Feel free to renumber as desired.
* @hide
*/
public static final int ASSISTANT_BUTTON = 10002;
/**
+ * The user has performed a long press on the power button hardware that is resulting
+ * in an action being performed.
+ * This is a private constant. Feel free to renumber as desired.
+ * @hide
+ */
+ public static final int LONG_PRESS_POWER_BUTTON = 10003;
+
+ /**
* Flag for {@link View#performHapticFeedback(int, int)
* View.performHapticFeedback(int, int)}: Ignore the setting in the
* view for whether to perform haptic feedback, do it always.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 1460cb2..c3a638c 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -295,7 +295,8 @@
/** Key code constant: Fast Forward media key. */
public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
/** Key code constant: Mute key.
- * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+ * Mute key for the microphone (unlike {@link #KEYCODE_VOLUME_MUTE}, which is the speaker mute
+ * key). */
public static final int KEYCODE_MUTE = 91;
/** Key code constant: Page Up key. */
public static final int KEYCODE_PAGE_UP = 92;
@@ -482,9 +483,10 @@
/** Key code constant: Numeric keypad ')' key. */
public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
/** Key code constant: Volume Mute key.
- * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
- * This key should normally be implemented as a toggle such that the first press
- * mutes the speaker and the second press restores the original volume. */
+ * Mute key for speaker (unlike {@link #KEYCODE_MUTE}, which is the mute key for the
+ * microphone). This key should normally be implemented as a toggle such that the first press
+ * mutes the speaker and the second press restores the original volume.
+ */
public static final int KEYCODE_VOLUME_MUTE = 164;
/** Key code constant: Info key.
* Common on TV remotes to show additional information related to what is
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3b4fcc0..f69bb6a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -129,17 +129,15 @@
/**
* The interface that apps use to talk to the window manager.
- * </p><p>
- * Each window manager instance is bound to a particular {@link Display}.
- * To obtain a {@link WindowManager} for a different display, use
- * {@link Context#createDisplayContext} to obtain a {@link Context} for that
- * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
- * to get the WindowManager.
- * </p><p>
- * The simplest way to show a window on another display is to create a
- * {@link Presentation}. The presentation will automatically obtain a
- * {@link WindowManager} and {@link Context} for that display.
- * </p>
+ * <p>
+ * Each window manager instance is bound to a {@link Display}. To obtain the
+ * <code>WindowManager</code> associated with a display,
+ * call {@link Context#createWindowContext(Display, int, Bundle)} to get the display's UI context,
+ * then call {@link Context#getSystemService(String)} or {@link Context#getSystemService(Class)} on
+ * the UI context.
+ * <p>
+ * The simplest way to show a window on a particular display is to create a {@link Presentation},
+ * which automatically obtains a <code>WindowManager</code> and context for the display.
*/
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
@@ -4792,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/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 4730eaa..7680aa6 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3957,8 +3957,10 @@
}
if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeString(mCollectionItemInfo.getRowTitle());
parcel.writeInt(mCollectionItemInfo.getRowIndex());
parcel.writeInt(mCollectionItemInfo.getRowSpan());
+ parcel.writeString(mCollectionItemInfo.getColumnTitle());
parcel.writeInt(mCollectionItemInfo.getColumnIndex());
parcel.writeInt(mCollectionItemInfo.getColumnSpan());
parcel.writeInt(mCollectionItemInfo.isHeading() ? 1 : 0);
@@ -4100,8 +4102,9 @@
ci.mHierarchical, ci.mSelectionMode);
CollectionItemInfo cii = other.mCollectionItemInfo;
mCollectionItemInfo = (cii == null) ? null
- : new CollectionItemInfo(cii.mRowIndex, cii.mRowSpan, cii.mColumnIndex,
- cii.mColumnSpan, cii.mHeading, cii.mSelected);
+ : new CollectionItemInfo(cii.mRowTitle, cii.mRowIndex, cii.mRowSpan,
+ cii.mColumnTitle, cii.mColumnIndex, cii.mColumnSpan,
+ cii.mHeading, cii.mSelected);
ExtraRenderingInfo ti = other.mExtraRenderingInfo;
mExtraRenderingInfo = (ti == null) ? null
: new ExtraRenderingInfo(ti);
@@ -4221,8 +4224,10 @@
if (mCollectionItemInfo != null) mCollectionItemInfo.recycle();
mCollectionItemInfo = isBitSet(nonDefaultFields, fieldIndex++)
? CollectionItemInfo.obtain(
+ parcel.readString(),
parcel.readInt(),
parcel.readInt(),
+ parcel.readString(),
parcel.readInt(),
parcel.readInt(),
parcel.readInt() == 1,
@@ -5570,8 +5575,9 @@
* @hide
*/
public static CollectionItemInfo obtain(CollectionItemInfo other) {
- return CollectionItemInfo.obtain(other.mRowIndex, other.mRowSpan, other.mColumnIndex,
- other.mColumnSpan, other.mHeading, other.mSelected);
+ return CollectionItemInfo.obtain(other.mRowTitle, other.mRowIndex, other.mRowSpan,
+ other.mColumnTitle, other.mColumnIndex, other.mColumnSpan, other.mHeading,
+ other.mSelected);
}
/**
@@ -5612,10 +5618,36 @@
*/
public static CollectionItemInfo obtain(int rowIndex, int rowSpan,
int columnIndex, int columnSpan, boolean heading, boolean selected) {
+ return obtain(null, rowIndex, rowSpan, null, columnIndex,
+ columnSpan, heading, selected);
+ }
+
+ /**
+ * Obtains a pooled instance.
+ *
+ * <p>In most situations object pooling is not beneficial. Creates a new instance using the
+ * constructor {@link
+ * AccessibilityNodeInfo.CollectionItemInfo#CollectionItemInfo(int,
+ * int, int, int, boolean, boolean)} instead.
+ *
+ * @param rowTitle The row title at which the item is located.
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnTitle The column title at which the item is located.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading. (Prefer
+ * {@link AccessibilityNodeInfo#setHeading(boolean)})
+ * @param selected Whether the item is selected.
+ */
+ @NonNull
+ public static CollectionItemInfo obtain(@Nullable String rowTitle, int rowIndex,
+ int rowSpan, @Nullable String columnTitle, int columnIndex, int columnSpan,
+ boolean heading, boolean selected) {
final CollectionItemInfo info = sPool.acquire();
if (info == null) {
- return new CollectionItemInfo(
- rowIndex, rowSpan, columnIndex, columnSpan, heading, selected);
+ return new CollectionItemInfo(rowTitle, rowIndex, rowSpan, columnTitle,
+ columnIndex, columnSpan, heading, selected);
}
info.mRowIndex = rowIndex;
@@ -5624,6 +5656,8 @@
info.mColumnSpan = columnSpan;
info.mHeading = heading;
info.mSelected = selected;
+ info.mRowTitle = rowTitle;
+ info.mColumnTitle = columnTitle;
return info;
}
@@ -5633,6 +5667,8 @@
private int mColumnSpan;
private int mRowSpan;
private boolean mSelected;
+ private String mRowTitle;
+ private String mColumnTitle;
/**
* Creates a new instance.
@@ -5660,12 +5696,33 @@
*/
public CollectionItemInfo(int rowIndex, int rowSpan, int columnIndex, int columnSpan,
boolean heading, boolean selected) {
+ this(null, rowIndex, rowSpan, null, columnIndex, columnSpan,
+ heading, selected);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * @param rowTitle The row title at which the item is located.
+ * @param rowIndex The row index at which the item is located.
+ * @param rowSpan The number of rows the item spans.
+ * @param columnTitle The column title at which the item is located.
+ * @param columnIndex The column index at which the item is located.
+ * @param columnSpan The number of columns the item spans.
+ * @param heading Whether the item is a heading.
+ * @param selected Whether the item is selected.
+ */
+ public CollectionItemInfo(@Nullable String rowTitle, int rowIndex, int rowSpan,
+ @Nullable String columnTitle, int columnIndex, int columnSpan, boolean heading,
+ boolean selected) {
mRowIndex = rowIndex;
mRowSpan = rowSpan;
mColumnIndex = columnIndex;
mColumnSpan = columnSpan;
mHeading = heading;
mSelected = selected;
+ mRowTitle = rowTitle;
+ mColumnTitle = columnTitle;
}
/**
@@ -5725,6 +5782,26 @@
}
/**
+ * Gets the row title at which the item is located.
+ *
+ * @return The row title.
+ */
+ @Nullable
+ public String getRowTitle() {
+ return mRowTitle;
+ }
+
+ /**
+ * Gets the column title at which the item is located.
+ *
+ * @return The column title.
+ */
+ @Nullable
+ public String getColumnTitle() {
+ return mColumnTitle;
+ }
+
+ /**
* Recycles this instance.
*
* <p>In most situations object pooling is not beneficial, and recycling is not necessary.
@@ -5741,6 +5818,8 @@
mRowSpan = 0;
mHeading = false;
mSelected = false;
+ mRowTitle = null;
+ mColumnTitle = null;
}
}
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
new file mode 100644
index 0000000..7677b89
--- /dev/null
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Abstract class to control the policies of the windows that can be displayed on the virtual
+ * display.
+ *
+ * @hide
+ */
+public abstract class DisplayWindowPolicyController {
+ /**
+ * The window flags that we are interested in.
+ * @see android.view.WindowManager.LayoutParams
+ * @see #keepActivityOnWindowFlagsChanged
+ */
+ private int mWindowFlags;
+
+ /**
+ * Returns {@code true} if the given window flags contain the flags that we're interested in.
+ */
+ public final boolean isInterestedWindowFlags(int windowFlags) {
+ return (mWindowFlags & windowFlags) != 0;
+ }
+
+ /**
+ * Sets the window flags that we’re interested in and expected
+ * #keepActivityOnWindowFlagsChanged to be called if any changes.
+ */
+ public final void setInterestedWindowFlags(int windowFlags) {
+ mWindowFlags = windowFlags;
+ }
+
+ /**
+ * Returns {@code true} if the given activities can be displayed on this virtual display.
+ */
+ public abstract boolean canContainActivities(@NonNull List<ActivityInfo> activities);
+
+ /**
+ * Called when an Activity window is layouted with the new changes where contains the
+ * window flags that we’re interested in.
+ * Returns {@code false} if the Activity cannot remain on the display and the activity task will
+ * be moved back to default display.
+ */
+ public abstract boolean keepActivityOnWindowFlagsChanged(
+ ActivityInfo activityInfo, int windowFlags);
+
+ /**
+ * This is called when the top activity of the display is changed.
+ */
+ public void onTopActivityChanged(ComponentName topActivity, int uid) {}
+
+ /**
+ * This is called when the apps that contains running activities on the display has changed.
+ */
+ public void onRunningAppsChanged(int[] runningUids) {}
+
+ /** Dump debug data */
+ public void dump(String prefix, final PrintWriter pw) {
+ pw.println(prefix + "DisplayWindowPolicyController{" + super.toString() + "}");
+ pw.println(prefix + " mWindowFlags=" + mWindowFlags);
+ }
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 5eb432e..cdfa206 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -19,12 +19,11 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
-import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentInfo;
/** @hide */
oneway interface ITaskFragmentOrganizer {
- void onTaskFragmentAppeared(in TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+ void onTaskFragmentAppeared(in TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentInfoChanged(in TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentVanished(in TaskFragmentInfo taskFragmentInfo);
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java
deleted file mode 100644
index 89d9a95..0000000
--- a/core/java/android/window/TaskFragmentAppearedInfo.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.window;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControl;
-
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-@TestApi
-public final class TaskFragmentAppearedInfo implements Parcelable {
-
- @NonNull
- private final TaskFragmentInfo mTaskFragmentInfo;
-
- @NonNull
- private final SurfaceControl mLeash;
-
- /** @hide */
- public TaskFragmentAppearedInfo(
- @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) {
- mTaskFragmentInfo = taskFragmentInfo;
- mLeash = leash;
- }
-
- @NonNull
- public TaskFragmentInfo getTaskFragmentInfo() {
- return mTaskFragmentInfo;
- }
-
- @NonNull
- public SurfaceControl getLeash() {
- return mLeash;
- }
-
- private TaskFragmentAppearedInfo(Parcel in) {
- mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
- mLeash = in.readTypedObject(SurfaceControl.CREATOR);
- }
-
- /** @hide */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedObject(mTaskFragmentInfo, flags);
- dest.writeTypedObject(mLeash, flags);
- }
-
- @NonNull
- public static final Creator<TaskFragmentAppearedInfo> CREATOR =
- new Creator<TaskFragmentAppearedInfo>() {
- @Override
- public TaskFragmentAppearedInfo createFromParcel(Parcel in) {
- return new TaskFragmentAppearedInfo(in);
- }
-
- @Override
- public TaskFragmentAppearedInfo[] newArray(int size) {
- return new TaskFragmentAppearedInfo[size];
- }
- };
-
- @Override
- public String toString() {
- return "TaskFragmentAppearedInfo{"
- + " taskFragmentInfo=" + mTaskFragmentInfo
- + "}";
- }
-
- /** @hide */
- @Override
- public int describeContents() {
- return 0;
- }
-}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 337c5a1..7e7d370 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -120,8 +120,7 @@
}
/** Called when a TaskFragment is created and organized by this organizer. */
- public void onTaskFragmentAppeared(
- @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {}
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
/** Called when the status of an organized TaskFragment is changed. */
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
@@ -169,7 +168,7 @@
private final ITaskFragmentOrganizer mInterface = new ITaskFragmentOrganizer.Stub() {
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
mExecutor.execute(
() -> TaskFragmentOrganizer.this.onTaskFragmentAppeared(taskFragmentInfo));
}
diff --git a/core/java/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/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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 601280a..f10880e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5963,6 +5963,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/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/config.xml b/core/res/res/values/config.xml
index 2c60fbd8..a36785f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4943,9 +4943,10 @@
the app in the letterbox mode. -->
<item name="config_fixedOrientationLetterboxAspectRatio" format="float" type="dimen">0.0</item>
- <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and
- min between device bottom corner radii will be used instead. -->
- <integer name="config_letterboxActivityCornersRadius">-1</integer>
+ <!-- Corners radius for activity presented the letterbox mode. Values < 0 enable rounded
+ corners with radius equal to min between device bottom corner radii. Default 0 value turns
+ off rounded corners logic in LetterboxUiController. -->
+ <integer name="config_letterboxActivityCornersRadius">0</integer>
<!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
ignored and 0 is used. -->
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/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/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/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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 85ef270..df751fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -27,8 +27,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.view.SurfaceControl;
-import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
@@ -51,9 +49,6 @@
/** Mapping from the client assigned unique token to the {@link TaskFragmentInfo}. */
private final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
- /** Mapping from the client assigned unique token to the TaskFragment {@link SurfaceControl}. */
- private final Map<IBinder, SurfaceControl> mFragmentLeashes = new ArrayMap<>();
-
/**
* Mapping from the client assigned unique token to the TaskFragment parent
* {@link Configuration}.
@@ -67,7 +62,7 @@
* Callback that notifies the controller about changes to task fragments.
*/
interface TaskFragmentCallback {
- void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo);
+ void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@@ -259,15 +254,12 @@
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
- final TaskFragmentInfo info = taskFragmentAppearedInfo.getTaskFragmentInfo();
- final IBinder fragmentToken = info.getFragmentToken();
- final SurfaceControl leash = taskFragmentAppearedInfo.getLeash();
- mFragmentInfos.put(fragmentToken, info);
- mFragmentLeashes.put(fragmentToken, leash);
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ mFragmentInfos.put(fragmentToken, taskFragmentInfo);
if (mCallback != null) {
- mCallback.onTaskFragmentAppeared(taskFragmentAppearedInfo);
+ mCallback.onTaskFragmentAppeared(taskFragmentInfo);
}
}
@@ -284,7 +276,6 @@
@Override
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
- mFragmentLeashes.remove(taskFragmentInfo.getFragmentToken());
mFragmentParentConfigs.remove(taskFragmentInfo.getFragmentToken());
if (mCallback != null) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 68c1904..fe6c7ba 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -37,7 +37,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
@@ -110,14 +109,13 @@
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
- TaskFragmentContainer container = getContainer(
- taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken());
+ public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
+ container.setInfo(taskFragmentInfo);
if (container.isFinished()) {
mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
}
diff --git a/libs/WindowManager/Shell/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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index d239e56..9ceed24 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
@@ -244,15 +244,24 @@
// Fullscreen
//
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract FullscreenTaskListener optionalFullscreenTaskListener();
+
@WMSingleton
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
+ @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
- Optional<RecentTasksController> recentTasksOptional
- ) {
- return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
- recentTasksOptional);
+ Optional<RecentTasksController> recentTasksOptional) {
+ if (fullscreenTaskListener.isPresent()) {
+ return fullscreenTaskListener.get();
+ } else {
+ return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
+ recentTasksOptional);
+ }
}
//
@@ -337,7 +346,7 @@
@Provides
static Optional<OneHandedController> providesOneHandedController(
@DynamicOverride Optional<OneHandedController> oneHandedController) {
- if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
+ if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
return oneHandedController;
}
return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/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/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6cc5f09..90135f2a 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
@@ -1282,6 +1282,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/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/input/PointerController.cpp b/libs/input/PointerController.cpp
index 8f04cfb..f43586f 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -17,24 +17,29 @@
#define LOG_TAG "PointerController"
//#define LOG_NDEBUG 0
-// Log debug messages about pointer updates
-#define DEBUG_POINTER_UPDATES 0
-
#include "PointerController.h"
-#include "MouseCursorController.h"
#include "PointerControllerContext.h"
-#include "TouchSpotController.h"
-#include <log/log.h>
-
-#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkColor.h>
-#include <SkPaint.h>
namespace android {
+namespace {
+
+const ui::Transform kIdentityTransform;
+
+} // namespace
+
+// --- PointerController::DisplayInfoListener ---
+
+void PointerController::DisplayInfoListener::onWindowInfosChanged(
+ const std::vector<android::gui::WindowInfo>&,
+ const std::vector<android::gui::DisplayInfo>& displayInfo) {
+ mPointerController.onDisplayInfosChanged(displayInfo);
+}
+
// --- PointerController ---
std::shared_ptr<PointerController> PointerController::create(
@@ -63,9 +68,16 @@
PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
const sp<Looper>& looper,
const sp<SpriteController>& spriteController)
- : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
+ : mContext(policy, looper, spriteController, *this),
+ mCursorController(mContext),
+ mDisplayInfoListener(new DisplayInfoListener(*this)) {
std::scoped_lock lock(mLock);
mLocked.presentation = Presentation::SPOT;
+ SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener);
+}
+
+PointerController::~PointerController() {
+ SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener);
}
bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX,
@@ -74,7 +86,14 @@
}
void PointerController::move(float deltaX, float deltaY) {
- mCursorController.move(deltaX, deltaY);
+ const int32_t displayId = mCursorController.getDisplayId();
+ vec2 transformed;
+ {
+ std::scoped_lock lock(mLock);
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ transformed = transformWithoutTranslation(transform, {deltaX, deltaY});
+ }
+ mCursorController.move(transformed.x, transformed.y);
}
void PointerController::setButtonState(int32_t buttonState) {
@@ -86,12 +105,26 @@
}
void PointerController::setPosition(float x, float y) {
- std::scoped_lock lock(mLock);
- mCursorController.setPosition(x, y);
+ const int32_t displayId = mCursorController.getDisplayId();
+ vec2 transformed;
+ {
+ std::scoped_lock lock(mLock);
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ transformed = transform.transform(x, y);
+ }
+ mCursorController.setPosition(transformed.x, transformed.y);
}
void PointerController::getPosition(float* outX, float* outY) const {
+ const int32_t displayId = mCursorController.getDisplayId();
mCursorController.getPosition(outX, outY);
+ {
+ std::scoped_lock lock(mLock);
+ const auto& transform = getTransformForDisplayLocked(displayId);
+ const auto xy = transform.inverse().transform(*outX, *outY);
+ *outX = xy.x;
+ *outY = xy.y;
+ }
}
int32_t PointerController::getDisplayId() const {
@@ -130,11 +163,25 @@
void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
BitSet32 spotIdBits, int32_t displayId) {
std::scoped_lock lock(mLock);
+ std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
+ const ui::Transform& transform = getTransformForDisplayLocked(displayId);
+
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
+ const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
+
+ const vec2 xy = transform.transform(spotCoords[index].getXYValue());
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+
+ float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+ outSpotCoords[index].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ }
+
auto it = mLocked.spotControllers.find(displayId);
if (it == mLocked.spotControllers.end()) {
mLocked.spotControllers.try_emplace(displayId, displayId, mContext);
}
- mLocked.spotControllers.at(displayId).setSpots(spotCoords, spotIdToIndex, spotIdBits);
+ mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits);
}
void PointerController::clearSpots() {
@@ -194,7 +241,7 @@
void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports) {
std::unordered_set<int32_t> displayIdSet;
- for (DisplayViewport viewport : viewports) {
+ for (const DisplayViewport& viewport : viewports) {
displayIdSet.insert(viewport.displayId);
}
@@ -214,4 +261,17 @@
}
}
+void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) {
+ std::scoped_lock lock(mLock);
+ mLocked.mDisplayInfos = displayInfo;
+}
+
+const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const {
+ const auto& di = mLocked.mDisplayInfos;
+ auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) {
+ return info.displayId == displayId;
+ });
+ return it != di.end() ? it->transform : kIdentityTransform;
+}
+
} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 97567ba..796077f 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -47,7 +47,7 @@
const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController);
- virtual ~PointerController() = default;
+ ~PointerController() override;
virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
virtual void move(float deltaX, float deltaY);
@@ -72,6 +72,8 @@
void reloadPointerResources();
void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
+ void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos);
+
private:
friend PointerControllerContext::LooperCallback;
friend PointerControllerContext::MessageHandler;
@@ -85,9 +87,23 @@
struct Locked {
Presentation presentation;
+ std::vector<gui::DisplayInfo> mDisplayInfos;
std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers;
} mLocked GUARDED_BY(mLock);
+ class DisplayInfoListener : public gui::WindowInfosListener {
+ public:
+ explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){};
+ void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&,
+ const std::vector<android::gui::DisplayInfo>&) override;
+
+ private:
+ PointerController& mPointerController;
+ };
+ sp<DisplayInfoListener> mDisplayInfoListener;
+
+ const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock);
+
PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper,
const sp<SpriteController>& spriteController);
void clearSpotsLocked();
diff --git a/media/Android.bp b/media/Android.bp
index 15b24b2..fcdfd72 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -115,6 +115,7 @@
aidl_interface {
name: "android.media.soundtrigger.types",
vendor_available: true,
+ host_supported: true,
flags: [
"-Werror",
"-Weverything",
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/core/java/android/window/TaskFragmentAppearedInfo.aidl b/media/java/android/media/tv/TsRequest.aidl
similarity index 77%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to media/java/android/media/tv/TsRequest.aidl
index 3729c09..0abc7ec 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/media/java/android/media/tv/TsRequest.aidl
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.media.tv;
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+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/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 0dd64b8..dabea30 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -16,13 +16,15 @@
package android.media.tv.interactive;
+import android.view.InputChannel;
+
/**
* Interface a client of the ITvIAppManager implements, to identify itself and receive information
* about changes to the state of each TV interactive application service.
* @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);
}
\ No newline at end of file
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/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index 093e1be..7479b2b 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -23,9 +23,15 @@
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 +73,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 +83,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);
@@ -351,18 +357,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 +449,43 @@
}
/**
+ * 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;
+ }
+ }
+
+ /**
* Releases this session.
*/
public void release() {
@@ -444,12 +502,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 {
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 78b8173..25dec62 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -25,11 +25,17 @@
import android.content.Intent;
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 +91,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 +130,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;
@@ -182,6 +192,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.
*
@@ -226,6 +290,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;
@@ -281,10 +378,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 +410,26 @@
public void dispatchSurfaceChanged(int format, int width, int height) {
mSessionImpl.dispatchSurfaceChanged(format, width, height);
}
+
+ 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 +442,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 +457,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/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index ae271120..f0f576e 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -251,8 +251,8 @@
private native int nativeFlushFilter();
private native int nativeRead(byte[] buffer, long offset, long size);
private native int nativeClose();
- private native String nativeCreateSharedFilter();
- private native void nativeReleaseSharedFilter(String token);
+ private native String nativeAcquireSharedFilterToken();
+ private native void nativeFreeSharedFilterToken(String token);
// Called by JNI
private Filter(long id) {
@@ -562,20 +562,20 @@
}
/**
- * Creates a shared filter.
+ * Acquires a shared filter token.
*
* @return a string shared filter token.
*/
@Nullable
- public String createSharedFilter() {
+ public String acquireSharedFilterToken() {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
if (mIsStarted || mIsShared) {
- Log.d(TAG, "Create shared filter in a wrong state, started: " +
+ Log.d(TAG, "Acquire shared filter in a wrong state, started: " +
mIsStarted + "shared: " + mIsShared);
return null;
}
- String token = nativeCreateSharedFilter();
+ String token = nativeAcquireSharedFilterToken();
if (token != null) {
mIsShared = true;
}
@@ -584,17 +584,17 @@
}
/**
- * Releases a shared filter.
+ * Frees a shared filter token.
*
* @param filterToken the token of the shared filter being released.
*/
- public void releaseSharedFilter(@NonNull String filterToken) {
+ public void freeSharedFilterToken(@NonNull String filterToken) {
synchronized (mLock) {
TunerUtils.checkResourceState(TAG, mIsClosed);
if (!mIsShared) {
return;
}
- nativeReleaseSharedFilter(filterToken);
+ nativeFreeSharedFilterToken(filterToken);
mIsShared = false;
}
}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index 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 c4dfee4..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;
}
@@ -3890,22 +3905,22 @@
return (jint)r;
}
-static jstring android_media_tv_Tuner_create_shared_filter(JNIEnv *env, jobject filter) {
+static jstring android_media_tv_Tuner_acquire_shared_filter_token(JNIEnv *env, jobject filter) {
sp<FilterClient> filterClient = getFilterClient(env, filter);
if (filterClient == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException",
- "Failed to create shared filter: filter client not found");
+ "Failed to acquire shared filter token: filter client not found");
return nullptr;
}
- string token = filterClient->createSharedFilter();
+ string token = filterClient->acquireSharedFilterToken();
if (token.empty()) {
return nullptr;
}
return env->NewStringUTF(token.data());
}
-static void android_media_tv_Tuner_release_shared_filter(
+static void android_media_tv_Tuner_free_shared_filter_token(
JNIEnv *env, jobject filter, jstring token) {
sp<FilterClient> filterClient = getFilterClient(env, filter);
if (filterClient == nullptr) {
@@ -3915,7 +3930,7 @@
}
std::string filterToken(env->GetStringUTFChars(token, nullptr));
- filterClient->releaseSharedFilter(filterToken);
+ filterClient->freeSharedFilterToken(filterToken);
}
static sp<TimeFilterClient> getTimeFilterClient(JNIEnv *env, jobject filter) {
@@ -4408,10 +4423,10 @@
{ "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
{ "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
- {"nativeCreateSharedFilter", "()Ljava/lang/String;",
- (void *)android_media_tv_Tuner_create_shared_filter},
- {"nativeReleaseSharedFilter", "(Ljava/lang/String;)V",
- (void *)android_media_tv_Tuner_release_shared_filter},
+ {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
+ (void *)android_media_tv_Tuner_acquire_shared_filter_token},
+ {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
+ (void *)android_media_tv_Tuner_free_shared_filter_token},
};
static const JNINativeMethod gSharedFilterMethods[] = {
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index e8b3de8..bd283dc 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -196,10 +196,10 @@
return Result::INVALID_STATE;
}
-string FilterClient::createSharedFilter() {
+string FilterClient::acquireSharedFilterToken() {
if (mTunerFilter != nullptr) {
string filterToken;
- if (mTunerFilter->createSharedFilter(&filterToken).isOk()) {
+ if (mTunerFilter->acquireSharedFilterToken(&filterToken).isOk()) {
return filterToken;
}
}
@@ -207,9 +207,9 @@
return "";
}
-Result FilterClient::releaseSharedFilter(const string& filterToken) {
+Result FilterClient::freeSharedFilterToken(const string& filterToken) {
if (mTunerFilter != nullptr) {
- Status s = mTunerFilter->releaseSharedFilter(filterToken);
+ Status s = mTunerFilter->freeSharedFilterToken(filterToken);
return ClientHelper::getServiceSpecificErrorCode(s);
}
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index c031b2a..7ebe7bc7 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -143,14 +143,14 @@
Result close();
/**
- * Create a new SharedFiler.
+ * Accquire a new SharedFiler token.
*/
- string createSharedFilter();
+ string acquireSharedFilterToken();
/**
- * Release SharedFiler.
+ * Release SharedFiler token.
*/
- Result releaseSharedFilter(const string& filterToken);
+ Result freeSharedFilterToken(const string& filterToken);
private:
Result getFilterMq();
diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp
new file mode 100644
index 0000000..2b81200
--- /dev/null
+++ b/omapi/aidl/Android.bp
@@ -0,0 +1,35 @@
+// Copyright 2020, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "android.se.omapi",
+ vendor_available: true,
+ srcs: ["android/se/omapi/*.aidl"],
+ stability: "vintf",
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ },
+ rust: {
+ enabled: true,
+ },
+ ndk: {
+ separate_platform_variant: false,
+ },
+ },
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl
new file mode 100644
index 0000000..725013a
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementChannel.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementChannel {
+ void close();
+ boolean isClosed();
+ boolean isBasicChannel();
+ byte[] getSelectResponse();
+ byte[] transmit(in byte[] command);
+ boolean selectNext();
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl
new file mode 100644
index 0000000..77e1c53f
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementListener.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementListener {
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl
new file mode 100644
index 0000000..2b10c47
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementReader.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementReader {
+ boolean isSecureElementPresent();
+ android.se.omapi.ISecureElementSession openSession();
+ void closeSessions();
+ boolean reset();
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
new file mode 100644
index 0000000..ae63462
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *//*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementService {
+ String[] getReaders();
+ android.se.omapi.ISecureElementReader getReader(in String reader);
+ boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames);
+}
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl
new file mode 100644
index 0000000..06287c5
--- /dev/null
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementSession.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *//*
+ * Copyright (c) 2015-2017, The Linux Foundation.
+ *//*
+ * Contributed by: Giesecke & Devrient GmbH.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.se.omapi;
+/* @hide */
+@VintfStability
+interface ISecureElementSession {
+ byte[] getAtr();
+ void close();
+ void closeChannels();
+ boolean isClosed();
+ android.se.omapi.ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener);
+ android.se.omapi.ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2, in android.se.omapi.ISecureElementListener listener);
+}
diff --git a/core/java/android/se/omapi/ISecureElementChannel.aidl b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
similarity index 94%
rename from core/java/android/se/omapi/ISecureElementChannel.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
index 4ae57ab..bbd3c14 100644
--- a/core/java/android/se/omapi/ISecureElementChannel.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementChannel.aidl
@@ -22,6 +22,7 @@
import android.se.omapi.ISecureElementSession;
/** @hide */
+@VintfStability
interface ISecureElementChannel {
/**
@@ -58,6 +59,9 @@
* Transmits the specified command APDU and returns the response APDU.
* MANAGE channel commands are not supported.
* Selection of applets is not supported in logical channels.
+ *
+ * @param command Command APDU, its structure is defined in ISO/IEC 7816-4
+ * in Standard byte format
*/
byte[] transmit(in byte[] command);
diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl
similarity index 97%
rename from core/java/android/se/omapi/ISecureElementListener.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementListener.aidl
index e9dd181..479dcd7 100644
--- a/core/java/android/se/omapi/ISecureElementListener.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementListener.aidl
@@ -23,5 +23,6 @@
* Interface to receive call-backs when the service is connected.
* @hide
*/
+@VintfStability
interface ISecureElementListener {
}
diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl
similarity index 94%
rename from core/java/android/se/omapi/ISecureElementReader.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementReader.aidl
index 41244ab..a6979fa 100644
--- a/core/java/android/se/omapi/ISecureElementReader.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementReader.aidl
@@ -22,6 +22,7 @@
import android.se.omapi.ISecureElementSession;
/** @hide */
+@VintfStability
interface ISecureElementReader {
/**
@@ -34,7 +35,7 @@
* Connects to a secure element in this reader. <br>
* This method prepares (initialises) the Secure Element for communication
* before the Session object is returned (e.g. powers the Secure Element by
- * ICC ON if its not already on). There might be multiple sessions opened at
+ * ICC ON if it is not already on). There might be multiple sessions opened at
* the same time on the same reader. The system ensures the interleaving of
* APDUs between the respective sessions.
*
diff --git a/core/java/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
similarity index 69%
rename from core/java/android/se/omapi/ISecureElementService.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementService.aidl
index 4fa799e..61ae481 100644
--- a/core/java/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
@@ -28,23 +28,31 @@
* SecureElement service interface.
* @hide
*/
+@VintfStability
interface ISecureElementService {
/**
* Returns the friendly names of available Secure Element readers.
+ * <ul>
+ * <li>If the reader is a SIM reader, then its name must be "SIM[Slot]".</li>
+ * <li>If the reader is a SD or micro SD reader, then its name must be "SD[Slot]"</li>
+ * <li>If the reader is a embedded SE reader, then its name must be "eSE[Slot]"</li>
+ * </ul>
+ * Slot is a decimal number without leading zeros. The Numbering must start with 1
+ * (e.g. SIM1, SIM2, ... or SD1, SD2, ... or eSE1, eSE2, ...).
*/
String[] getReaders();
/**
* Returns SecureElement Service reader object to the given name.
*/
- ISecureElementReader getReader(String reader);
+ ISecureElementReader getReader(in String reader);
/**
* Checks if the application defined by the package name is allowed to
* receive NFC transaction events for the defined AID.
*/
- boolean[] isNFCEventAllowed(String reader, in byte[] aid,
+ boolean[] isNFCEventAllowed(in String reader, in byte[] aid,
in String[] packageNames);
}
diff --git a/core/java/android/se/omapi/ISecureElementSession.aidl b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl
similarity index 84%
rename from core/java/android/se/omapi/ISecureElementSession.aidl
rename to omapi/aidl/android/se/omapi/ISecureElementSession.aidl
index 8ea599f..129ecc4 100644
--- a/core/java/android/se/omapi/ISecureElementSession.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementSession.aidl
@@ -27,6 +27,7 @@
import android.se.omapi.ISecureElementListener;
/** @hide */
+@VintfStability
interface ISecureElementSession {
/**
@@ -45,7 +46,6 @@
*/
void closeChannels();
-
/**
* Tells if this session is closed.
*
@@ -59,15 +59,19 @@
* applet if aid != null.
* Logical channels cannot be opened with this connection.
* Use interface method openLogicalChannel() to open a logical channel.
+ * Listener is passed to secure element service and used to monitor whether
+ * the client application that uses OMAPI is still alive or not.
*/
ISecureElementChannel openBasicChannel(in byte[] aid, in byte p2,
- ISecureElementListener listener);
+ in ISecureElementListener listener);
/**
* Opens a connection using the next free logical channel of the card in the
* specified reader. Selects the specified applet.
* Selection of other applets with this connection is not supported.
+ * Listener is passed to secure element service and used to monitor whether
+ * the client application that uses OMAPI is still alive or not.
*/
ISecureElementChannel openLogicalChannel(in byte[] aid, in byte p2,
- ISecureElementListener listener);
+ in ISecureElementListener listener);
}
diff --git a/omapi/aidl/vts/functional/AccessControlApp/Android.bp b/omapi/aidl/vts/functional/AccessControlApp/Android.bp
new file mode 100644
index 0000000..f03c3f6
--- /dev/null
+++ b/omapi/aidl/vts/functional/AccessControlApp/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "VtsHalOmapiSeAccessControlTestCases",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalOmapiSeAccessControlTestCases.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libcutils",
+ "libhidlbase",
+ "libnativehelper",
+ "libutils",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "VtsHalHidlTargetTestBase",
+ "android.se.omapi-V1-ndk",
+ ],
+ cflags: [
+ "-O0",
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+ require_root: true,
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp
new file mode 100644
index 0000000..9ea6543
--- /dev/null
+++ b/omapi/aidl/vts/functional/AccessControlApp/VtsHalOmapiSeAccessControlTestCases.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/se/omapi/BnSecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementChannel.h>
+#include <aidl/android/se/omapi/ISecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementReader.h>
+#include <aidl/android/se/omapi/ISecureElementService.h>
+#include <aidl/android/se/omapi/ISecureElementSession.h>
+
+#include <VtsCoreUtil.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <utils/String16.h>
+
+using namespace std;
+using namespace ::testing;
+using namespace android;
+
+int main(int argc, char** argv) {
+ InitGoogleTest(&argc, argv);
+ int status = RUN_ALL_TESTS();
+ return status;
+}
+
+namespace {
+
+class OMAPISEAccessControlTest : public TestWithParam<std::string> {
+ protected:
+
+ class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {};
+
+ /**
+ * Verifies TLV data
+ *
+ * @return true if the data is tlv formatted, false otherwise
+ */
+ bool verifyBerTlvData(std::vector<uint8_t> tlv) {
+ if (tlv.size() == 0) {
+ LOG(ERROR) << "Invalid tlv, null";
+ return false;
+ }
+ int i = 0;
+ if ((tlv[i++] & 0x1F) == 0x1F) {
+ // extra byte for TAG field
+ i++;
+ }
+
+ int len = tlv[i++] & 0xFF;
+ if (len > 127) {
+ // more than 1 byte for length
+ int bytesLength = len - 128;
+ len = 0;
+ for (int j = bytesLength; j > 0; j--) {
+ len += (len << 8) + (tlv[i++] & 0xFF);
+ }
+ }
+ // Additional 2 bytes for the SW
+ return (tlv.size() == (i + len + 2));
+ }
+
+ void testSelectableAid(
+ std::vector<std::vector<uint8_t>> authorizedAids) {
+ for (auto aid : authorizedAids) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::vector<uint8_t> selectResponse = {};
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+ ASSERT_TRUE(
+ verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+ }
+ }
+ }
+ }
+
+ void testUnauthorisedAid(
+ std::vector<std::vector<uint8_t>> unAuthorizedAids) {
+ for (auto aid : unAuthorizedAids) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ if (!res.isOk()) {
+ ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+ ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+ }
+ }
+ }
+ }
+ }
+
+ void testTransmitAPDU(
+ std::vector<uint8_t> aid,
+ std::vector<std::vector<uint8_t>> apdus) {
+ for (auto apdu : apdus) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ ASSERT_NE(reader, nullptr) << "reader is null";
+ bool status = false;
+ std::vector<uint8_t> selectResponse = {};
+ std::vector<uint8_t> transmitResponse = {};
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+ ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+ ASSERT_TRUE(
+ verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+
+ res = channel->transmit(apdu, &transmitResponse);
+ LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+ << " Message: " << res.getMessage();
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+ ASSERT_TRUE(res.isOk()) << "failed to transmit";
+ }
+ }
+ }
+ }
+
+ void testUnauthorisedAPDU(
+ std::vector<uint8_t> aid,
+ std::vector<std::vector<uint8_t>> apdus) {
+ for (auto apdu : apdus) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ ASSERT_NE(reader, nullptr) << "reader is null";
+ bool status = false;
+ std::vector<uint8_t> selectResponse = {};
+ std::vector<uint8_t> transmitResponse = {};
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+ ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+ ASSERT_TRUE(
+ verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+
+ res = channel->transmit(apdu, &transmitResponse);
+ LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+ << " Message: " << res.getMessage();
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+ if (!res.isOk()) {
+ ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+ ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+ }
+ }
+ }
+ }
+ }
+
+ bool supportOMAPIReaders() {
+ return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
+ }
+
+ void getFirstApiLevel(int32_t* outApiLevel) {
+ int32_t firstApiLevel = property_get_int32(FEATURE_SE_API_LEVEL.c_str(), -1);
+ if (firstApiLevel < 0) {
+ firstApiLevel = property_get_int32(FEATURE_SE_SDK_VERSION.c_str(), -1);
+ }
+ ASSERT_GT(firstApiLevel, 0); // first_api_level must exist
+ *outApiLevel = firstApiLevel;
+ return;
+ }
+
+ bool supportsHardware() {
+ bool lowRamDevice = property_get_bool(FEATURE_SE_LOW_RAM.c_str(), true);
+ return !lowRamDevice || deviceSupportsFeature(FEATURE_SE_HARDWARE_WATCH.c_str()) ||
+ deviceSupportsFeature(FEATURE_SE_OMAPI_SERVICE.c_str()); // android.se.omapi
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(supportsHardware());
+ int32_t apiLevel;
+ getFirstApiLevel(&apiLevel);
+ ASSERT_TRUE(apiLevel > 27);
+ ASSERT_TRUE(supportOMAPIReaders());
+ LOG(INFO) << "get OMAPI service with name:" << GetParam();
+ ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
+ mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder);
+ ASSERT_TRUE(mOmapiSeService);
+
+ std::vector<std::string> readers = {};
+
+ if (mOmapiSeService != NULL) {
+ auto status = mOmapiSeService->getReaders(&readers);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ for (auto readerName : readers) {
+ // Filter eSE readers only
+ if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+ std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader;
+ status = mOmapiSeService->getReader(readerName, &reader);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ mVSReaders[readerName] = reader;
+ }
+ }
+ }
+ }
+
+ void TearDown() override {
+ if (mOmapiSeService != nullptr) {
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ reader->closeSessions();
+ }
+ }
+ }
+ }
+
+ static inline std::string const ESE_READER_PREFIX = "eSE";
+ static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+ static inline std::string const FEATURE_SE_LOW_RAM = "ro.config.low_ram";
+ static inline std::string const FEATURE_SE_HARDWARE_WATCH = "android.hardware.type.watch";
+ static inline std::string const FEATURE_SE_OMAPI_SERVICE = "com.android.se";
+ static inline std::string const FEATURE_SE_SDK_VERSION = "ro.build.version.sdk";
+ static inline std::string const FEATURE_SE_API_LEVEL = "ro.product.first_api_level";
+
+ std::vector<uint8_t> AID_40 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x40};
+ std::vector<uint8_t> AID_41 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x41};
+ std::vector<uint8_t> AID_42 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x42};
+ std::vector<uint8_t> AID_43 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x43};
+ std::vector<uint8_t> AID_44 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x44};
+ std::vector<uint8_t> AID_45 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x45};
+ std::vector<uint8_t> AID_46 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x46};
+ std::vector<uint8_t> AID_47 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x47};
+ std::vector<uint8_t> AID_48 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x48};
+ std::vector<uint8_t> AID_49 = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x49};
+ std::vector<uint8_t> AID_4A = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4A};
+ std::vector<uint8_t> AID_4B = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4B};
+ std::vector<uint8_t> AID_4C = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4C};
+ std::vector<uint8_t> AID_4D = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4D};
+ std::vector<uint8_t> AID_4E = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4E};
+ std::vector<uint8_t> AID_4F = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x4F};
+
+ std::vector<std::vector<uint8_t>> AUTHORIZED_AID = {AID_40, AID_41, AID_42, AID_44, AID_45,
+ AID_47, AID_48, AID_49, AID_4A, AID_4B,
+ AID_4C, AID_4D, AID_4E, AID_4F};
+ std::vector<std::vector<uint8_t>> UNAUTHORIZED_AID = {AID_43, AID_46};
+
+ /* Authorized APDU for AID_40 */
+ std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_40 = {
+ {0x00, 0x06, 0x00, 0x00},
+ {0xA0, 0x06, 0x00, 0x00},
+ };
+ /* Unauthorized APDU for AID_40 */
+ std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_40 = {
+ {0x00, 0x08, 0x00, 0x00, 0x00},
+ {0x80, 0x06, 0x00, 0x00},
+ {0xA0, 0x08, 0x00, 0x00, 0x00},
+ {0x94, 0x06, 0x00, 0x00, 0x00},
+ };
+
+ /* Authorized APDU for AID_41 */
+ std::vector<std::vector<uint8_t>> AUTHORIZED_APDU_AID_41 = {
+ {0x94, 0x06, 0x00, 0x00},
+ {0x94, 0x08, 0x00, 0x00, 0x00},
+ {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}};
+ /* Unauthorized APDU for AID_41 */
+ std::vector<std::vector<uint8_t>> UNAUTHORIZED_APDU_AID_41 = {
+ {0x00, 0x06, 0x00, 0x00},
+ {0x80, 0x06, 0x00, 0x00},
+ {0xA0, 0x06, 0x00, 0x00},
+ {0x00, 0x08, 0x00, 0x00, 0x00},
+ {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0x80, 0x08, 0x00, 0x00, 0x00},
+ {0xA0, 0x08, 0x00, 0x00, 0x00},
+ {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ };
+
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService;
+
+ std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
+ mVSReaders = {};
+};
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAID) {
+ testSelectableAid(AUTHORIZED_AID);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorizedAID) {
+ testUnauthorisedAid(UNAUTHORIZED_AID);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID40) {
+ testTransmitAPDU(AID_40, AUTHORIZED_APDU_AID_40);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID40) {
+ testUnauthorisedAPDU(AID_40, UNAUTHORIZED_APDU_AID_40);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestAuthorizedAPDUAID41) {
+ testTransmitAPDU(AID_41, AUTHORIZED_APDU_AID_41);
+}
+
+TEST_P(OMAPISEAccessControlTest, TestUnauthorisedAPDUAID41) {
+ testUnauthorisedAPDU(AID_41, UNAUTHORIZED_APDU_AID_41);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEAccessControlTest,
+ testing::ValuesIn(::android::getAidlHalInstanceNames(
+ aidl::android::se::omapi::ISecureElementService::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEAccessControlTest);
+
+} // namespace
diff --git a/omapi/aidl/vts/functional/omapi/Android.bp b/omapi/aidl/vts/functional/omapi/Android.bp
new file mode 100644
index 0000000..c3ab8d1
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "VtsHalOmapiSeServiceV1_TargetTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ srcs: [
+ "VtsHalOmapiSeServiceV1_TargetTest.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libcutils",
+ "libhidlbase",
+ "libnativehelper",
+ "libutils",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "VtsHalHidlTargetTestBase",
+ "android.se.omapi-V1-ndk",
+ ],
+ cflags: [
+ "-O0",
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+ require_root: true,
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
new file mode 100644
index 0000000..319cb7e
--- /dev/null
+++ b/omapi/aidl/vts/functional/omapi/VtsHalOmapiSeServiceV1_TargetTest.cpp
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/se/omapi/BnSecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementChannel.h>
+#include <aidl/android/se/omapi/ISecureElementListener.h>
+#include <aidl/android/se/omapi/ISecureElementReader.h>
+#include <aidl/android/se/omapi/ISecureElementService.h>
+#include <aidl/android/se/omapi/ISecureElementSession.h>
+
+#include <VtsCoreUtil.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <cutils/properties.h>
+#include <gtest/gtest.h>
+#include <hidl/GtestPrinter.h>
+#include <hidl/ServiceManagement.h>
+#include <utils/String16.h>
+
+using namespace std;
+using namespace ::testing;
+using namespace android;
+
+int main(int argc, char** argv) {
+ InitGoogleTest(&argc, argv);
+ int status = RUN_ALL_TESTS();
+ return status;
+}
+
+namespace {
+
+class OMAPISEServiceHalTest : public TestWithParam<std::string> {
+ protected:
+ class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {};
+
+ void testSelectableAid(
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+ std::vector<uint8_t> aid, std::vector<uint8_t>& selectResponse) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+ }
+
+ void testNonSelectableAid(
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+ std::vector<uint8_t> aid) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(aid, 0x00, seListener, &channel);
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ LOG(ERROR) << res.getMessage();
+ ASSERT_FALSE(res.isOk()) << "expected to fail to open channel for this test";
+ }
+
+ /**
+ * Verifies TLV data
+ *
+ * @return true if the data is tlv formatted, false otherwise
+ */
+ bool verifyBerTlvData(std::vector<uint8_t> tlv) {
+ if (tlv.size() == 0) {
+ LOG(ERROR) << "Invalid tlv, null";
+ return false;
+ }
+ int i = 0;
+ if ((tlv[i++] & 0x1F) == 0x1F) {
+ // extra byte for TAG field
+ i++;
+ }
+
+ int len = tlv[i++] & 0xFF;
+ if (len > 127) {
+ // more than 1 byte for length
+ int bytesLength = len - 128;
+ len = 0;
+ for (int j = bytesLength; j > 0; j--) {
+ len += (len << 8) + (tlv[i++] & 0xFF);
+ }
+ }
+ // Additional 2 bytes for the SW
+ return (tlv.size() == (i + len + 2));
+ }
+
+ void internalTransmitApdu(
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader,
+ std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+ std::vector<uint8_t> selectResponse = {};
+
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+
+ res = channel->transmit(apdu, &transmitResponse);
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+ LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode()
+ << " Message: " << res.getMessage();
+ ASSERT_TRUE(res.isOk()) << "failed to transmit";
+ }
+
+ bool supportOMAPIReaders() {
+ return (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str()));
+ }
+
+ void SetUp() override {
+ LOG(INFO) << "get OMAPI service with name:" << GetParam();
+ ::ndk::SpAIBinder ks2Binder(AServiceManager_getService(GetParam().c_str()));
+ mOmapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder);
+ ASSERT_TRUE(mOmapiSeService);
+
+ std::vector<std::string> readers = {};
+
+ if (omapiSecureService() != NULL) {
+ auto status = omapiSecureService()->getReaders(&readers);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ for (auto readerName : readers) {
+ // Filter eSE readers only
+ if (readerName.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+ std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader;
+ status = omapiSecureService()->getReader(readerName, &reader);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ mVSReaders[readerName] = reader;
+ }
+ }
+ }
+ }
+
+ void TearDown() override {
+ if (mOmapiSeService != nullptr) {
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ reader->closeSessions();
+ }
+ }
+ }
+ }
+
+ bool isDebuggableBuild() {
+ char value[PROPERTY_VALUE_MAX] = {0};
+ property_get("ro.system.build.type", value, "");
+ if (strcmp(value, "userdebug") == 0) {
+ return true;
+ }
+ if (strcmp(value, "eng") == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementService> omapiSecureService() {
+ return mOmapiSeService;
+ }
+
+ static inline std::string const ESE_READER_PREFIX = "eSE";
+ static inline std::string const FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+ std::vector<uint8_t> SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31};
+ std::vector<uint8_t> LONG_SELECT_RESPONSE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41,
+ 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64,
+ 0x43, 0x54, 0x53, 0x32};
+ std::vector<uint8_t> NON_SELECTABLE_AID = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
+ 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0xFF};
+
+ std::vector<std::vector<uint8_t>> ILLEGAL_COMMANDS_TRANSMIT = {
+ {0x00, 0x70, 0x00, 0x00},
+ {0x00, 0x70, 0x80, 0x00},
+ {0x00, 0xA4, 0x04, 0x04, 0x10, 0x4A, 0x53, 0x52, 0x31, 0x37, 0x37,
+ 0x54, 0x65, 0x73, 0x74, 0x65, 0x72, 0x20, 0x31, 0x2E, 0x30}};
+
+ /* OMAPI APDU Test case 1 and 3 */
+ std::vector<std::vector<uint8_t>> NO_DATA_APDU = {{0x00, 0x06, 0x00, 0x00},
+ {0x80, 0x06, 0x00, 0x00},
+ {0xA0, 0x06, 0x00, 0x00},
+ {0x94, 0x06, 0x00, 0x00},
+ {0x00, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0x80, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0xA0, 0x0A, 0x00, 0x00, 0x01, 0xAA},
+ {0x94, 0x0A, 0x00, 0x00, 0x01, 0xAA}};
+
+ /* OMAPI APDU Test case 2 and 4 */
+ std::vector<std::vector<uint8_t>> DATA_APDU = {{0x00, 0x08, 0x00, 0x00, 0x00},
+ {0x80, 0x08, 0x00, 0x00, 0x00},
+ {0xA0, 0x08, 0x00, 0x00, 0x00},
+ {0x94, 0x08, 0x00, 0x00, 0x00},
+ {0x00, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0x80, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0xA0, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00},
+ {0x94, 0x0C, 0x00, 0x00, 0x01, 0xAA, 0x00}};
+
+ /* Case 2 APDU command expects the P2 received in the SELECT command as 1-byte outgoing data */
+ std::vector<uint8_t> CHECK_SELECT_P2_APDU = {0x00, 0xF4, 0x00, 0x00, 0x00};
+
+ /* OMAPI APDU Test case 1 and 3 */
+ std::vector<std::vector<uint8_t>> SW_62xx_NO_DATA_APDU = {{0x00, 0xF3, 0x00, 0x06},
+ {0x00, 0xF3, 0x00, 0x0A, 0x01, 0xAA}};
+
+ /* OMAPI APDU Test case 2 and 4 */
+ std::vector<uint8_t> SW_62xx_DATA_APDU = {0x00, 0xF3, 0x00, 0x08, 0x00};
+ std::vector<uint8_t> SW_62xx_VALIDATE_DATA_APDU = {0x00, 0xF3, 0x00, 0x0C, 0x01, 0xAA, 0x00};
+ std::vector<std::vector<uint8_t>> SW_62xx = {
+ {0x62, 0x00}, {0x62, 0x81}, {0x62, 0x82}, {0x62, 0x83}, {0x62, 0x85}, {0x62, 0xF1},
+ {0x62, 0xF2}, {0x63, 0xF1}, {0x63, 0xF2}, {0x63, 0xC2}, {0x62, 0x02}, {0x62, 0x80},
+ {0x62, 0x84}, {0x62, 0x86}, {0x63, 0x00}, {0x63, 0x81}};
+
+ std::vector<std::vector<uint8_t>> SEGMENTED_RESP_APDU = {
+ // Get response Case2 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x00, 0xC2, 0x08, 0x00, 0x00},
+ // Get response Case4 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x00, 0xC4, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00},
+ // Get response Case2 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x00, 0xC6, 0x08, 0x00, 0x00},
+ // Get response Case4 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x00, 0xC8, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00},
+ // Test device buffer capacity 7FFF data
+ {0x00, 0xC2, 0x7F, 0xFF, 0x00},
+ // Get response 6CFF+61XX with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x00, 0xCF, 0x08, 0x00, 0x00},
+ // Get response with another CLA with answer length (P1P2) of 0x0800, 2048 bytes
+ {0x94, 0xC2, 0x08, 0x00, 0x00}};
+ long SERVICE_CONNECTION_TIME_OUT = 3000;
+
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementService> mOmapiSeService;
+
+ std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>>
+ mVSReaders = {};
+};
+
+/** Tests getReaders API */
+TEST_P(OMAPISEServiceHalTest, TestGetReaders) {
+ std::vector<std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> eseReaders =
+ {};
+
+ for (const auto& [name, reader] : mVSReaders) {
+ bool status = false;
+ LOG(INFO) << "Name of the reader: " << name;
+
+ if (reader) {
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ }
+ ASSERT_TRUE(status);
+
+ if (name.find(ESE_READER_PREFIX) == std::string::npos) {
+ LOG(ERROR) << "Incorrect Reader name";
+ FAIL();
+ }
+
+ if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) {
+ eseReaders.push_back(reader);
+ } else {
+ LOG(INFO) << "Reader not supported: " << name;
+ FAIL();
+ }
+ }
+
+ if (deviceSupportsFeature(FEATURE_SE_OMAPI_ESE.c_str())) {
+ ASSERT_GE(eseReaders.size(), 1);
+ } else {
+ ASSERT_TRUE(eseReaders.size() == 0);
+ }
+}
+
+/** Tests OpenBasicChannel API when aid is null */
+TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNullAid) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ std::vector<uint8_t> aid = {};
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ bool result = false;
+
+ auto status = reader->openSession(&session);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+ if (!session) {
+ LOG(ERROR) << "Could not open session";
+ FAIL();
+ }
+
+ status = session->openBasicChannel(aid, 0x00, seListener, &channel);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ if (channel != nullptr) {
+ status = channel->isBasicChannel(&result);
+ ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened";
+ }
+ }
+ }
+}
+
+/** Tests OpenBasicChannel API when aid is provided */
+TEST_P(OMAPISEServiceHalTest, TestOpenBasicChannelNonNullAid) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ bool result = false;
+
+ auto status = reader->openSession(&session);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+ if (!session) {
+ LOG(ERROR) << "Could not open session";
+ FAIL();
+ }
+
+ status = session->openBasicChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+ ASSERT_TRUE(status.isOk()) << status.getMessage();
+
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+
+ if (channel != nullptr) {
+ status = channel->isBasicChannel(&result);
+ ASSERT_TRUE(status.isOk()) << "Basic Channel cannot be opened";
+ }
+ }
+ }
+}
+
+/** Tests Select API */
+TEST_P(OMAPISEServiceHalTest, TestSelectableAid) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::vector<uint8_t> selectResponse = {};
+ testSelectableAid(reader, SELECTABLE_AID, selectResponse);
+ }
+ }
+}
+
+/** Tests Select API */
+TEST_P(OMAPISEServiceHalTest, TestLongSelectResponse) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::vector<uint8_t> selectResponse = {};
+ testSelectableAid(reader, LONG_SELECT_RESPONSE_AID, selectResponse);
+ ASSERT_TRUE(verifyBerTlvData(selectResponse)) << "Select Response is not complete";
+ }
+ }
+}
+
+/** Test to fail open channel with wrong aid */
+TEST_P(OMAPISEServiceHalTest, TestWrongAid) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ testNonSelectableAid(reader, NON_SELECTABLE_AID);
+ }
+ }
+}
+
+/** Tests with invalid cmds in Transmit */
+TEST_P(OMAPISEServiceHalTest, TestSecurityExceptionInTransmit) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session;
+ std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel;
+ auto seListener = ndk::SharedRefBase::make<::OMAPISEServiceHalTest::SEListener>();
+ std::vector<uint8_t> selectResponse = {};
+
+ ASSERT_NE(reader, nullptr) << "reader is null";
+
+ bool status = false;
+ auto res = reader->isSecureElementPresent(&status);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_TRUE(status);
+
+ res = reader->openSession(&session);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(session, nullptr) << "Could not open session";
+
+ res = session->openLogicalChannel(SELECTABLE_AID, 0x00, seListener, &channel);
+ ASSERT_TRUE(res.isOk()) << res.getMessage();
+ ASSERT_NE(channel, nullptr) << "Could not open channel";
+
+ res = channel->getSelectResponse(&selectResponse);
+ ASSERT_TRUE(res.isOk()) << "failed to get Select Response";
+ ASSERT_GE(selectResponse.size(), 2);
+
+ ASSERT_EQ((selectResponse[selectResponse.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((selectResponse[selectResponse.size() - 2] & 0xFF), (0x90));
+
+ for (auto cmd : ILLEGAL_COMMANDS_TRANSMIT) {
+ std::vector<uint8_t> response = {};
+ res = channel->transmit(cmd, &response);
+ ASSERT_EQ(res.getExceptionCode(), EX_SECURITY);
+ ASSERT_FALSE(res.isOk()) << "expected failed status for this test";
+ }
+ if (channel != nullptr) channel->close();
+ if (session != nullptr) session->close();
+ }
+ }
+}
+
+/**
+ * Tests Transmit API for all readers.
+ *
+ * Checks the return status and verifies the size of the
+ * response.
+ */
+TEST_P(OMAPISEServiceHalTest, TestTransmitApdu) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ for (auto apdu : NO_DATA_APDU) {
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ ASSERT_GE(response.size(), 2);
+ ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+ }
+
+ for (auto apdu : DATA_APDU) {
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ /* 256 byte data and 2 bytes of status word */
+ ASSERT_GE(response.size(), 258);
+ ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+ }
+ }
+ }
+}
+
+/**
+ * Tests if underlying implementations returns the correct Status Word
+ *
+ * TO verify that :
+ * - the device does not modify the APDU sent to the Secure Element
+ * - the warning code is properly received by the application layer as SW answer
+ * - the verify that the application layer can fetch the additionnal data (when present)
+ */
+TEST_P(OMAPISEServiceHalTest, testStatusWordTransmit) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ for (auto apdu : SW_62xx_NO_DATA_APDU) {
+ for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+ apdu[2] = i + 1;
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ std::vector<uint8_t> SW = SW_62xx[i];
+ ASSERT_GE(response.size(), 2);
+ ASSERT_EQ(response[response.size() - 1], SW[1]);
+ ASSERT_EQ(response[response.size() - 2], SW[0]);
+ }
+ }
+
+ for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+ std::vector<uint8_t> apdu = SW_62xx_DATA_APDU;
+ apdu[2] = i + 1;
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ std::vector<uint8_t> SW = SW_62xx[i];
+ ASSERT_GE(response.size(), 3);
+ ASSERT_EQ(response[response.size() - 1], SW[1]);
+ ASSERT_EQ(response[response.size() - 2], SW[0]);
+ }
+
+ for (uint8_t i = 0x00; i < SW_62xx.size(); i++) {
+ std::vector<uint8_t> apdu = SW_62xx_VALIDATE_DATA_APDU;
+ apdu[2] = i + 1;
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ ASSERT_GE(response.size(), apdu.size() + 2);
+ std::vector<uint8_t> responseSubstring((response.begin() + 0),
+ (response.begin() + apdu.size()));
+ // We should not care about which channel number is actually assigned.
+ responseSubstring[0] = apdu[0];
+ ASSERT_TRUE((responseSubstring == apdu));
+ std::vector<uint8_t> SW = SW_62xx[i];
+ ASSERT_EQ(response[response.size() - 1], SW[1]);
+ ASSERT_EQ(response[response.size() - 2], SW[0]);
+ }
+ }
+ }
+}
+
+/** Test if the responses are segmented by the underlying implementation */
+TEST_P(OMAPISEServiceHalTest, TestSegmentedResponseTransmit) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ for (auto apdu : SEGMENTED_RESP_APDU) {
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, apdu, response);
+ int expectedLength = (0x00 << 24) | (0x00 << 16) | (apdu[2] << 8) | apdu[3];
+ ASSERT_EQ(response.size(), (expectedLength + 2));
+ ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+ ASSERT_EQ((response[response.size() - 3] & 0xFF), (0xFF));
+ }
+ }
+ }
+}
+
+/**
+ * Tests the P2 value of the select command.
+ *
+ * Verifies that the default P2 value (0x00) is not modified by the underlying implementation.
+ */
+TEST_P(OMAPISEServiceHalTest, TestP2Value) {
+ ASSERT_TRUE(supportOMAPIReaders() == true);
+ if (mVSReaders.size() > 0) {
+ for (const auto& [name, reader] : mVSReaders) {
+ std::vector<uint8_t> response = {};
+ internalTransmitApdu(reader, CHECK_SELECT_P2_APDU, response);
+ ASSERT_GE(response.size(), 3);
+ ASSERT_EQ((response[response.size() - 1] & 0xFF), (0x00));
+ ASSERT_EQ((response[response.size() - 2] & 0xFF), (0x90));
+ ASSERT_EQ((response[response.size() - 3] & 0xFF), (0x00));
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, OMAPISEServiceHalTest,
+ testing::ValuesIn(::android::getAidlHalInstanceNames(
+ aidl::android::se::omapi::ISecureElementService::descriptor)),
+ android::hardware::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OMAPISEServiceHalTest);
+
+} // namespace
diff --git a/omapi/java/Android.bp b/omapi/java/Android.bp
new file mode 100644
index 0000000..8d38da0
--- /dev/null
+++ b/omapi/java/Android.bp
@@ -0,0 +1,17 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+ name: "framework-omapi-sources",
+ srcs: [
+ "**/*.java",
+ "**/*.aidl",
+ ],
+ visibility: ["//frameworks/base"],
+}
diff --git a/core/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
similarity index 100%
rename from core/java/android/se/OWNERS
rename to omapi/java/android/se/OWNERS
diff --git a/core/java/android/se/omapi/Channel.java b/omapi/java/android/se/omapi/Channel.java
similarity index 100%
rename from core/java/android/se/omapi/Channel.java
rename to omapi/java/android/se/omapi/Channel.java
diff --git a/core/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
similarity index 100%
rename from core/java/android/se/omapi/OWNERS
rename to omapi/java/android/se/omapi/OWNERS
diff --git a/core/java/android/se/omapi/Reader.java b/omapi/java/android/se/omapi/Reader.java
similarity index 98%
rename from core/java/android/se/omapi/Reader.java
rename to omapi/java/android/se/omapi/Reader.java
index 90c934d..3c2135d9 100644
--- a/core/java/android/se/omapi/Reader.java
+++ b/omapi/java/android/se/omapi/Reader.java
@@ -170,7 +170,9 @@
try {
closeSessions();
return mReader.reset();
- } catch (RemoteException ignore) {return false;}
+ } catch (RemoteException ignore) {
+ return false;
+ }
}
}
}
diff --git a/core/java/android/se/omapi/SEService.java b/omapi/java/android/se/omapi/SEService.java
similarity index 96%
rename from core/java/android/se/omapi/SEService.java
rename to omapi/java/android/se/omapi/SEService.java
index 333af91..f42ca36 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/omapi/java/android/se/omapi/SEService.java
@@ -230,20 +230,20 @@
* is not exist.
* @return A Reader object for this uicc slot.
*/
- public @NonNull Reader getUiccReader(int slotNumber) {
- if (slotNumber < 1) {
- throw new IllegalArgumentException("slotNumber should be larger than 0");
- }
- loadReaders();
+ public @NonNull Reader getUiccReader(int slotNumber) {
+ if (slotNumber < 1) {
+ throw new IllegalArgumentException("slotNumber should be larger than 0");
+ }
+ loadReaders();
- String readerName = UICC_TERMINAL + slotNumber;
- Reader reader = mReaders.get(readerName);
+ String readerName = UICC_TERMINAL + slotNumber;
+ Reader reader = mReaders.get(readerName);
- if (reader == null) {
+ if (reader == null) {
throw new IllegalArgumentException("Reader:" + readerName + " doesn't exist");
- }
+ }
- return reader;
+ return reader;
}
/**
diff --git a/core/java/android/se/omapi/Session.java b/omapi/java/android/se/omapi/Session.java
similarity index 100%
rename from core/java/android/se/omapi/Session.java
rename to omapi/java/android/se/omapi/Session.java
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/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/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index a210e90..8b17be1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -19,7 +19,6 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
-import android.app.Dialog;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -85,6 +84,7 @@
@VisibleForTesting
protected Context mContext;
+ private final int mThemeResId;
@VisibleForTesting
protected TextView mZenAlarmWarning;
@VisibleForTesting
@@ -97,10 +97,15 @@
protected LayoutInflater mLayoutInflater;
public EnableZenModeDialog(Context context) {
- mContext = context;
+ this(context, 0);
}
- public Dialog createDialog() {
+ public EnableZenModeDialog(Context context, int themeResId) {
+ mContext = context;
+ mThemeResId = themeResId;
+ }
+
+ public AlertDialog createDialog() {
mNotificationManager = (NotificationManager) mContext.
getSystemService(Context.NOTIFICATION_SERVICE);
mForeverId = Condition.newId(mContext).appendPath("forever").build();
@@ -108,7 +113,7 @@
mUserId = mContext.getUserId();
mAttached = false;
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
.setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 0fe4efe..a944bf5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -80,6 +80,7 @@
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Settings.System.RING_VIBRATION_INTENSITY,
Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
Settings.System.DISPLAY_COLOR_MODE,
Settings.System.ALARM_ALERT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 462c3a5..63acffb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -120,6 +120,7 @@
VALIDATORS.put(System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
+ VALIDATORS.put(System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_VALIDATOR);
VALIDATORS.put(System.RINGTONE, URI_VALIDATOR);
VALIDATORS.put(System.NOTIFICATION_SOUND, URI_VALIDATOR);
VALIDATORS.put(System.ALARM_ALERT, URI_VALIDATOR);
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 3ccf5e4..5fec4cc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -53,8 +53,12 @@
private val ghostedView: View,
/** The [InteractionJankMonitor.CujType] associated to this animation. */
- private val cujType: Int? = null
+ private val cujType: Int? = null,
+ private var interactionJankMonitor: InteractionJankMonitor? = null
) : ActivityLaunchAnimator.Controller {
+
+ constructor(view: View, type: Int) : this(view, type, null)
+
/** The container to which we will add the ghost view and expanding background. */
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
@@ -170,7 +174,7 @@
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
- cujType?.let { InteractionJankMonitor.getInstance().begin(ghostedView, it) }
+ cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
}
override fun onLaunchAnimationProgress(
@@ -251,7 +255,7 @@
return
}
- cujType?.let { InteractionJankMonitor.getInstance().end(it) }
+ cujType?.let { interactionJankMonitor?.end(it) }
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
deleted file mode 100644
index 9f44aca..0000000
--- a/packages/SystemUI/res/color/prv_text_color_on_accent.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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:androidprv="http://schemas.android.com/apk/prv/res/android"
- xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="?androidprv:attr/textColorOnAccent" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
index 1a128df..14cb1de9 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -16,8 +16,8 @@
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:insetTop="@dimen/qs_dialog_button_vertical_inset"
- android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+ android:insetTop="@dimen/dialog_button_vertical_inset"
+ android:insetBottom="@dimen/dialog_button_vertical_inset">
<ripple android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
@@ -29,10 +29,10 @@
<shape android:shape="rectangle">
<corners android:radius="?android:attr/buttonCornerRadius"/>
<solid android:color="?androidprv:attr/colorAccentPrimary"/>
- <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
- android:top="@dimen/qs_dialog_button_vertical_padding"
- android:right="@dimen/qs_dialog_button_horizontal_padding"
- android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+ <padding android:left="@dimen/dialog_button_horizontal_padding"
+ android:top="@dimen/dialog_button_vertical_padding"
+ android:right="@dimen/dialog_button_horizontal_padding"
+ android:bottom="@dimen/dialog_button_vertical_padding"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 467c20f..665b456 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -16,8 +16,8 @@
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:insetTop="@dimen/qs_dialog_button_vertical_inset"
- android:insetBottom="@dimen/qs_dialog_button_vertical_inset">
+ android:insetTop="@dimen/dialog_button_vertical_inset"
+ android:insetBottom="@dimen/dialog_button_vertical_inset">
<ripple android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
@@ -32,10 +32,10 @@
<stroke android:color="?androidprv:attr/colorAccentPrimary"
android:width="1dp"
/>
- <padding android:left="@dimen/qs_dialog_button_horizontal_padding"
- android:top="@dimen/qs_dialog_button_vertical_padding"
- android:right="@dimen/qs_dialog_button_horizontal_padding"
- android:bottom="@dimen/qs_dialog_button_vertical_padding"/>
+ <padding android:left="@dimen/dialog_button_horizontal_padding"
+ android:top="@dimen/dialog_button_vertical_padding"
+ android:right="@dimen/dialog_button_horizontal_padding"
+ android:bottom="@dimen/dialog_button_vertical_padding"/>
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
new file mode 100644
index 0000000..a3e289a
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@*android:id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ android:paddingTop="@dimen/dialog_button_bar_top_padding"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ android:paddingBottom="@dimen/dialog_bottom_padding"
+ style="?android:attr/buttonBarStyle">
+ <com.android.internal.widget.ButtonBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale"
+ android:orientation="horizontal"
+ android:gravity="bottom">
+
+ <Button
+ android:id="@android:id/button3"
+ style="?android:attr/buttonBarNeutralButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Space
+ android:id="@*android:id/spacer"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:visibility="invisible" />
+
+ <Button
+ android:id="@android:id/button2"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button
+ android:id="@android:id/button1"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_marginStart="8dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </com.android.internal.widget.ButtonBarLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
new file mode 100644
index 0000000..f280cbd
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.internal.widget.AlertDialogLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@*android:id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/dialog_top_padding"
+ >
+
+ <include layout="@layout/alert_dialog_title_systemui" />
+
+ <FrameLayout
+ android:id="@*android:id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
+
+ <ScrollView
+ android:id="@*android:id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Space
+ android:id="@*android:id/textSpacerNoTitle"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.Dialog.Body.Message" />
+
+ <Space
+ android:id="@*android:id/textSpacerNoButtons"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="6dp" />
+ </LinearLayout>
+ </ScrollView>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@*android:id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="48dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ >
+
+ <FrameLayout
+ android:id="@*android:id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <include
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_button_bar_systemui" />
+
+</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
new file mode 100644
index 0000000..480ba00
--- /dev/null
+++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@*android:id/topPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+>
+
+ <!-- If the client uses a customTitle, it will be added here. -->
+
+ <LinearLayout
+ android:id="@*android:id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal|top">
+
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginBottom="16dp"
+ android:scaleType="fitCenter"
+ android:src="@null"
+ android:tint="?androidprv:attr/colorAccentPrimaryVariant"
+ />
+
+ <com.android.internal.widget.DialogTitle
+ android:id="@*android:id/alertTitle"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ style="@style/TextAppearance.Dialog.Title" />
+ </LinearLayout>
+
+ <Space
+ android:id="@*android:id/titleDividerNoCustom"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="0dp" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 86e2661..98518c2 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -156,6 +156,14 @@
style="@style/InternetDialog.NetworkSummary"/>
</LinearLayout>
+ <View
+ android:id="@+id/mobile_toggle_divider"
+ android:layout_width="1dp"
+ android:layout_height="28dp"
+ android:layout_marginEnd="16dp"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/textColorSecondary"/>
+
<FrameLayout
android:layout_width="@dimen/settingslib_switch_track_width"
android:layout_height="48dp"
@@ -367,8 +375,9 @@
android:id="@+id/done_layout"
android:layout_width="67dp"
android:layout_height="48dp"
+ android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
- android:layout_marginBottom="40dp"
+ android:layout_marginBottom="34dp"
android:layout_gravity="end|center_vertical"
android:clickable="true"
android:focusable="true">
diff --git a/packages/SystemUI/res/layout/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/qs_user_detail.xml b/packages/SystemUI/res/layout/qs_user_detail.xml
index 91d3a53..1aec296 100644
--- a/packages/SystemUI/res/layout/qs_user_detail.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail.xml
@@ -22,6 +22,6 @@
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- sysui:verticalSpacing="4dp"
+ sysui:verticalSpacing="20dp"
sysui:horizontalSpacing="4dp"
style="@style/UserDetailView" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index cc6c5d3..91b11fc 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -24,8 +24,6 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="top|center_horizontal"
- android:paddingTop="16dp"
- android:minHeight="112dp"
android:clipChildren="false"
android:clipToPadding="false"
android:focusable="true"
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
index 9495ee6..355df2c 100644
--- a/packages/SystemUI/res/layout/qs_user_dialog_content.xml
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -15,75 +15,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:padding="24dp"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
->
- <TextView
- android:id="@+id/title"
- android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:textAlignment="center"
- android:text="@string/qs_user_switch_dialog_title"
- android:textAppearance="@style/TextAppearance.QSDialog.Title"
- android:layout_marginBottom="32dp"
- sysui:layout_constraintTop_toTopOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/grid"
- />
-
+ >
<com.android.systemui.qs.PseudoGridView
- android:id="@+id/grid"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="28dp"
- sysui:verticalSpacing="4dp"
- sysui:horizontalSpacing="4dp"
- sysui:fixedChildWidth="80dp"
- sysui:layout_constraintTop_toBottomOf="@id/title"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toEndOf="parent"
- sysui:layout_constraintBottom_toTopOf="@id/barrier"
- />
-
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/barrier"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- sysui:barrierDirection="top"
- sysui:constraint_referenced_ids="settings,done"
+ android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ sysui:verticalSpacing="20dp"
+ sysui:horizontalSpacing="4dp"
+ sysui:fixedChildWidth="80dp"
/>
-
- <Button
- android:id="@+id/settings"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_more_user_settings"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toStartOf="parent"
- sysui:layout_constraintEnd_toStartOf="@id/done"
- sysui:layout_constraintHorizontal_chainStyle="spread_inside"
- style="@style/Widget.QSDialog.Button.BorderButton"
- />
-
- <Button
- android:id="@+id/done"
- android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:text="@string/quick_settings_done"
- sysui:layout_constraintTop_toBottomOf="@id/barrier"
- sysui:layout_constraintBottom_toBottomOf="parent"
- sysui:layout_constraintStart_toEndOf="@id/settings"
- sysui:layout_constraintEnd_toEndOf="parent"
- style="@style/Widget.QSDialog.Button"
- />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 07e28b6..cb963e6 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -16,9 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog">
- <item name="android:buttonCornerRadius">28dp</item>
- </style>
+ <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Dialog"/>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert" />
@@ -53,13 +51,17 @@
<style name="TextAppearance.InternetDialog.Active">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textSize">16sp</item>
- <item name="android:textColor">@color/connected_network_primary_color</item>
+ <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textDirection">locale</item>
</style>
<style name="TextAppearance.InternetDialog.Secondary.Active">
<item name="android:textSize">14sp</item>
- <item name="android:textColor">@color/connected_network_secondary_color</item>
+ <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+ </style>
+
+ <style name="InternetDialog.Divider.Active">
+ <item name="android:background">?android:attr/textColorSecondaryInverse</item>
</style>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 56464e4..1bf2cd8 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -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 059aad7..aee1f43 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1276,10 +1276,19 @@
<dimen name="qs_tile_service_request_tile_width">192dp</dimen>
<dimen name="qs_tile_service_request_content_space">24dp</dimen>
- <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen>
- <dimen name="qs_dialog_button_vertical_padding">8dp</dimen>
+ <!-- Dimensions for unified SystemUI dialogs styling. Used by Theme.SystemUI.Dialog and
+ alert_dialog_systemui.xml
+ -->
+ <dimen name="dialog_button_horizontal_padding">16dp</dimen>
+ <dimen name="dialog_button_vertical_padding">8dp</dimen>
<!-- The button will be 48dp tall, but the background needs to be 36dp tall -->
- <dimen name="qs_dialog_button_vertical_inset">6dp</dimen>
+ <dimen name="dialog_button_vertical_inset">6dp</dimen>
+ <dimen name="dialog_top_padding">24dp</dimen>
+ <dimen name="dialog_bottom_padding">18dp</dimen>
+ <dimen name="dialog_side_padding">24dp</dimen>
+ <dimen name="dialog_button_bar_top_padding">32dp</dimen>
+
+ <!-- ************************************************************************* -->
<dimen name="keyguard_unfold_translation_x">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d972b7fc..b6cdd1b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -14,8 +14,8 @@
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- NOTE: Adding the androidprv: namespace to this file will break the studio build. -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<!-- HybridNotification themes and styles -->
@@ -351,11 +351,19 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
+ <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
+
+ <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog">
<item name="android:buttonCornerRadius">28dp</item>
- <item name="android:buttonBarPositiveButtonStyle">@style/Widget.QSDialog.Button</item>
- <item name="android:buttonBarNegativeButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
- <item name="android:buttonBarNeutralButtonStyle">@style/Widget.QSDialog.Button.BorderButton</item>
+ <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item>
+ <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
+ <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item>
+ <item name="android:colorBackground">?androidprv:attr/colorSurface</item>
+ <item name="android:alertDialogStyle">@style/AlertDialogStyle</item>
+ </style>
+
+ <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault">
+ <item name="android:layout">@layout/alert_dialog_systemui</item>
</style>
<style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
@@ -863,24 +871,37 @@
<item name="actionDividerHeight">32dp</item>
</style>
- <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog">
+ <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:lineHeight">32sp</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textAlignment">center</item>
</style>
- <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog">
+ <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:lineHeight">20sp</item>
+ </style>
+
+ <style name="TextAppearance.Dialog.Body.Message">
+ <item name="android:gravity">center</item>
+ <item name="android:textAlignment">center</item>
+ </style>
+
+ <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
- <item name="android:textColor">@color/prv_text_color_on_accent</item>
+ <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20sp</item>
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
<item name="android:stateListAnimator">@null</item>
- <item name="android:layout_marginHorizontal">4dp</item>
</style>
- <style name="Widget.QSDialog.Button.BorderButton">
+ <style name="Widget.Dialog.Button.BorderButton">
<item name="android:background">@drawable/qs_dialog_btn_outline</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
@@ -957,4 +978,10 @@
<style name="TextAppearance.InternetDialog.Secondary.Active"/>
+ <style name="InternetDialog.Divider">
+ <item name="android:background">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="InternetDialog.Divider.Active"/>
+
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 8bd0f91..0149751 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -95,4 +95,9 @@
* Sent when screen turned on and ready to use (blocker scrim is hidden)
*/
void onScreenTurnedOn() = 21;
+
+ /**
+ * Sent when the desired dark intensity of the nav buttons has changed
+ */
+ void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 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/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e24f07c..b56d189 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -801,13 +801,16 @@
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
+ boolean lockedOutStateChanged = false;
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+ lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
requireStrongAuthIfAllLockedOut();
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
+ lockedOutStateChanged |= !mFingerprintLockedOut;
mFingerprintLockedOut = true;
if (isUdfpsEnrolled()) {
updateFingerprintListeningState();
@@ -820,9 +823,14 @@
cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
}
}
+
+ if (lockedOutStateChanged) {
+ notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ }
}
private void handleFingerprintLockoutReset() {
+ boolean changed = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
mFingerprintLockedOut = false;
mFingerprintLockedOutPermanent = false;
@@ -837,6 +845,10 @@
} else {
updateFingerprintListeningState();
}
+
+ if (changed) {
+ notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ }
}
private void setFingerprintRunningState(int fingerprintRunningState) {
@@ -999,7 +1011,9 @@
}
}
+ boolean lockedOutStateChanged = false;
if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
+ lockedOutStateChanged = !mFaceLockedOutPermanent;
mFaceLockedOutPermanent = true;
requireStrongAuthIfAllLockedOut();
}
@@ -1011,11 +1025,21 @@
BiometricSourceType.FACE);
}
}
+
+ if (lockedOutStateChanged) {
+ notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ }
}
private void handleFaceLockoutReset() {
+ boolean changed = mFaceLockedOutPermanent;
mFaceLockedOutPermanent = false;
+
updateFaceListeningState();
+
+ if (changed) {
+ notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ }
}
private void setFaceRunningState(int faceRunningState) {
@@ -1237,6 +1261,16 @@
}
}
+ private void notifyLockedOutStateChanged(BiometricSourceType type) {
+ Assert.isMainThread();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onLockedOutStateChanged(type);
+ }
+ }
+ }
+
public boolean isScreenOn() {
return mScreenOn;
}
@@ -2454,6 +2488,10 @@
}
}
+ public boolean isFingerprintLockedOut() {
+ return mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ }
+
/**
* If biometrics hardware is available, not disabled, and user has enrolled templates.
* This does NOT check if the device is encrypted or in lockdown.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 12431984..8170a81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -292,6 +292,11 @@
public void onStrongAuthStateChanged(int userId) { }
/**
+ * When the current user's locked out state changed.
+ */
+ public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) { }
+
+ /**
* Called when the dream's window state is changed.
* @param dreaming true if the dream's window has been created and is visible
*/
diff --git a/packages/SystemUI/src/com/android/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/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 223eb78..8f4d6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -255,7 +255,6 @@
private void maybeShowInputBouncer() {
if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
mKeyguardViewManager.showBouncer(true);
- mKeyguardViewManager.resetAlternateAuth(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
new file mode 100644
index 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/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/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 2f7c8ba..fa3ad4e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2371,7 +2371,8 @@
message.animate()
.alpha(0f)
.setDuration(TOAST_FADE_TIME)
- .setStartDelay(visibleTime);
+ .setStartDelay(visibleTime)
+ .setListener(null);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e97e762..fbc9ba6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -793,7 +793,8 @@
return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
} else if (trust && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
- } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
+ } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
+ || mUpdateMonitor.isFingerprintLockedOut())) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -820,6 +821,7 @@
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
/**
@@ -845,7 +847,8 @@
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- Lazy<NotificationShadeDepthController> notificationShadeDepthController) {
+ Lazy<NotificationShadeDepthController> notificationShadeDepthController,
+ InteractionJankMonitor interactionJankMonitor) {
super(context);
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
@@ -882,6 +885,7 @@
mKeyguardStateController = keyguardStateController;
mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+ mInteractionJankMonitor = interactionJankMonitor;
}
public void userActivity() {
@@ -2245,8 +2249,7 @@
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
0 /* fadeoutDuration */);
- InteractionJankMonitor.getInstance()
- .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
@Override
@@ -2255,7 +2258,7 @@
}
};
try {
- InteractionJankMonitor.getInstance().begin(
+ mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RunRemoteAnimation"));
runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
wallpapers, nonApps, callback);
@@ -2271,14 +2274,14 @@
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
mSurfaceBehindRemoteAnimationRunning = true;
- InteractionJankMonitor.getInstance().begin(
+ mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("DismissPanel"));
// Pass the surface and metadata to the unlock animation controller.
mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
- InteractionJankMonitor.getInstance().begin(
+ mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RemoteAnimationDisabled"));
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
@@ -2288,7 +2291,7 @@
// supported, so it's always null.
mContext.getMainExecutor().execute(() -> {
if (finishedCallback == null) {
- InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
return;
}
@@ -2316,8 +2319,7 @@
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
- InteractionJankMonitor.getInstance()
- .end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
}
@@ -2328,8 +2330,7 @@
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
- InteractionJankMonitor.getInstance()
- .cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index cae9fee..8d23e9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -22,6 +22,7 @@
import android.content.pm.PackageManager;
import android.os.PowerManager;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -97,7 +98,8 @@
KeyguardStateController keyguardStateController,
Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- Lazy<NotificationShadeDepthController> notificationShadeDepthController) {
+ Lazy<NotificationShadeDepthController> notificationShadeDepthController,
+ InteractionJankMonitor interactionJankMonitor) {
return new KeyguardViewMediator(
context,
falsingCollector,
@@ -120,7 +122,8 @@
keyguardStateController,
keyguardUnlockAnimationController,
unlockedScreenOffAnimationController,
- notificationShadeDepthController
+ notificationShadeDepthController,
+ interactionJankMonitor
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 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/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 868193b..54e40f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -289,6 +289,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 +315,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);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 52103d3..23062d8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -72,7 +72,7 @@
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,8 +87,13 @@
}
};
+ /**
+ * @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,
@@ -96,6 +101,7 @@
NavigationModeController navigationModeController,
UserTracker userTracker,
DumpManager dumpManager) {
+ mContext = context;
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
mUserTracker = userTracker;
@@ -109,8 +115,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),
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index e0da9a0..75ef8de 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -563,7 +563,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(
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3398778..0429c02 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.FileDescriptor;
@@ -104,7 +105,8 @@
TaskbarDelegate taskbarDelegate,
NavigationBar.Factory navigationBarFactory,
DumpManager dumpManager,
- AutoHideController autoHideController) {
+ AutoHideController autoHideController,
+ LightBarController lightBarController) {
mContext = context;
mHandler = mainHandler;
mNavigationBarFactory = navigationBarFactory;
@@ -116,7 +118,7 @@
mTaskbarDelegate = taskbarDelegate;
mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
navBarHelper, navigationModeController, sysUiFlagsContainer,
- dumpManager, autoHideController);
+ dumpManager, autoHideController, lightBarController);
mIsTablet = isTablet(mContext);
dumpManager.registerDumpable(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 680cc5c..364a8ae 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;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 428d9d6..fb9b8eb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -64,6 +64,9 @@
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -85,6 +88,8 @@
private NavigationModeController mNavigationModeController;
private SysUiState mSysUiState;
private AutoHideController mAutoHideController;
+ private LightBarController mLightBarController;
+ private LightBarTransitionsController mLightBarTransitionsController;
private int mDisplayId;
private int mNavigationIconHints;
private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
@@ -141,7 +146,8 @@
NavBarHelper navBarHelper,
NavigationModeController navigationModeController,
SysUiState sysUiState, DumpManager dumpManager,
- AutoHideController autoHideController) {
+ AutoHideController autoHideController,
+ LightBarController lightBarController) {
// TODO: adding this in the ctor results in a dagger dependency cycle :(
mCommandQueue = commandQueue;
mOverviewProxyService = overviewProxyService;
@@ -150,6 +156,30 @@
mSysUiState = sysUiState;
dumpManager.registerDumpable(this);
mAutoHideController = autoHideController;
+ mLightBarController = lightBarController;
+ mLightBarTransitionsController = createLightBarTransitionsController();
+ }
+
+ // Separated into a method to keep setDependencies() clean/readable.
+ private LightBarTransitionsController createLightBarTransitionsController() {
+ return new LightBarTransitionsController(mContext,
+ new LightBarTransitionsController.DarkIntensityApplier() {
+ @Override
+ public void applyDarkIntensity(float darkIntensity) {
+ mOverviewProxyService.onNavButtonsDarkIntensityChanged(darkIntensity);
+ }
+
+ @Override
+ public int getTintAnimationDuration() {
+ return LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION;
+ }
+ }, mCommandQueue) {
+ @Override
+ public boolean supportsIconTintForNavMode(int navigationMode) {
+ // Always tint taskbar nav buttons (region sampling handles gesture bar separately).
+ return true;
+ }
+ };
}
public void init(int displayId) {
@@ -162,7 +192,7 @@
mEdgeBackGestureHandler.onNavigationModeChanged(
mNavigationModeController.addListener(this));
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
mEdgeBackGestureHandler.onNavBarAttached();
// Initialize component callback
Display display = mDisplayManager.getDisplay(displayId);
@@ -171,6 +201,7 @@
// Set initial state for any listeners
updateSysuiFlags();
mAutoHideController.setNavigationBar(mAutoHideUiElement);
+ mLightBarController.setNavigationBar(mLightBarTransitionsController);
mInitialized = true;
}
@@ -189,6 +220,8 @@
mWindowContext = null;
}
mAutoHideController.setNavigationBar(null);
+ mLightBarTransitionsController.destroy(mContext);
+ mLightBarController.setNavigationBar(null);
mInitialized = false;
}
@@ -268,6 +301,10 @@
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
InsetsVisibilities requestedVisibilities, String packageName) {
mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
+ if (mLightBarController != null && displayId == mDisplayId) {
+ mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/,
+ BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme);
+ }
if (mBehavior != behavior) {
mBehavior = behavior;
updateSysuiFlags();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index 98b9146..fce0c0c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -17,8 +17,10 @@
package com.android.systemui.qs
import android.content.Intent
+import android.os.Handler
import android.os.UserManager
import android.provider.Settings
+import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import android.view.View
import android.widget.Toast
import androidx.annotation.VisibleForTesting
@@ -35,6 +37,7 @@
import com.android.systemui.qs.FooterActionsController.ExpansionState.COLLAPSED
import com.android.systemui.qs.FooterActionsController.ExpansionState.EXPANDED
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.phone.SettingsButton
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -42,6 +45,7 @@
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.ViewController
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import javax.inject.Named
@@ -55,6 +59,7 @@
private val qsPanelController: QSPanelController,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
+ private val userTracker: UserTracker,
private val userInfoController: UserInfoController,
private val multiUserSwitchController: MultiUserSwitchController,
private val deviceProvisionedController: DeviceProvisionedController,
@@ -64,7 +69,9 @@
private val globalActionsDialog: GlobalActionsDialogLite,
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
- private val buttonsVisibleState: ExpansionState
+ private val buttonsVisibleState: ExpansionState,
+ private val globalSetting: GlobalSettings,
+ private val handler: Handler
) : ViewController<FooterActionsView>(view) {
enum class ExpansionState { COLLAPSED, EXPANDED }
@@ -83,6 +90,16 @@
mView.onUserInfoChanged(picture, isGuestUser)
}
+ private val multiUserSetting =
+ object : SettingObserver(
+ globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
+ override fun handleValueChanged(value: Int, observedChange: Boolean) {
+ if (observedChange) {
+ updateView()
+ }
+ }
+ }
+
private val onClickListener = View.OnClickListener { v ->
// Don't do anything until views are unhidden. Don't do anything if the tap looks
// suspicious.
@@ -182,6 +199,7 @@
return
}
this.listening = listening
+ multiUserSetting.isListening = listening
if (this.listening) {
userInfoController.addCallback(onUserInfoChangedListener)
updateView()
@@ -215,4 +233,4 @@
}
private fun isTunerEnabled() = tunerService.isTunerEnabled
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
index f6c89a9..dd4dc87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt
@@ -16,18 +16,22 @@
package com.android.systemui.qs
+import android.os.Handler
import android.os.UserManager
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.FooterActionsController.ExpansionState
import com.android.systemui.qs.dagger.QSFlagsModule
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import javax.inject.Named
@@ -35,6 +39,7 @@
private val qsPanelController: QSPanelController,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
+ private val userTracker: UserTracker,
private val userInfoController: UserInfoController,
private val multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
private val deviceProvisionedController: DeviceProvisionedController,
@@ -43,7 +48,9 @@
private val tunerService: TunerService,
private val globalActionsDialog: GlobalActionsDialogLite,
private val uiEventLogger: UiEventLogger,
- @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean
+ @Named(QSFlagsModule.PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
+ private val globalSettings: GlobalSettings,
+ @Main private val handler: Handler
) {
private lateinit var view: FooterActionsView
private lateinit var buttonsVisibleState: ExpansionState
@@ -60,8 +67,9 @@
fun build(): FooterActionsController {
return FooterActionsController(view, qsPanelController, activityStarter, userManager,
- userInfoController, multiUserSwitchControllerFactory.create(view),
+ userTracker, userInfoController, multiUserSwitchControllerFactory.create(view),
deviceProvisionedController, falsingManager, metricsLogger, tunerService,
- globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState)
+ globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState,
+ globalSettings, handler)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
index 2f189be..768598a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -133,10 +133,7 @@
x += width + mHorizontalSpacing;
}
}
- y += maxHeight;
- if (row > 0) {
- y += mVerticalSpacing;
- }
+ y += maxHeight + mVerticalSpacing;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 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/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 99cb700..18b401f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
@@ -41,7 +42,6 @@
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.widget.Switch;
import android.widget.Toast;
@@ -53,6 +53,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysUIToast;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -84,6 +85,7 @@
private final DndDetailAdapter mDetailAdapter;
private final SharedPreferences mSharedPreferences;
private final SettingObserver mSettingZenDuration;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mListening;
private boolean mShowingDetail;
@@ -100,7 +102,8 @@
QSLogger qsLogger,
ZenModeController zenModeController,
@Main SharedPreferences sharedPreferences,
- SecureSettings secureSettings
+ SecureSettings secureSettings,
+ DialogLaunchAnimator dialogLaunchAnimator
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -108,6 +111,7 @@
mSharedPreferences = sharedPreferences;
mDetailAdapter = new DndDetailAdapter();
mController.observe(getLifecycle(), mZenCallback);
+ mDialogLaunchAnimator = dialogLaunchAnimator;
mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler,
Settings.Secure.ZEN_DURATION, getHost().getUserId()) {
@Override
@@ -117,8 +121,6 @@
};
}
-
-
public static void setVisible(Context context, boolean visible) {
Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
}
@@ -187,14 +189,17 @@
switch (zenDuration) {
case Settings.Secure.ZEN_DURATION_PROMPT:
mUiHandler.post(() -> {
- Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
- mDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- SystemUIDialog.setShowForAllUsers(mDialog, true);
- SystemUIDialog.registerDismissListener(mDialog);
- SystemUIDialog.setWindowOnTop(mDialog);
- mUiHandler.post(() -> mDialog.show());
- mHost.collapsePanels();
+ Dialog dialog = makeZenModeDialog();
+ if (view != null) {
+ final Dialog hostDialog =
+ mDialogLaunchAnimator.showFromView(dialog, view, false);
+ setDialogListeners(dialog, hostDialog);
+ } else {
+ // If we are not launching with animator, register default
+ // dismiss listener
+ SystemUIDialog.registerDismissListener(dialog);
+ dialog.show();
+ }
});
break;
case Settings.Secure.ZEN_DURATION_FOREVER:
@@ -209,6 +214,20 @@
}
}
+ private Dialog makeZenModeDialog() {
+ AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
+ .createDialog();
+ SystemUIDialog.applyFlags(dialog);
+ SystemUIDialog.setShowForAllUsers(dialog, true);
+ return dialog;
+ }
+
+ private void setDialogListeners(Dialog zenModeDialog, Dialog hostDialog) {
+ // Zen mode dialog is never hidden.
+ SystemUIDialog.registerDismissListener(zenModeDialog, hostDialog::dismiss);
+ zenModeDialog.setOnCancelListener(dialog -> hostDialog.cancel());
+ }
+
@Override
protected void handleSecondaryClick(@Nullable View view) {
if (mController.isVolumeRestricted()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/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 77b9cc1..883552a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -56,6 +56,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.Utils;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
@@ -120,6 +121,7 @@
private TextView mMobileTitleText;
private TextView mMobileSummaryText;
private Switch mMobileDataToggle;
+ private View mMobileToggleDivider;
private Switch mWiFiToggle;
private FrameLayout mDoneLayout;
private Drawable mBackgroundOn;
@@ -207,6 +209,7 @@
mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+ mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider);
mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
@@ -378,6 +381,14 @@
mMobileNetworkLayout.setBackground(
isCarrierNetworkConnected ? mBackgroundOn : mBackgroundOff);
+ TypedArray array = mContext.obtainStyledAttributes(
+ R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
+ int dividerColor = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorSecondary);
+ mMobileToggleDivider.setBackgroundColor(isCarrierNetworkConnected
+ ? array.getColor(0, dividerColor) : dividerColor);
+ array.recycle();
+
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
deleted file mode 100644
index 26d1bbd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.user
-
-import android.content.Context
-import android.os.Bundle
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.view.WindowInsets
-import android.view.WindowManager
-import com.android.systemui.qs.PseudoGridView
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.R
-
-/**
- * Dialog for switching users or creating new ones.
- */
-class UserDialog(
- context: Context
-) : SystemUIDialog(context) {
-
- // create() is no-op after creation
- private lateinit var _doneButton: View
- /**
- * Button with text "Done" in dialog.
- */
- val doneButton: View
- get() {
- create()
- return _doneButton
- }
-
- private lateinit var _settingsButton: View
- /**
- * Button with text "User Settings" in dialog.
- */
- val settingsButton: View
- get() {
- create()
- return _settingsButton
- }
-
- private lateinit var _grid: PseudoGridView
- /**
- * Grid to populate with user avatar from adapter
- */
- val grid: ViewGroup
- get() {
- create()
- return _grid
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- window?.apply {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
- attributes.receiveInsetsIgnoringZOrder = true
- setGravity(Gravity.CENTER)
- }
- setContentView(R.layout.qs_user_dialog_content)
-
- _doneButton = requireViewById(R.id.done)
- _settingsButton = requireViewById(R.id.settings)
- _grid = requireViewById(R.id.grid)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index d74a50e..00e0454 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -21,13 +21,16 @@
import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
+import android.view.LayoutInflater
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import javax.inject.Provider
@@ -40,7 +43,7 @@
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
private val dialogLaunchAnimator: DialogLaunchAnimator,
- private val dialogFactory: (Context) -> UserDialog
+ private val dialogFactory: (Context) -> SystemUIDialog
) {
@Inject
@@ -54,7 +57,7 @@
activityStarter,
falsingManager,
dialogLaunchAnimator,
- { UserDialog(it) }
+ { SystemUIDialog(it) }
)
companion object {
@@ -71,9 +74,10 @@
with(dialogFactory(view.context)) {
setShowForAllUsers(true)
setCanceledOnTouchOutside(true)
- create() // Needs to be called before we can retrieve views
- settingsButton.setOnClickListener {
+ setTitle(R.string.qs_user_switch_dialog_title)
+ setPositiveButton(R.string.quick_settings_done, null)
+ setNeutralButton(R.string.quick_settings_more_user_settings) { _, _ ->
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
activityStarter.postStartActivityDismissingKeyguard(
@@ -81,12 +85,14 @@
0
)
}
- dismiss()
}
- doneButton.setOnClickListener { dismiss() }
+ val gridFrame = LayoutInflater.from(this.context)
+ .inflate(R.layout.qs_user_dialog_content, null)
+ setView(gridFrame)
val adapter = userDetailViewAdapterProvider.get()
- adapter.linkToViewGroup(grid)
+
+ adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
val hostDialog = dialogLaunchAnimator.showFromView(this, view)
adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index fa874b1..3ed7e84 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -986,6 +986,18 @@
}
}
+ public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onNavButtonsDarkIntensityChanged(darkIntensity);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy to update nav buttons dark intensity");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onNavButtonsDarkIntensityChanged()", e);
+ }
+ }
+
private void updateEnabledState() {
final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 75b3592..3cecbb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -533,9 +533,13 @@
* @param animate {@code true} to show animations.
*/
public void recomputeDisableFlags(int displayId, boolean animate) {
- int disabled1 = getDisabled1(displayId);
- int disabled2 = getDisabled2(displayId);
- disable(displayId, disabled1, disabled2, animate);
+ // This must update holding the lock otherwise it can clobber the disabled flags set on the
+ // binder thread from the disable() call
+ synchronized (mLock) {
+ int disabled1 = getDisabled1(displayId);
+ int disabled2 = getDisabled2(displayId);
+ disable(displayId, disabled1, disabled2, animate);
+ }
}
private void setDisabled(int displayId, int disabled1, int disabled2) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 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/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index ecde001..83ef41e 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,55 @@
}
/**
+ * 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();
+ 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 +585,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/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 0fb9fc8..9bf21d1 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,13 @@
return false
}
- fun isNewPipelineEnabled(): Boolean = featureFlags.isEnabled(
- Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
+ fun assertLegacyPipelineEnabled(): Nothing =
+ error("Old pipeline code running w/ new pipeline enabled")
+
+ fun isNewPipelineEnabled(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
fun isSmartspaceDedupingEnabled(): Boolean =
- featureFlags.isEnabled(Flags.SMARTSPACE)
- && featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
+ featureFlags.isEnabled(Flags.SMARTSPACE) &&
+ featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e78b4f4..0389a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -791,6 +791,7 @@
* these don't exist, although there are a couple exceptions.
*/
public Iterable<NotificationEntry> getPendingNotificationsIterator() {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mPendingNotifications.values();
}
@@ -803,6 +804,7 @@
* @return a {@link NotificationEntry} if it has been prepared, else null
*/
public NotificationEntry getActiveNotificationUnfiltered(String key) {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mActiveNotifications.get(key);
}
@@ -811,6 +813,7 @@
* notification doesn't exist.
*/
public NotificationEntry getPendingOrActiveNotif(String key) {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
return entry;
@@ -945,6 +948,7 @@
* @return A read-only list of the currently active notifications
*/
public List<NotificationEntry> getVisibleNotifications() {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications;
}
@@ -954,17 +958,20 @@
*/
@Override
public Collection<NotificationEntry> getAllNotifs() {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyAllNotifications;
}
@Nullable
@Override
public NotificationEntry getEntry(String key) {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return getPendingOrActiveNotif(key);
}
/** @return A count of the active notifications */
public int getActiveNotificationsCount() {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications.size();
}
@@ -972,6 +979,7 @@
* @return {@code true} if there is at least one notification that should be visible right now
*/
public boolean hasActiveNotifications() {
+ mNotifPipelineFlags.checkLegacyPipelineEnabled();
return mReadOnlyNotifications.size() != 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java
new file mode 100644
index 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/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..2787975 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
@@ -253,6 +253,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;
}
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 b5d4635..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,10 +358,10 @@
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();
}
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/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/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
similarity index 65%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
index 3729c09..e2edc01 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+/** 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/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
similarity index 76%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
index 3729c09..11d4e83 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
-/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
- */
-parcelable TaskFragmentAppearedInfo;
+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/core/java/android/window/TaskFragmentAppearedInfo.aidl b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
similarity index 63%
copy from core/java/android/window/TaskFragmentAppearedInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
index 3729c09..6e7f415 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package android.window;
+package com.android.systemui.statusbar.notification.collection.render
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
/**
- * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
- * @hide
+ * Extension used during the render stage which assumes the summary exists, and throws a more
+ * helpful error if not.
*/
-parcelable TaskFragmentAppearedInfo;
+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 9411de7..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
@@ -47,7 +48,7 @@
import dagger.Lazy
import java.io.FileDescriptor
import java.io.PrintWriter
-import java.util.*
+import java.util.Optional
import javax.inject.Inject
/**
@@ -85,6 +86,7 @@
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()) {
@@ -152,8 +155,14 @@
}
override fun resetUserExpandedStates() {
- for (entry in entryManager.visibleNotifications) {
- entry.resetUserExpansion()
+ if (notifPipelineFlags.isNewPipelineEnabled()) {
+ for (entry in notifPipeline.get().allNotifs) {
+ entry.resetUserExpansion()
+ }
+ } else {
+ for (entry in entryManager.visibleNotifications) {
+ entry.resetUserExpansion()
+ }
}
}
@@ -167,9 +176,12 @@
}
}
- override fun getActiveNotificationsCount(): Int {
- return entryManager.activeNotificationsCount
- }
+ override fun getActiveNotificationsCount(): Int =
+ if (notifPipelineFlags.isNewPipelineEnabled()) {
+ notifPipeline.get().getShadeListCount()
+ } else {
+ entryManager.activeNotificationsCount
+ }
companion object {
// NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 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..1c2b938 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
@@ -92,6 +92,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 +132,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 +146,7 @@
private boolean mUpdateBackgroundOnUpdate;
private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
+ private boolean mIsFaded;
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -812,6 +815,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 +2349,7 @@
onExpansionChanged(false /* userAction */, wasExpanded);
if (mIsSummaryWithChildren) {
mChildrenContainer.updateGroupOverflow();
+ resetChildSystemExpandedStates();
}
}
}
@@ -2774,17 +2785,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()) {
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 59ea0ea..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;
@@ -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/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 a10d309..b9f0241 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
@@ -678,7 +678,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 +1173,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 +1444,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())) {
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..dd68f94 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);
@@ -988,7 +995,7 @@
}
public int getVisibleNotificationCount() {
- return mView.getVisibleNotificationCount();
+ return mNotifStats.getNumActiveNotifs();
}
public int getIntrinsicContentHeight() {
@@ -1183,7 +1190,7 @@
public void updateShowEmptyShadeView() {
mShowEmptyShadeView = mBarState != KEYGUARD
&& (!mView.isQsExpanded() || mView.isUsingSplitNotificationShade())
- && mView.getVisibleNotificationCount() == 0;
+ && getVisibleNotificationCount() == 0;
mView.updateEmptyShadeView(
mShowEmptyShadeView,
@@ -1246,29 +1253,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 +1383,10 @@
return mNotificationListContainer;
}
+ public NotifStackController getNotifStackController() {
+ return mNotifStackController;
+ }
+
public void resetCheckSnoozeLeavebehind() {
mView.resetCheckSnoozeLeavebehind();
}
@@ -1394,17 +1398,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 +1869,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/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 67f51cb..aa3b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintManager;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.PowerManager;
@@ -46,9 +47,11 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.FileDescriptor;
@@ -71,6 +74,7 @@
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+ private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -167,6 +171,10 @@
private final MetricsLogger mMetricsLogger;
private final AuthController mAuthController;
+ private final StatusBarStateController mStatusBarStateController;
+
+ private long mLastFpFailureUptimeMillis;
+ private int mNumConsecutiveFpFailures;
private static final class PendingAuthenticated {
public final int userId;
@@ -209,7 +217,10 @@
BIOMETRIC_IRIS_FAILURE(403),
@UiEvent(doc = "A biometric event of type iris errored.")
- BIOMETRIC_IRIS_ERROR(404);
+ BIOMETRIC_IRIS_ERROR(404),
+
+ @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
+ BIOMETRIC_BOUNCER_SHOWN(916);
private final int mId;
@@ -257,7 +268,8 @@
NotificationMediaManager notificationMediaManager,
WakefulnessLifecycle wakefulnessLifecycle,
ScreenLifecycle screenLifecycle,
- AuthController authController) {
+ AuthController authController,
+ StatusBarStateController statusBarStateController) {
mContext = context;
mPowerManager = powerManager;
mShadeController = shadeController;
@@ -279,6 +291,7 @@
mKeyguardBypassController.setUnlockController(this);
mMetricsLogger = metricsLogger;
mAuthController = authController;
+ mStatusBarStateController = statusBarStateController;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -620,6 +633,22 @@
.setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
.ifPresent(UI_EVENT_LOGGER::log);
+
+ long currUptimeMillis = SystemClock.uptimeMillis();
+ if (currUptimeMillis - mLastFpFailureUptimeMillis < 2000) { // attempt within 2 seconds
+ mNumConsecutiveFpFailures += 1;
+ } else {
+ mNumConsecutiveFpFailures = 1;
+ }
+ mLastFpFailureUptimeMillis = currUptimeMillis;
+
+ if (biometricSourceType.equals(BiometricSourceType.FINGERPRINT)
+ && mUpdateMonitor.isUdfpsSupported()
+ && mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+ mKeyguardViewController.showBouncer(true);
+ UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN);
+ mNumConsecutiveFpFailures = 0;
+ }
cleanup();
}
@@ -631,6 +660,16 @@
.addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
.ifPresent(UI_EVENT_LOGGER::log);
+
+ // if we're on the shade and we're locked out, immediately show the bouncer
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT
+ && (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
+ || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+ && mUpdateMonitor.isUdfpsSupported()
+ && (mStatusBarStateController.getState() == StatusBarState.SHADE
+ || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) {
+ mKeyguardViewController.showBouncer(true);
+ }
cleanup();
}
@@ -664,6 +703,8 @@
mBiometricModeListener.onResetMode();
mBiometricModeListener.notifyBiometricAuthModeChanged();
}
+ mNumConsecutiveFpFailures = 0;
+ mLastFpFailureUptimeMillis = 0;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 927b4c8..0664900 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,15 +36,19 @@
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 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.
*/
+@StatusBarFragmentScope
public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
DarkIconDispatcher.DarkReceiver, NotificationWakeUpCoordinator.WakeUpListener {
public static final int CONTENT_FADE_DURATION = 110;
@@ -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),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 353868b..9647486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,6 +82,13 @@
public void onStrongAuthStateChanged(int userId) {
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
+
+ @Override
+ public void onLockedOutStateChanged(BiometricSourceType type) {
+ if (type == BiometricSourceType.FINGERPRINT) {
+ mBouncerPromptReason = mCallback.getBouncerPromptReason();
+ }
+ }
};
private final Runnable mRemoveViewRunnable = this::removeView;
private final KeyguardBypassController mKeyguardBypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 570b0ca..88ae0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -37,7 +37,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.policy.BatteryController;
import java.io.FileDescriptor;
@@ -251,7 +250,7 @@
private void updateNavigation() {
if (mNavigationBarController != null
- && !QuickStepContract.isGesturalMode(mNavigationMode)) {
+ && mNavigationBarController.supportsIconTintForNavMode(mNavigationMode)) {
mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index 9021b74..415fb92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -28,6 +28,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -230,6 +231,14 @@
}
/**
+ * Return whether to use the tint calculated in this class for nav icons.
+ */
+ public boolean supportsIconTintForNavMode(int navigationMode) {
+ // In gesture mode, we already do region sampling to update tint based on content beneath.
+ return !QuickStepContract.isGesturalMode(navigationMode);
+ }
+
+ /**
* Interface to apply a specific dark intensity.
*/
public interface DarkIntensityApplier {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 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/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 94b010d..d26ac29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1172,16 +1172,16 @@
HeadsUpAppearanceController oldController = mHeadsUpAppearanceController;
if (mHeadsUpAppearanceController != null) {
// This view is being recreated, let's destroy the old one
+ // TODO(b/205609837): Automatically destroy the old controller so that this
+ // class doesn't need to hold a reference to 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 =
+ statusBarFragmentComponent.getHeadsUpAppearanceController();
+ // TODO(b/205609837): Delete this readFrom method so that this class doesn't
+ // need to hold a reference to the old controller.
mHeadsUpAppearanceController.readFrom(oldController);
mLightsOutNotifController.setLightsOutNotifView(
@@ -1488,6 +1488,7 @@
mBubblesOptional,
mPresenter,
mStackScrollerController.getNotificationListContainer(),
+ mStackScrollerController.getNotifStackController(),
mNotificationActivityStarter,
mPresenter);
}
@@ -1909,10 +1910,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) {
@@ -1992,7 +1989,7 @@
}
}
- public void maybeEscalateHeadsUp() {
+ private void maybeEscalateHeadsUp() {
mHeadsUpManager.getAllEntries().forEach(entry -> {
final StatusBarNotification sbn = entry.getSbn();
final Notification notification = sbn.getNotification();
@@ -2003,6 +2000,7 @@
try {
EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION,
sbn.getKey());
+ wakeUpForFullScreenIntent();
notification.fullScreenIntent.send();
entry.notifyFullScreenIntentLaunched();
} catch (PendingIntent.CanceledException e) {
@@ -2012,6 +2010,17 @@
mHeadsUpManager.releaseAllImmediately();
}
+ void wakeUpForFullScreenIntent() {
+ if (isGoingToSleep() || mDozing) {
+ mPowerManager.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:full_screen_intent");
+ mWakeUpComingFromTouch = false;
+ mWakeUpTouchLocation = null;
+ }
+ }
+
void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
@@ -3541,7 +3550,7 @@
DejankUtils.startDetectingBlockingIpcs(tag);
updateRevealEffect(false /* wakingUp */);
updateNotificationPanelTouchState();
- notifyHeadsUpGoingToSleep();
+ maybeEscalateHeadsUp();
dismissVolumeDialog();
mWakeUpCoordinator.setFullyAwake(false);
mBypassHeadsUpNotifier.setFullyAwake(false);
@@ -4086,10 +4095,6 @@
}
}
- protected void notifyHeadsUpGoingToSleep() {
- maybeEscalateHeadsUp();
- }
-
/**
* @return Whether the security bouncer from Keyguard is showing.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index a77a097..ae3b7ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -30,11 +30,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.media.AudioAttributes;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
@@ -106,10 +106,8 @@
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
- private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .build();
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@Inject
StatusBarCommandQueueCallbacks(
@@ -611,9 +609,9 @@
}
private void vibrateForCameraGesture() {
- // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibratorOptional.ifPresent(
- v -> v.vibrate(mCameraLaunchGestureVibrationEffect, VIBRATION_ATTRIBUTES));
+ v -> v.vibrate(mCameraLaunchGestureVibrationEffect,
+ HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES));
}
private static VibrationEffect getCameraGestureVibrationEffect(
@@ -627,6 +625,8 @@
.compose();
}
if (vibratorOptional.isPresent() && vibratorOptional.get().hasAmplitudeControl()) {
+ // Make sure to pass -1 for repeat so VibratorManagerService doesn't stop us when going
+ // to sleep.
return VibrationEffect.createWaveform(
StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_TIMINGS,
StatusBar.CAMERA_LAUNCH_GESTURE_VIBRATION_AMPLITUDES,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 7ab4a1e..e2bf0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -441,6 +441,8 @@
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showBouncer(boolean scrimmed) {
+ resetAlternateAuth(false);
+
if (mShowing && !mBouncer.isShowing()) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
}
@@ -553,12 +555,13 @@
public void onStartedWakingUp() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(false);
- View currentView = getCurrentNavBarView();
- if (currentView != null) {
- currentView.animate()
- .alpha(1f)
- .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
- .start();
+ NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ if (navBarView != null) {
+ navBarView.forEachView(view ->
+ view.animate()
+ .alpha(1f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start());
}
}
@@ -566,12 +569,13 @@
public void onStartedGoingToSleep() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(true);
- View currentView = getCurrentNavBarView();
- if (currentView != null) {
- currentView.animate()
- .alpha(0f)
- .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
- .start();
+ NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ if (navBarView != null) {
+ navBarView.forEachView(view ->
+ view.animate()
+ .alpha(0f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start());
}
}
@@ -1013,17 +1017,6 @@
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
- /**
- * Updates the visibility of the nav bar content views.
- */
- private void updateNavigationBarContentVisibility(boolean navBarContentVisible) {
- final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
- if (navBarView != null && navBarView.getCurrentView() != null) {
- final View currentView = navBarView.getCurrentView();
- currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE);
- }
- }
-
private View getCurrentNavBarView() {
final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
return navBarView != null ? navBarView.getCurrentView() : null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 11ed8cd..863ce57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -41,6 +41,8 @@
import android.util.EventLog;
import android.view.View;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
@@ -588,7 +590,8 @@
}
}
- private void handleFullScreenIntent(NotificationEntry entry) {
+ @VisibleForTesting
+ void handleFullScreenIntent(NotificationEntry entry) {
if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
if (shouldSuppressFullScreenIntent(entry)) {
mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey());
@@ -612,6 +615,7 @@
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
+ mStatusBar.wakeUpForFullScreenIntent();
fullscreenIntent.send();
entry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 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/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 1130ec2..ed52a81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -36,6 +36,8 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
+import androidx.annotation.Nullable;
+
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.DialogListener;
@@ -303,13 +305,32 @@
* the screen off / close system dialogs broadcast.
* <p>
* <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
- * calling this because it causes a leak of BroadcastReceiver.
+ * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that
+ * takes an extra Runnable as a parameter.
*
* @param dialog The dialog to be associated with the listener.
*/
public static void registerDismissListener(Dialog dialog) {
+ registerDismissListener(dialog, null);
+ }
+
+
+ /**
+ * Registers a listener that dismisses the given dialog when it receives
+ * the screen off / close system dialogs broadcast.
+ * <p>
+ * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
+ * calling this because it causes a leak of BroadcastReceiver.
+ *
+ * @param dialog The dialog to be associated with the listener.
+ * @param dismissAction An action to run when the dialog is dismissed.
+ */
+ public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
DismissReceiver dismissReceiver = new DismissReceiver(dialog);
- dialog.setOnDismissListener(d -> dismissReceiver.unregister());
+ dialog.setOnDismissListener(d -> {
+ dismissReceiver.unregister();
+ if (dismissAction != null) dismissAction.run();
+ });
dismissReceiver.register();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/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..6e31192 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,6 +18,7 @@
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.fragment.CollapsedStatusBarFragment;
@@ -66,4 +67,8 @@
@StatusBarFragmentScope
@RootView
PhoneStatusBarView getPhoneStatusBarView();
+
+ /** */
+ @StatusBarFragmentScope
+ HeadsUpAppearanceController getHeadsUpAppearanceController();
}
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/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
index 58e0cb2..3696ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewLaunchAnimatorControllerTest.kt
@@ -16,22 +16,53 @@
package com.android.systemui.animation
+import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.widget.LinearLayout
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewParent
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var view: View
+ @Mock lateinit var rootView: ViewGroup
+ @Mock lateinit var viewParent: ViewParent
+ @Mock lateinit var drawable: Drawable
+ lateinit var controller: GhostedViewLaunchAnimatorController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(view.rootView).thenReturn(rootView)
+ whenever(view.background).thenReturn(drawable)
+ whenever(view.height).thenReturn(0)
+ whenever(view.width).thenReturn(0)
+ whenever(view.parent).thenReturn(viewParent)
+ whenever(view.visibility).thenReturn(View.VISIBLE)
+ whenever(view.invalidate()).then { /* NO-OP */ }
+ whenever(view.getLocationOnScreen(any())).then { /* NO-OP */ }
+ whenever(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+ whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
+ controller = GhostedViewLaunchAnimatorController(view, 0, interactionJankMonitor)
+ }
+
@Test
fun animatingOrphanViewDoesNotCrash() {
- val ghostedView = LinearLayout(mContext)
- val controller = GhostedViewLaunchAnimatorController(ghostedView)
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
controller.onIntentStarted(willAnimate = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0e86964..1cf21ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -351,9 +351,8 @@
mSystemClock.advanceTime(205);
mController.onTouchOutsideView();
- // THEN show the bouncer and reset alt auth
+ // THEN show the bouncer
verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
- verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 6d8645e..b774daf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -41,6 +41,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
@@ -64,9 +65,6 @@
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
-import java.util.Optional;
-import java.util.function.Function;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,6 +73,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+import java.util.function.Function;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -103,6 +104,7 @@
private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
+ private @Mock InteractionJankMonitor mInteractionJankMonitor;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -121,6 +123,8 @@
.thenReturn(mUnfoldAnimationOptional);
when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
+ when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
+ when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -144,7 +148,8 @@
mKeyguardStateController,
() -> mKeyguardUnlockAnimationController,
mUnlockedScreenOffAnimationController,
- () -> mNotificationShadeDepthController);
+ () -> mNotificationShadeDepthController,
+ mInteractionJankMonitor);
mViewMediator.start();
}
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..ed5b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -80,7 +80,7 @@
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);
@@ -96,13 +96,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 +112,7 @@
@Test
public void assistantCallbacksFiredAfterConnecting() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -133,7 +133,7 @@
@Test
public void a11yCallbacksFiredAfterModeChange() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -146,7 +146,7 @@
@Test
public void assistantCallbacksFiredAfterNavModeChange() {
- mNavBarHelper.init(mContext);
+ mNavBarHelper.init();
// 1st set of callbacks get called when registering
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -159,7 +159,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/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 31d8830..9d2541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.After;
@@ -86,7 +87,8 @@
mock(TaskbarDelegate.class),
mNavigationBarFactory,
mock(DumpManager.class),
- mock(AutoHideController.class)));
+ mock(AutoHideController.class),
+ mock(LightBarController.class)));
initializeNavigationBars();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
index d2bba36..26f04fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
@@ -1,6 +1,8 @@
package com.android.systemui.qs
+import android.os.Handler
import android.os.UserManager
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -16,10 +18,12 @@
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.FooterActionsController.ExpansionState
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.tuner.TunerService
+import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.leaks.FakeTunerService
import com.android.systemui.utils.leaks.LeakCheckedTest
import com.google.common.truth.Truth.assertThat
@@ -42,6 +46,8 @@
@Mock
private lateinit var userManager: UserManager
@Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
@@ -62,11 +68,13 @@
private lateinit var view: FooterActionsView
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
private lateinit var testableLooper: TestableLooper
+ private lateinit var fakeSettings: FakeSettings
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ fakeSettings = FakeSettings()
injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
@@ -74,10 +82,11 @@
.inflate(R.layout.footer_actions, null) as FooterActionsView
controller = FooterActionsController(view, qsPanelController, activityStarter,
- userManager, userInfoController, multiUserSwitchController,
+ userManager, userTracker, userInfoController, multiUserSwitchController,
deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
globalActionsDialog, uiEventLogger, showPMLiteButton = true,
- buttonsVisibleState = ExpansionState.EXPANDED)
+ buttonsVisibleState = ExpansionState.EXPANDED, fakeSettings,
+ Handler(testableLooper.looper))
controller.init()
ViewUtils.attachView(view)
// View looper is the testable looper associated with the test
@@ -122,4 +131,24 @@
assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
}
+
+ @Test
+ fun testMultiUserSwitchUpdatedWhenSettingChanged() {
+ // When expanded, listening is true
+ controller.setListening(true)
+ testableLooper.processAllMessages()
+
+ val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
+ assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
+
+ // The setting is only used as an indicator for whether the view should refresh. The actual
+ // value of the setting is ignored; isMultiUserEnabled is the source of truth
+ whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
+
+ // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
+ fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
+ testableLooper.processAllMessages()
+
+ assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index f99703e..3ea2cc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -16,22 +16,28 @@
package com.android.systemui.qs.tiles
+import android.app.Dialog
import android.content.ContextWrapper
import android.content.SharedPreferences
import android.os.Handler
import android.provider.Settings
+import android.provider.Settings.Global.ZEN_MODE_OFF
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
@@ -40,9 +46,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.io.File
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -70,6 +79,10 @@
private lateinit var zenModeController: ZenModeController
@Mock
private lateinit var sharedPreferences: SharedPreferences
+ @Mock
+ private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock
+ private lateinit var hostDialog: Dialog
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
@@ -81,15 +94,17 @@
testableLooper = TestableLooper.get(this)
secureSettings = FakeSettings()
- Mockito.`when`(qsHost.userId).thenReturn(DEFAULT_USER)
- Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+ whenever(qsHost.userId).thenReturn(DEFAULT_USER)
+ whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+ whenever(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
+ .thenReturn(hostDialog)
val wrappedContext = object : ContextWrapper(context) {
override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
return sharedPreferences
}
}
- Mockito.`when`(qsHost.context).thenReturn(wrappedContext)
+ whenever(qsHost.context).thenReturn(wrappedContext)
tile = DndTile(
qsHost,
@@ -102,7 +117,8 @@
qsLogger,
zenModeController,
sharedPreferences,
- secureSettings
+ secureSettings,
+ dialogLaunchAnimator
)
}
@@ -147,4 +163,32 @@
assertThat(tile.state.forceExpandIcon).isTrue()
}
+
+ @Test
+ fun testLaunchDialogFromViewWhenPrompt() {
+ whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+ secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER)
+ testableLooper.processAllMessages()
+
+ val view = View(context)
+ tile.handleClick(view)
+ testableLooper.processAllMessages()
+
+ verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean())
+ }
+
+ @Test
+ fun testNoLaunchDialogWhenNotPrompt() {
+ whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+
+ secureSettings.putIntForUser(KEY, 60, DEFAULT_USER)
+ testableLooper.processAllMessages()
+
+ val view = View(context)
+ tile.handleClick(view)
+ testableLooper.processAllMessages()
+
+ verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean())
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
deleted file mode 100644
index d5fe588..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.user
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class UserDialogTest : SysuiTestCase() {
-
- private lateinit var dialog: UserDialog
-
- @Before
- fun setUp() {
- dialog = UserDialog(mContext)
- }
-
- @After
- fun tearDown() {
- dialog.dismiss()
- }
-
- @Test
- fun doneButtonExists() {
- assertThat(dialog.doneButton).isInstanceOf(View::class.java)
- }
-
- @Test
- fun settingsButtonExists() {
- assertThat(dialog.settingsButton).isInstanceOf(View::class.java)
- }
-
- @Test
- fun gridExistsAndIsViewGroup() {
- assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java)
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index ea3a42c..3c4a557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.user
import android.app.Dialog
+import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
import android.testing.AndroidTestingRunner
@@ -28,6 +29,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -43,7 +45,6 @@
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.argThat
-import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -53,27 +54,21 @@
class UserSwitchDialogControllerTest : SysuiTestCase() {
@Mock
- private lateinit var dialog: UserDialog
+ private lateinit var dialog: SystemUIDialog
@Mock
private lateinit var falsingManager: FalsingManager
@Mock
- private lateinit var settingsView: View
- @Mock
- private lateinit var doneView: View
- @Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var userDetailViewAdapter: UserDetailView.Adapter
@Mock
private lateinit var launchView: View
@Mock
- private lateinit var gridView: PseudoGridView
- @Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Mock
private lateinit var hostDialog: Dialog
@Captor
- private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
+ private lateinit var clickCaptor: ArgumentCaptor<DialogInterface.OnClickListener>
private lateinit var controller: UserSwitchDialogController
@@ -81,11 +76,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(dialog.settingsButton).thenReturn(settingsView)
- `when`(dialog.doneButton).thenReturn(doneView)
- `when`(dialog.grid).thenReturn(gridView)
-
`when`(launchView.context).thenReturn(mContext)
+ `when`(dialog.context).thenReturn(mContext)
`when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
.thenReturn(hostDialog)
@@ -105,30 +97,6 @@
}
@Test
- fun createCalledBeforeDoneButton() {
- controller.showDialog(launchView)
- val inOrder = inOrder(dialog)
- inOrder.verify(dialog).create()
- inOrder.verify(dialog).doneButton
- }
-
- @Test
- fun createCalledBeforeSettingsButton() {
- controller.showDialog(launchView)
- val inOrder = inOrder(dialog)
- inOrder.verify(dialog).create()
- inOrder.verify(dialog).settingsButton
- }
-
- @Test
- fun createCalledBeforeGrid() {
- controller.showDialog(launchView)
- val inOrder = inOrder(dialog)
- inOrder.verify(dialog).create()
- inOrder.verify(dialog).grid
- }
-
- @Test
fun dialog_showForAllUsers() {
controller.showDialog(launchView)
verify(dialog).setShowForAllUsers(true)
@@ -143,51 +111,44 @@
@Test
fun adapterAndGridLinked() {
controller.showDialog(launchView)
- verify(userDetailViewAdapter).linkToViewGroup(gridView)
+ verify(userDetailViewAdapter).linkToViewGroup(any<PseudoGridView>())
}
@Test
- fun clickDoneButton_dismiss() {
+ fun doneButtonSetWithNullHandler() {
controller.showDialog(launchView)
- verify(doneView).setOnClickListener(capture(clickCaptor))
-
- clickCaptor.value.onClick(doneView)
-
- verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
- verify(dialog).dismiss()
+ verify(dialog).setPositiveButton(anyInt(), eq(null))
}
@Test
- fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() {
+ fun clickSettingsButton_noFalsing_opensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
controller.showDialog(launchView)
- verify(settingsView).setOnClickListener(capture(clickCaptor))
+ verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
- clickCaptor.value.onClick(settingsView)
+ clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
verify(activityStarter)
.postStartActivityDismissingKeyguard(
argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
eq(0)
)
- verify(dialog).dismiss()
}
@Test
- fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() {
+ fun clickSettingsButton_Falsing_notOpensSettings() {
`when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
controller.showDialog(launchView)
- verify(settingsView).setOnClickListener(capture(clickCaptor))
+ verify(dialog).setNeutralButton(anyInt(), capture(clickCaptor))
- clickCaptor.value.onClick(settingsView)
+ clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
- verify(dialog).dismiss()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 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/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/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 25fd801..07debe6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -101,6 +102,8 @@
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private ScreenLifecycle mScreenLifecycle;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -123,7 +126,7 @@
mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters,
mMetricsLogger, mDumpManager, mPowerManager,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
- mAuthController);
+ mAuthController, mStatusBarStateController);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
}
@@ -378,6 +381,23 @@
}
@Test
+ public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+ // GIVEN UDFPS is supported
+ when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+
+ // WHEN udfps fails twice - then don't show the bouncer
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+
+ // WHEN udfps fails the third time
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
+
+ @Test
public void onFinishedGoingToSleep_authenticatesWhenPending() {
when(mUpdateMonitor.isGoingToSleep()).thenReturn(true);
mBiometricUnlockController.onFinishedGoingToSleep(-1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 6f174cb..c5bdfed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -374,6 +374,21 @@
}
@Test
+ public void testHideAltAuth_onShowBouncer() {
+ // GIVEN alt auth is showing
+ mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ when(mBouncer.isShowing()).thenReturn(false);
+ when(mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()).thenReturn(true);
+ reset(mAlternateAuthInterceptor);
+
+ // WHEN showBouncer is called
+ mStatusBarKeyguardViewManager.showBouncer(true);
+
+ // THEN alt bouncer should be hidden
+ verify(mAlternateAuthInterceptor).hideAlternateAuthBouncer();
+ }
+
+ @Test
public void testUpdateResources_delegatesToBouncer() {
mStatusBarKeyguardViewManager.updateResources();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 348c181..07ec0e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -35,6 +35,7 @@
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Handler;
@@ -116,6 +117,8 @@
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
+ private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ @Mock
private Handler mHandler;
@Mock
private BubblesManager mBubblesManager;
@@ -137,7 +140,7 @@
@Mock
private OnUserInteractionCallback mOnUserInteractionCallback;
@Mock
- private NotificationActivityStarter mNotificationActivityStarter;
+ private StatusBarNotificationActivityStarter mNotificationActivityStarter;
@Mock
private ActivityLaunchAnimator mActivityLaunchAnimator;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -219,7 +222,7 @@
mock(NotificationLockscreenUserManager.class),
mShadeController,
mKeyguardStateController,
- mock(NotificationInterruptStateProvider.class),
+ mNotificationInterruptStateProvider,
mock(LockPatternUtils.class),
mock(StatusBarRemoteInputCallback.class),
mActivityIntentHelper,
@@ -375,4 +378,27 @@
// Notification should not be cancelled.
verify(mEntryManager, never()).performRemoveNotification(eq(sbn), any(), anyInt());
}
+
+ @Test
+ public void testOnFullScreenIntentWhenDozing_wakeUpDevice() {
+ // GIVEN entry that can has a full screen intent that can show
+ Notification.Builder nb = new Notification.Builder(mContext, "a")
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFullScreenIntent(mock(PendingIntent.class), true);
+ StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0,
+ "tag" + System.currentTimeMillis(), 0, 0,
+ nb.build(), new UserHandle(0), null, 0);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
+ when(entry.getSbn()).thenReturn(sbn);
+ when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
+ .thenReturn(true);
+
+ // WHEN
+ mNotificationActivityStarter.handleFullScreenIntent(entry);
+
+ // THEN display should try wake up for the full screen intent
+ verify(mStatusBar).wakeUpForFullScreenIntent();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 609d69c..d36c516 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
@@ -48,6 +48,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;
@@ -65,7 +66,9 @@
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;
@@ -90,8 +93,12 @@
private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
private OperatorNameViewController mOperatorNameViewController;
+ @Mock
private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory;
+ @Mock
private StatusBarFragmentComponent mStatusBarFragmentComponent;
+ @Mock
+ private HeadsUpAppearanceController mHeadsUpAppearanceController;
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -254,6 +261,32 @@
}
@Test
+ public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
+ mFragments.dispatchResume();
+ processAllMessages();
+ CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+
+ when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+ }
+
+ @Test
+ public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
+ mFragments.dispatchResume();
+ processAllMessages();
+ CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment;
+
+ when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
+
+ fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+ assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+ }
+
+ @Test
public void setUp_fragmentCreatesDaggerComponent() {
mFragments.dispatchResume();
processAllMessages();
@@ -264,11 +297,8 @@
@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);
@@ -306,6 +336,13 @@
mOperatorNameViewControllerFactory);
}
+ private void setUpDaggerComponent() {
+ when(mStatusBarFragmentComponentFactory.create(any()))
+ .thenReturn(mStatusBarFragmentComponent);
+ when(mStatusBarFragmentComponent.getHeadsUpAppearanceController())
+ .thenReturn(mHeadsUpAppearanceController);
+ }
+
private void setUpNotificationIconAreaController() {
mMockNotificationAreaController = mock(NotificationIconAreaController.class);
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/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 67bb726..f1599e4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -39,6 +39,7 @@
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -1101,8 +1102,12 @@
try {
MagnificationProcessor magnificationProcessor =
mSystemSupport.getMagnificationProcessor();
- return magnificationProcessor
- .setScaleAndCenter(displayId, scale, centerX, centerY, animate, mId);
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setScale(scale)
+ .setCenterX(centerX)
+ .setCenterY(centerY).build();
+ return magnificationProcessor.setMagnificationConfig(displayId, config, animate,
+ mId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 572cfdc..f3a5d35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3528,9 +3528,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)
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 6473bf5..327f087 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -84,6 +84,8 @@
@GuardedBy("mLock")
private int mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+ @GuardedBy("mLock")
+ private int mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
// Track the active user to reset the magnification and get the associated user settings.
private @UserIdInt int mUserId = UserHandle.USER_SYSTEM;
@GuardedBy("mLock")
@@ -239,6 +241,7 @@
synchronized (mLock) {
mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+ mLastActivatedMode = mActivatedMode;
}
logMagnificationModeWithImeOnIfNeeded();
disableFullScreenMagnificationIfNeeded(displayId);
@@ -276,6 +279,7 @@
synchronized (mLock) {
mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+ mLastActivatedMode = mActivatedMode;
}
logMagnificationModeWithImeOnIfNeeded();
} else {
@@ -298,6 +302,16 @@
}
/**
+ * Returns the last activated magnification mode. If there is no activated magnifier before, it
+ * returns fullscreen mode by default.
+ */
+ public int getLastActivatedMode() {
+ synchronized (mLock) {
+ return mLastActivatedMode;
+ }
+ }
+
+ /**
* Wrapper method of logging the magnification activated mode and its duration of the usage
* when the magnification is disabled.
*
@@ -336,6 +350,7 @@
synchronized (mLock) {
fullMagnificationController = mFullScreenMagnificationController;
windowMagnificationManager = mWindowMagnificationMgr;
+ mLastActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
}
mScaleProvider.onUserChanged(userId);
@@ -462,7 +477,15 @@
return mTempPoint;
}
- private boolean isActivated(int displayId, int mode) {
+ /**
+ * Return {@code true} if the specified magnification mode on the given display is activated
+ * or not.
+ *
+ * @param displayId The logical displayId.
+ * @param mode It's either ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN or
+ * ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW.
+ */
+ public boolean isActivated(int displayId, int mode) {
boolean isActivated = false;
if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
synchronized (mLock) {
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 efc6d51..2324a5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -16,6 +16,13 @@
package com.android.server.accessibility.magnification;
+import static android.accessibilityservice.MagnificationConfig.DEFAULT_MODE;
+import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
+import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
+import android.accessibilityservice.MagnificationConfig;
import android.annotation.NonNull;
import android.graphics.Region;
@@ -23,6 +30,22 @@
* Processor class for AccessibilityService connection to control magnification on the specified
* display. This wraps the function of magnification controller.
*
+ * <p>
+ * If the magnification config uses {@link DEFAULT_MODE}. This processor will control the current
+ * activated magnifier on the display. If there is no magnifier activated, it controls
+ * full-screen magnifier by default.
+ * </p>
+ *
+ * <p>
+ * If the magnification config uses {@link FULLSCREEN_MODE}. This processor will control
+ * full-screen magnifier on the display.
+ * </p>
+ *
+ * <p>
+ * If the magnification config uses {@link WINDOW_MODE}. This processor will control
+ * the activated window magnifier on the display.
+ * </p>
+ *
* @see MagnificationController
* @see FullScreenMagnificationController
*/
@@ -35,53 +58,166 @@
}
/**
- * {@link FullScreenMagnificationController#getScale(int)}
+ * Gets the magnification config of the display.
+ *
+ * @param displayId The logical display id
+ * @return the magnification config
+ */
+ public @NonNull MagnificationConfig getMagnificationConfig(int displayId) {
+ final int mode = getControllingMode(displayId);
+ MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
+ if (mode == FULLSCREEN_MODE) {
+ final FullScreenMagnificationController fullScreenMagnificationController =
+ mController.getFullScreenMagnificationController();
+ builder.setMode(mode)
+ .setScale(fullScreenMagnificationController.getScale(displayId))
+ .setCenterX(fullScreenMagnificationController.getCenterX(displayId))
+ .setCenterY(fullScreenMagnificationController.getCenterY(displayId));
+ } else if (mode == WINDOW_MODE) {
+ final WindowMagnificationManager windowMagnificationManager =
+ mController.getWindowMagnificationMgr();
+ builder.setMode(mode)
+ .setScale(windowMagnificationManager.getScale(displayId))
+ .setCenterX(windowMagnificationManager.getCenterX(displayId))
+ .setCenterY(windowMagnificationManager.getCenterY(displayId));
+ }
+ return builder.build();
+ }
+
+ /**
+ * Sets the magnification config of the display. If animation is disabled, the transition
+ * is immediate.
+ *
+ * @param displayId The logical display id
+ * @param config The magnification config
+ * @param animate {@code true} to animate from the current config or
+ * {@code false} to set the config immediately
+ * @param id The ID of the service requesting the change
+ * @return {@code true} if the magnification spec changed, {@code false} if the spec did not
+ * change
+ */
+ public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config,
+ boolean animate, int id) {
+ int configMode = config.getMode();
+ if (configMode == DEFAULT_MODE) {
+ configMode = getControllingMode(displayId);
+ }
+ if (configMode == FULLSCREEN_MODE) {
+ return setScaleAndCenterForFullScreenMagnification(displayId, config.getScale(),
+ config.getCenterX(), config.getCenterY(),
+ animate, id);
+ } else if (configMode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
+ config.getScale(), config.getCenterX(), config.getCenterY());
+ }
+ return false;
+ }
+
+ /**
+ * Returns the magnification scale. If an animation is in progress,
+ * this reflects the end state of the animation.
+ *
+ * @param displayId The logical display id.
+ * @return the scale
*/
public float getScale(int displayId) {
- return mController.getFullScreenMagnificationController().getScale(displayId);
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ return mController.getFullScreenMagnificationController().getScale(displayId);
+ } else if (mode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().getScale(displayId);
+ }
+ return 0;
}
/**
- * {@link FullScreenMagnificationController#getCenterX(int)}
+ * Returns the magnification center in X coordinate of the controlling magnification mode.
+ * If the service can control magnification but fullscreen magnifier is not registered, it will
+ * register the magnifier for this call then unregister the magnifier finally to make the
+ * magnification center correct.
+ *
+ * @param displayId The logical display id
+ * @param canControlMagnification Whether the service can control magnification
+ * @return the X coordinate
*/
public float getCenterX(int displayId, boolean canControlMagnification) {
- boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
- canControlMagnification);
- try {
- return mController.getFullScreenMagnificationController().getCenterX(displayId);
- } finally {
- if (registeredJustForThisCall) {
- unregister(displayId);
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+ canControlMagnification);
+ try {
+ return mController.getFullScreenMagnificationController().getCenterX(displayId);
+ } finally {
+ if (registeredJustForThisCall) {
+ unregister(displayId);
+ }
}
+ } else if (mode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().getCenterX(displayId);
}
+ return 0;
}
/**
- * {@link FullScreenMagnificationController#getCenterY(int)}
+ * Returns the magnification center in Y coordinate of the controlling magnification mode.
+ * If the service can control magnification but fullscreen magnifier is not registered, it will
+ * register the magnifier for this call then unregister the magnifier finally to make the
+ * magnification center correct.
+ *
+ * @param displayId The logical display id
+ * @param canControlMagnification Whether the service can control magnification
+ * @return the Y coordinate
*/
public float getCenterY(int displayId, boolean canControlMagnification) {
- boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
- canControlMagnification);
- try {
- return mController.getFullScreenMagnificationController().getCenterY(displayId);
- } finally {
- if (registeredJustForThisCall) {
- unregister(displayId);
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+ canControlMagnification);
+ try {
+ return mController.getFullScreenMagnificationController().getCenterY(displayId);
+ } finally {
+ if (registeredJustForThisCall) {
+ unregister(displayId);
+ }
}
+ } else if (mode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().getCenterY(displayId);
}
+ return 0;
}
/**
- * {@link FullScreenMagnificationController#getMagnificationRegion(int, Region)}
+ * Return the magnification bounds of the current controlling magnification on the given
+ * display. If the magnifier is not enabled, it returns an empty region.
+ * If the service can control magnification but fullscreen magnifier is not registered, it will
+ * register the magnifier for this call then unregister the magnifier finally to make
+ * the magnification region correct.
+ *
+ * @param displayId The logical display id
+ * @param outRegion the region to populate
+ * @param canControlMagnification Whether the service can control magnification
+ * @return outRegion the magnification bounds of full-screen magnifier or the magnification
+ * source bounds of window magnifier
*/
public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
boolean canControlMagnification) {
- boolean registeredJustForThisCall = registerMagnificationIfNeeded(displayId,
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
+ } else if (mode == WINDOW_MODE) {
+ mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
+ outRegion);
+ }
+ return outRegion;
+ }
+
+ private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
+ boolean canControlMagnification) {
+ boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
canControlMagnification);
try {
mController.getFullScreenMagnificationController().getMagnificationRegion(displayId,
outRegion);
- return outRegion;
} finally {
if (registeredJustForThisCall) {
unregister(displayId);
@@ -89,67 +225,105 @@
}
}
- /**
- * {@link FullScreenMagnificationController#setScaleAndCenter(int, float, float, float, boolean,
- * int)}
- */
- public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY,
+ private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
+ float centerX, float centerY,
boolean animate, int id) {
if (!isRegistered(displayId)) {
register(displayId);
}
- return mController.getFullScreenMagnificationController().setScaleAndCenter(displayId,
+ return mController.getFullScreenMagnificationController().setScaleAndCenter(
+ displayId,
scale,
centerX, centerY, animate, id);
}
/**
- * {@link FullScreenMagnificationController#reset(int, boolean)}
+ * Resets the magnification on the given display. The reset mode could be full-screen or
+ * window if it is activated.
+ *
+ * @param displayId The logical display id.
+ * @param animate {@code true} to animate the transition, {@code false}
+ * to transition immediately
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
*/
public boolean reset(int displayId, boolean animate) {
- return mController.getFullScreenMagnificationController().reset(displayId, animate);
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ return mController.getFullScreenMagnificationController().reset(displayId, animate);
+ } else if (mode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().reset(displayId);
+ }
+ return false;
}
/**
* {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)}
*/
+ // TODO: support window magnification
public void resetAllIfNeeded(int connectionId) {
mController.getFullScreenMagnificationController().resetAllIfNeeded(connectionId);
}
/**
+ * {@link FullScreenMagnificationController#isMagnifying(int)}
+ * {@link WindowMagnificationManager#isWindowMagnifierEnabled(int)}
+ */
+ public boolean isMagnifying(int displayId) {
+ int mode = getControllingMode(displayId);
+ if (mode == FULLSCREEN_MODE) {
+ return mController.getFullScreenMagnificationController().isMagnifying(displayId);
+ } else if (mode == WINDOW_MODE) {
+ return mController.getWindowMagnificationMgr().isWindowMagnifierEnabled(displayId);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current controlling magnification mode on the given display.
+ * If there is no magnifier activated, it fallbacks to the last activated mode.
+ * And the last activated mode is {@link FULLSCREEN_MODE} by default.
+ *
+ * @param displayId The logical display id
+ */
+ public int getControllingMode(int displayId) {
+ if (mController.isActivated(displayId,
+ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)) {
+ return WINDOW_MODE;
+ } else if (mController.isActivated(displayId,
+ ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)) {
+ return FULLSCREEN_MODE;
+ } else {
+ return (mController.getLastActivatedMode() == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)
+ ? WINDOW_MODE
+ : FULLSCREEN_MODE;
+ }
+ }
+
+ private boolean registerDisplayMagnificationIfNeeded(int displayId,
+ boolean canControlMagnification) {
+ if (!isRegistered(displayId) && canControlMagnification) {
+ register(displayId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isRegistered(int displayId) {
+ return mController.getFullScreenMagnificationController().isRegistered(displayId);
+ }
+
+ /**
* {@link FullScreenMagnificationController#register(int)}
*/
- public void register(int displayId) {
+ private void register(int displayId) {
mController.getFullScreenMagnificationController().register(displayId);
}
/**
* {@link FullScreenMagnificationController#unregister(int)} (int)}
*/
- public void unregister(int displayId) {
+ private void unregister(int displayId) {
mController.getFullScreenMagnificationController().unregister(displayId);
}
-
- /**
- * {@link FullScreenMagnificationController#isMagnifying(int)}
- */
- public boolean isMagnifying(int displayId) {
- return mController.getFullScreenMagnificationController().isMagnifying(displayId);
- }
-
- /**
- * {@link FullScreenMagnificationController#isRegistered(int)}
- */
- public boolean isRegistered(int displayId) {
- return mController.getFullScreenMagnificationController().isRegistered(displayId);
- }
-
- private boolean registerMagnificationIfNeeded(int displayId, boolean canControlMagnification) {
- if (!isRegistered(displayId) && canControlMagnification) {
- register(displayId);
- return true;
- }
- return false;
- }
}
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 e1ec273..d34b4a9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
+import android.graphics.Region;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -271,9 +272,12 @@
* or {@link Float#NaN} to leave unchanged.
* @param centerY The screen-relative Y coordinate around which to center,
* or {@link Float#NaN} to leave unchanged.
+ * @return {@code true} if the magnification is enabled successfully.
*/
- void enableWindowMagnification(int displayId, float scale, float centerX, float centerY) {
- enableWindowMagnification(displayId, scale, centerX, centerY, STUB_ANIMATION_CALLBACK);
+ public boolean enableWindowMagnification(int displayId, float scale, float centerX,
+ float centerY) {
+ return enableWindowMagnification(displayId, scale, centerX, centerY,
+ STUB_ANIMATION_CALLBACK);
}
/**
@@ -287,25 +291,29 @@
* @param centerY The screen-relative Y coordinate around which to center,
* 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.
*/
- void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
- @Nullable MagnificationAnimationCallback animationCallback) {
+ public boolean enableWindowMagnification(int displayId, float scale, float centerX,
+ float centerY, @Nullable MagnificationAnimationCallback animationCallback) {
final boolean enabled;
+ boolean previousEnabled;
synchronized (mLock) {
if (mConnectionWrapper == null) {
- return;
+ return false;
}
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
magnifier = createWindowMagnifier(displayId);
}
+ previousEnabled = magnifier.mEnabled;
enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
animationCallback);
}
- if (enabled) {
+ if (enabled && !previousEnabled) {
mCallback.onWindowMagnificationActivationState(displayId, true);
}
+ return enabled;
}
/**
@@ -464,7 +472,7 @@
* @param displayId The logical display id
* @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled.
*/
- float getCenterX(int displayId) {
+ public float getCenterX(int displayId) {
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -480,7 +488,7 @@
* @param displayId The logical display id
* @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled.
*/
- float getCenterY(int displayId) {
+ public float getCenterY(int displayId) {
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -491,6 +499,42 @@
}
/**
+ * Populates magnified bounds on the screen. And the populated magnified bounds would be
+ * empty If window magnifier is not activated.
+ *
+ * @param displayId The logical display id.
+ * @param outRegion the region to populate
+ */
+ public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) {
+ synchronized (mLock) {
+ WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+ if (magnifier == null) {
+ outRegion.setEmpty();
+ } else {
+ outRegion.set(magnifier.mSourceBounds);
+ }
+ }
+ }
+
+ /**
+ * Resets the magnification scale and center.
+ *
+ * @param displayId The logical display id.
+ * @return {@code true} if the magnification spec changed, {@code false} if
+ * the spec did not change
+ */
+ public boolean reset(int displayId) {
+ synchronized (mLock) {
+ WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
+ if (magnifier == null) {
+ return false;
+ }
+ magnifier.reset();
+ return true;
+ }
+ }
+
+ /**
* Creates the windowMagnifier based on the specified display and stores it.
*
* @param displayId logical display id.
@@ -626,8 +670,9 @@
@GuardedBy("mLock")
boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback animationCallback) {
- if (mEnabled) {
- return false;
+ // Handle defaults. The scale may be NAN when just updating magnification center.
+ if (Float.isNaN(scale)) {
+ scale = getScale();
}
final float normScale = MagnificationScaleProvider.constrainScale(scale);
if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 5e2449d..422749e 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -210,7 +210,7 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter, null, FgThread.getHandler(),
- Context.RECEIVER_NOT_EXPORTED);
+ Context.RECEIVER_EXPORTED);
mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
com.android.internal.R.string.config_defaultAugmentedAutofillService);
diff --git a/services/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/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/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b019789..ac20a08 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -685,6 +685,7 @@
final UUID errorId = mTraceErrorLogger.generateErrorId();
if (mTraceErrorLogger.isAddErrorIdEnabled()) {
mTraceErrorLogger.addErrorIdToTrace("system_server", errorId);
+ mTraceErrorLogger.addSubjectToTrace(subject, errorId);
}
// Log the atom as early as possible since it is used as a mechanism to trigger
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 15871aa..02a16fc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15417,6 +15417,16 @@
}
@Override
+ public String getSwitchingFromUserMessage() {
+ return mUserController.getSwitchingFromSystemUserMessage();
+ }
+
+ @Override
+ public String getSwitchingToUserMessage() {
+ return mUserController.getSwitchingToSystemUserMessage();
+ }
+
+ @Override
public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
mUserController.setStopUserOnSwitch(value);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 17daa75..592abbb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -789,7 +789,7 @@
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
// exported, or are System level broadcasts
- if (!skip && !filter.exported && Process.SYSTEM_UID != r.callingUid
+ if (!skip && !filter.exported && !Process.isCoreUid(r.callingUid)
&& filter.receiverList.uid != r.callingUid) {
Slog.w(TAG, "Exported Denial: sending "
@@ -800,7 +800,7 @@
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
+ " not specifying RECEIVER_EXPORTED");
- skip = true;
+ // skip = true;
}
if (skip) {
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 4220506..18ad1f5 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -286,6 +286,7 @@
&& mService.mTraceErrorLogger.isAddErrorIdEnabled()) {
errorId = mService.mTraceErrorLogger.generateErrorId();
mService.mTraceErrorLogger.addErrorIdToTrace(mApp.processName, errorId);
+ mService.mTraceErrorLogger.addSubjectToTrace(annotation, errorId);
} else {
errorId = null;
}
diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java
index c658100..29a9b5c 100644
--- a/services/core/java/com/android/server/am/TraceErrorLogger.java
+++ b/services/core/java/com/android/server/am/TraceErrorLogger.java
@@ -54,4 +54,17 @@
COUNTER_PREFIX + processName + "#" + errorId.toString(),
PLACEHOLDER_VALUE);
}
+
+ /**
+ * Pushes a counter containing an ANR/Watchdog subject and a unique id so that the subject
+ * can be uniquely identified.
+ *
+ * @param subject The subject to include in the trace.
+ * @param errorId The unique id with which to tag the trace.
+ */
+ public void addSubjectToTrace(String subject, UUID errorId) {
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ String.format("Subject(for ErrorId %s):%s", errorId.toString(), subject),
+ PLACEHOLDER_VALUE);
+ }
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d52f52e..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
@@ -1809,7 +1809,8 @@
private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
- getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
+ getSwitchingFromSystemUserMessageUnchecked(),
+ getSwitchingToSystemUserMessageUnchecked());
}
private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
@@ -2622,18 +2623,40 @@
}
}
- private String getSwitchingFromSystemUserMessage() {
+ // Called by AMS, must check permission
+ String getSwitchingFromSystemUserMessage() {
+ checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()");
+
+ return getSwitchingFromSystemUserMessageUnchecked();
+ }
+
+ // Called by AMS, must check permission
+ String getSwitchingToSystemUserMessage() {
+ checkHasManageUsersPermission("getSwitchingToSystemUserMessage()");
+
+ return getSwitchingToSystemUserMessageUnchecked();
+ }
+
+ private String getSwitchingFromSystemUserMessageUnchecked() {
synchronized (mLock) {
return mSwitchingFromSystemUserMessage;
}
}
- private String getSwitchingToSystemUserMessage() {
+ private String getSwitchingToSystemUserMessageUnchecked() {
synchronized (mLock) {
return mSwitchingToSystemUserMessage;
}
}
+ private void checkHasManageUsersPermission(String operation) {
+ if (mInjector.checkCallingPermission(
+ android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to call " + operation);
+ }
+ }
+
void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (mLock) {
long token = proto.start(fieldId);
@@ -2706,6 +2729,12 @@
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
+ if (mSwitchingFromSystemUserMessage != null) {
+ pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
+ }
+ if (mSwitchingToSystemUserMessage != null) {
+ pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
+ }
}
}
diff --git a/services/core/java/com/android/server/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 e4bed3d..1c62699 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -110,6 +110,7 @@
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -239,6 +240,10 @@
public final SparseArray<CallbackRecord> mCallbacks =
new SparseArray<CallbackRecord>();
+ /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */
+ final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController =
+ new SparseArray<>();
+
// List of all currently registered display adapters.
private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
@@ -1117,7 +1122,8 @@
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
IMediaProjection projection, int callingUid, String packageName, Surface surface,
- int flags, VirtualDisplayConfig virtualDisplayConfig) {
+ int flags, VirtualDisplayConfig virtualDisplayConfig,
+ DisplayWindowPolicyController controller) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
Slog.w(TAG, "Rejecting request to create private virtual display "
@@ -1145,6 +1151,9 @@
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display != null) {
+ if (controller != null) {
+ mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller);
+ }
return display.getDisplayIdLocked();
}
@@ -1188,6 +1197,10 @@
DisplayDevice device =
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
if (device != null) {
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
+ if (display != null) {
+ mDisplayWindowPolicyController.delete(display.getDisplayIdLocked());
+ }
// TODO: multi-display - handle virtual displays the same as other display adapters.
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
@@ -2139,6 +2152,15 @@
}
pw.println();
mPersistentDataStore.dump(pw);
+
+ final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size();
+ pw.println();
+ pw.println("Display Window Policy Controllers: size="
+ + displayWindowPolicyControllerCount);
+ for (int i = 0; i < displayWindowPolicyControllerCount; i++) {
+ pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":");
+ mDisplayWindowPolicyController.valueAt(i).dump(" ", pw);
+ }
}
pw.println();
mDisplayModeDirector.dump(pw);
@@ -2704,6 +2726,13 @@
@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");
@@ -2803,7 +2832,7 @@
final long token = Binder.clearCallingIdentity();
try {
return createVirtualDisplayInternal(callback, projection, callingUid, packageName,
- surface, flags, virtualDisplayConfig);
+ surface, flags, virtualDisplayConfig, controller);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3624,6 +3653,13 @@
public void onEarlyInteractivityChange(boolean interactive) {
mLogicalDisplayMapper.onEarlyInteractivityChange(interactive);
}
+
+ @Override
+ public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) {
+ synchronized (mSyncRoot) {
+ return mDisplayWindowPolicyController.get(displayId);
+ }
+ }
}
class DesiredDisplayModeSpecsObserver
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/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..c08d343 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -408,6 +408,9 @@
private PowerManagerWrapper mPowerManager;
@Nullable
+ private PowerManagerInternalWrapper mPowerManagerInternal;
+
+ @Nullable
private Looper mIoLooper;
@HdmiControlManager.HdmiCecVersion
@@ -734,6 +737,7 @@
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 +762,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 +3252,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/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index ffeaad1..457c2fd 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -154,10 +154,10 @@
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
- boolean isSuccess = updater.setLocales(locales).commit();
+ boolean isConfigChanged = updater.setLocales(locales).commit();
//We want to send the broadcasts only if config was actually updated on commit.
- if (isSuccess) {
+ if (isConfigChanged) {
notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
notifyRegisteredReceivers(appPackageName, userId, locales);
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/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 53c2802..6f54625 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -500,16 +500,10 @@
forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
}
}
- final StateProvider stateProvider = new StateProvider() {
- // TODO: This lock and its handling should be owned by AppsFilter
- private final Object mLock = new Object();
-
- @Override
- public void runWithState(CurrentStateCallback command) {
- synchronized (mLock) {
- command.currentState(pms.getPackageStates(),
- injector.getUserManagerInternal().getUserInfos());
- }
+ final StateProvider stateProvider = command -> {
+ synchronized (injector.getLock()) {
+ command.currentState(injector.getSettings().getPackagesLocked().untrackedStorage(),
+ injector.getUserManagerInternal().getUserInfos());
}
};
AppsFilter appsFilter = new AppsFilter(stateProvider, featureConfig,
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 861fec7..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);
@@ -2050,6 +2053,7 @@
}
}
+ final String packageName = pkg.getPackageName();
for (Map.Entry<String, String> entry : fsverityCandidates.entrySet()) {
final String filePath = entry.getKey();
final String signaturePath = entry.getValue();
@@ -2077,10 +2081,13 @@
try {
// A file may already have fs-verity, e.g. when reused during a split
// install. If the measurement succeeds, no need to attempt to set up.
- mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+ mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+ rootHash);
} catch (Installer.InstallerException e) {
- mPm.mInstaller.installApkVerity(filePath, fd, result.getContentSize());
- mPm.mInstaller.assertFsverityRootHashMatches(filePath, rootHash);
+ mPm.mInstaller.installApkVerity(packageName, filePath, fd,
+ result.getContentSize());
+ mPm.mInstaller.assertFsverityRootHashMatches(packageName, filePath,
+ rootHash);
}
} finally {
IoUtils.closeQuietly(fd);
@@ -3125,10 +3132,12 @@
true, true, pkgList, uidArray, null);
}
} else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
+ // No need to kill consumers if it's installation of new version static shared lib.
+ final boolean dontKillApp = !update && res.mPkg.getStaticSharedLibName() != null;
for (int i = 0; i < res.mLibraryConsumers.size(); i++) {
AndroidPackage pkg = res.mLibraryConsumers.get(i);
// send broadcast that all consumers of the static shared library have changed
- mPm.sendPackageChangedBroadcast(pkg.getPackageName(), false /* dontKillApp */,
+ mPm.sendPackageChangedBroadcast(pkg.getPackageName(), dontKillApp,
new ArrayList<>(Collections.singletonList(pkg.getPackageName())),
pkg.getUid(), null);
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index c8bd2c0..a380344 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -497,7 +497,7 @@
*
* @throws InstallerException if {@code dexopt} fails.
*/
- public boolean dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
+ public boolean dexopt(String apkPath, int uid, String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
String compilerFilter, @Nullable String volumeUuid, @Nullable String classLoaderContext,
@Nullable String seInfo, boolean downgrade, int targetSdkVersion,
@@ -585,11 +585,14 @@
}
}
- public void rmPackageDir(String packageDir) throws InstallerException {
+ /**
+ * Remove a directory belonging to a package.
+ */
+ public void rmPackageDir(String packageName, String packageDir) throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(packageDir);
try {
- mInstalld.rmPackageDir(packageDir);
+ mInstalld.rmPackageDir(packageName, packageDir);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -662,35 +665,44 @@
}
}
- public void createOatDir(String oatDir, String dexInstructionSet)
+ /**
+ * Creates an oat dir for given package and instruction set.
+ */
+ public void createOatDir(String packageName, String oatDir, String dexInstructionSet)
throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.createOatDir(oatDir, dexInstructionSet);
+ mInstalld.createOatDir(packageName, oatDir, dexInstructionSet);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public void linkFile(String relativePath, String fromBase, String toBase)
+ /**
+ * Creates a hardlink for a path.
+ */
+ public void linkFile(String packageName, String relativePath, String fromBase, String toBase)
throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(fromBase);
BlockGuard.getVmPolicy().onPathAccess(toBase);
try {
- mInstalld.linkFile(relativePath, fromBase, toBase);
+ mInstalld.linkFile(packageName, relativePath, fromBase, toBase);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public void moveAb(String apkPath, String instructionSet, String outputPath)
+ /**
+ * Moves oat/vdex/art from "B" set defined by ro.boot.slot_suffix to the default set.
+ */
+ public void moveAb(String packageName, String apkPath, String instructionSet, String outputPath)
throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(apkPath);
BlockGuard.getVmPolicy().onPathAccess(outputPath);
try {
- mInstalld.moveAb(apkPath, instructionSet, outputPath);
+ mInstalld.moveAb(packageName, apkPath, instructionSet, outputPath);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -700,35 +712,41 @@
* Deletes the optimized artifacts generated by ART and returns the number
* of freed bytes.
*/
- public long deleteOdex(String apkPath, String instructionSet, String outputPath)
- throws InstallerException {
+ public long deleteOdex(String packageName, String apkPath, String instructionSet,
+ String outputPath) throws InstallerException {
if (!checkBeforeRemote()) return -1;
BlockGuard.getVmPolicy().onPathAccess(apkPath);
BlockGuard.getVmPolicy().onPathAccess(outputPath);
try {
- return mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
+ return mInstalld.deleteOdex(packageName, apkPath, instructionSet, outputPath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
- throws InstallerException {
+ /**
+ * Enables legacy apk-verity for an apk.
+ */
+ public void installApkVerity(String packageName, String filePath, FileDescriptor verityInput,
+ int contentSize) throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(filePath);
try {
- mInstalld.installApkVerity(filePath, verityInput, contentSize);
+ mInstalld.installApkVerity(packageName, filePath, verityInput, contentSize);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
- throws InstallerException {
+ /**
+ * Checks if provided hash matches the file's fs-verity merkle tree root hash.
+ */
+ public void assertFsverityRootHashMatches(String packageName, String filePath,
+ @NonNull byte[] expectedHash) throws InstallerException {
if (!checkBeforeRemote()) return;
BlockGuard.getVmPolicy().onPathAccess(filePath);
try {
- mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
+ mInstalld.assertFsverityRootHashMatches(packageName, filePath, expectedHash);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 9e6f4f7..c125fe1 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -411,6 +411,7 @@
final List<String> paths =
AndroidPackageUtils.getAllCodePathsExcludingResourceOnly(pkg);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+ final String packageName = pkg.getPackageName();
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (String path : paths) {
String oatDir = PackageDexOptimizer.getOatDir(
@@ -420,7 +421,7 @@
packagePaths++;
try {
- installer.moveAb(path, dexCodeInstructionSet, oatDir);
+ installer.moveAb(packageName, path, dexCodeInstructionSet, oatDir);
pathsSuccessful++;
} catch (InstallerException e) {
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index de1c2ad..7c8515b 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 {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 803a283..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(
@@ -2402,6 +2418,7 @@
try {
final List<File> fromFiles = mResolvedInheritedFiles;
final File toDir = stageDir;
+ final String tempPackageName = toDir.getName();
if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
@@ -2411,7 +2428,7 @@
if (isLinkPossible(fromFiles, toDir)) {
if (!mResolvedInstructionSets.isEmpty()) {
final File oatDir = new File(toDir, "oat");
- createOatDirs(mResolvedInstructionSets, oatDir);
+ createOatDirs(tempPackageName, mResolvedInstructionSets, oatDir);
}
// pre-create lib dirs for linking if necessary
if (!mResolvedNativeLibPaths.isEmpty()) {
@@ -2434,7 +2451,7 @@
new File(libDir, archDirPath));
}
}
- linkFiles(fromFiles, toDir, mInheritedFilesBase);
+ linkFiles(tempPackageName, fromFiles, toDir, mInheritedFilesBase);
} else {
// TODO: this should delegate to DCS so the system process
// avoids holding open FDs into containers.
@@ -3529,18 +3546,19 @@
throw new IOException("File: " + pathStr + " outside base: " + baseStr);
}
- private void createOatDirs(List<String> instructionSets, File fromDir)
+ private void createOatDirs(String packageName, List<String> instructionSets, File fromDir)
throws PackageManagerException {
for (String instructionSet : instructionSets) {
try {
- mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
+ mInstaller.createOatDir(packageName, fromDir.getAbsolutePath(), instructionSet);
} catch (InstallerException e) {
throw PackageManagerException.from(e);
}
}
}
- private void linkFile(String relativePath, String fromBase, String toBase) throws IOException {
+ private void linkFile(String packageName, String relativePath, String fromBase, String toBase)
+ throws IOException {
try {
// Try
final IncrementalFileStorages incrementalFileStorages = getIncrementalFileStorages();
@@ -3548,21 +3566,21 @@
fromBase, toBase)) {
return;
}
- mInstaller.linkFile(relativePath, fromBase, toBase);
+ mInstaller.linkFile(packageName, relativePath, fromBase, toBase);
} catch (InstallerException | IOException e) {
throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
+ fromBase + ", " + toBase + ")", e);
}
}
- private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
+ private void linkFiles(String packageName, List<File> fromFiles, File toDir, File fromDir)
throws IOException {
for (File fromFile : fromFiles) {
final String relativePath = getRelativePath(fromFile, fromDir);
final String fromBase = fromDir.getAbsolutePath();
final String toBase = toDir.getAbsolutePath();
- linkFile(relativePath, fromBase, toBase);
+ linkFile(packageName, relativePath, fromBase, toBase);
}
Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
@@ -4299,7 +4317,8 @@
incrementalFileStorages.cleanUpAndMarkComplete();
}
if (stageDir != null) {
- mInstaller.rmPackageDir(stageDir.getAbsolutePath());
+ final String tempPackageName = stageDir.getName();
+ mInstaller.rmPackageDir(tempPackageName, stageDir.getAbsolutePath());
}
} catch (InstallerException ignored) {
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 747bbfa5..5526990 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
@@ -9376,7 +9414,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..1848ef5 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;
@@ -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/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 3a2ac1c..48b893b 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -94,9 +94,10 @@
}
}
- mInstaller.rmPackageDir(codePath.getAbsolutePath());
+ final String packageName = codePath.getName();
+ mInstaller.rmPackageDir(packageName, codePath.getAbsolutePath());
if (needRemoveParent) {
- mInstaller.rmPackageDir(codePathParent.getAbsolutePath());
+ mInstaller.rmPackageDir(packageName, codePathParent.getAbsolutePath());
removeCachedResult(codePathParent);
}
} catch (Installer.InstallerException e) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
index 6cc94ce..9b08ef9 100644
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ b/services/core/java/com/android/server/pm/ScanPackageHelper.java
@@ -894,14 +894,15 @@
* Returns if forced apk verification can be skipped for the whole package, including splits.
*/
private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
- if (!canSkipForcedApkVerification(pkg.getBaseApkPath())) {
+ final String packageName = pkg.getPackageName();
+ if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
return false;
}
// TODO: Allow base and splits to be verified individually.
String[] splitCodePaths = pkg.getSplitCodePaths();
if (!ArrayUtils.isEmpty(splitCodePaths)) {
for (int i = 0; i < splitCodePaths.length; i++) {
- if (!canSkipForcedApkVerification(splitCodePaths[i])) {
+ if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
return false;
}
}
@@ -914,7 +915,7 @@
* whether the apk contains signed root hash. Note that the signer's certificate still needs to
* match one in a trusted source, and should be done separately.
*/
- private boolean canSkipForcedApkVerification(String apkPath) {
+ private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
return VerityUtils.hasFsverity(apkPath);
}
@@ -926,7 +927,8 @@
}
synchronized (mPm.mInstallLock) {
// Returns whether the observed root hash matches what kernel has.
- mPm.mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved);
+ mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
+ rootHashObserved);
return true;
}
} catch (Installer.InstallerException | IOException | DigestException
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4a41047..1f5d79c 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;
@@ -437,7 +438,7 @@
public void forceCurrent() {
sdkVersion = Build.VERSION.SDK_INT;
databaseVersion = CURRENT_DATABASE_VERSION;
- fingerprint = Build.FINGERPRINT;
+ fingerprint = PackagePartitions.FINGERPRINT;
}
}
@@ -5357,7 +5358,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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 40d8845..74529c5 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,17 @@
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);
+ }
+
@Override
public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
return canAddMoreProfilesToUser(UserManager.USER_TYPE_PROFILE_MANAGED, userId,
@@ -3716,7 +3728,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 +4859,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 +4890,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 +4935,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/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/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 5820489..5371454 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -1043,10 +1043,12 @@
public long deleteOptimizedFiles(ArtPackageInfo packageInfo) {
long freedBytes = 0;
boolean hadErrors = false;
+ final String packageName = packageInfo.getPackageName();
for (String codePath : packageInfo.getCodePaths()) {
for (String isa : packageInfo.getInstructionSets()) {
try {
- freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir());
+ freedBytes += mInstaller.deleteOdex(packageName, codePath, isa,
+ packageInfo.getOatDir());
} catch (InstallerException e) {
Log.e(TAG, "Failed deleting oat files for " + codePath, e);
hadErrors = true;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2369c5e..131e587 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -332,6 +332,10 @@
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_PHYSICAL_EMULATION);
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
/**
* Keyguard stuff
@@ -1115,21 +1119,21 @@
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power - Long Press - Global Actions");
showGlobalActions();
break;
case LONG_PRESS_POWER_SHUT_OFF:
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power - Long Press - Shut Off");
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power - Long Press - Go To Voice Assist");
// Some devices allow the voice assistant intent during setup (and use that intent
// to launch something else, like Settings). So we explicitly allow that via the
@@ -1153,7 +1157,7 @@
break;
case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power - Very Long Press - Show Global Actions");
showGlobalActions();
break;
@@ -2098,7 +2102,8 @@
mPowerKeyHandled = true;
break;
case POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS:
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false,
+ performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
"Power + Volume Up - Global Actions");
showGlobalActions();
mPowerKeyHandled = true;
@@ -5291,7 +5296,7 @@
return false;
}
- mVibrator.vibrate(uid, packageName, effect, reason, TOUCH_VIBRATION_ATTRIBUTES);
+ mVibrator.vibrate(uid, packageName, effect, reason, getVibrationAttributes(effectId));
return true;
}
@@ -5320,6 +5325,7 @@
case HapticFeedbackConstants.GESTURE_START:
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
case HapticFeedbackConstants.EDGE_SQUEEZE:
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
case HapticFeedbackConstants.REJECT:
@@ -5358,6 +5364,22 @@
}
}
+ private VibrationAttributes getVibrationAttributes(int effectId) {
+ switch (effectId) {
+ case HapticFeedbackConstants.EDGE_SQUEEZE:
+ case HapticFeedbackConstants.EDGE_RELEASE:
+ return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
+ case HapticFeedbackConstants.ASSISTANT_BUTTON:
+ case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
+ case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+ case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+ return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
+ default:
+ return TOUCH_VIBRATION_ATTRIBUTES;
+ }
+ }
+
@Override
public void keepScreenOnStartedLw() {
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 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/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 6d0f08d..70a804b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -110,8 +110,8 @@
private static final VibrationEffect CHARGING_VIBRATION_EFFECT =
VibrationEffect.createWaveform(CHARGING_VIBRATION_TIME, CHARGING_VIBRATION_AMPLITUDE,
-1);
- private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
- VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+ private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
private final Object mLock = new Object();
@@ -807,7 +807,7 @@
final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
if (vibrate) {
- mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, TOUCH_VIBRATION_ATTRIBUTES);
+ mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
}
// play sound
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 7cdae58..3182290 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -56,6 +56,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
+import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -3407,7 +3408,7 @@
pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag,
metricsState.isTelephonyDetectionSupported(),
metricsState.isGeoDetectionSupported(),
- metricsState.isUserLocationEnabled(),
+ metricsState.getUserLocationEnabledSetting(),
metricsState.getAutoDetectionEnabledSetting(),
metricsState.getGeoDetectionEnabledSetting(),
convertToMetricsDetectionMode(metricsState.getDetectionMode()),
@@ -3434,7 +3435,7 @@
case MetricsTimeZoneDetectorState.DETECTION_MODE_TELEPHONY:
return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
default:
- throw new IllegalArgumentException("" + detectionMode);
+ return TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
}
}
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/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 22814b3..1a5945e 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -39,21 +39,21 @@
private final boolean mTelephonyDetectionSupported;
private final boolean mGeoDetectionSupported;
- private final boolean mAutoDetectionEnabled;
+ private final boolean mAutoDetectionEnabledSetting;
private final @UserIdInt int mUserId;
private final boolean mUserConfigAllowed;
- private final boolean mLocationEnabled;
- private final boolean mGeoDetectionEnabled;
+ private final boolean mLocationEnabledSetting;
+ private final boolean mGeoDetectionEnabledSetting;
private ConfigurationInternal(Builder builder) {
mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
mGeoDetectionSupported = builder.mGeoDetectionSupported;
- mAutoDetectionEnabled = builder.mAutoDetectionEnabled;
+ mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
mUserId = builder.mUserId;
mUserConfigAllowed = builder.mUserConfigAllowed;
- mLocationEnabled = builder.mLocationEnabled;
- mGeoDetectionEnabled = builder.mGeoDetectionEnabled;
+ mLocationEnabledSetting = builder.mLocationEnabledSetting;
+ mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
}
/** Returns true if the device supports any form of auto time zone detection. */
@@ -73,7 +73,7 @@
/** Returns the value of the auto time zone detection enabled setting. */
public boolean getAutoDetectionEnabledSetting() {
- return mAutoDetectionEnabled;
+ return mAutoDetectionEnabledSetting;
}
/**
@@ -81,7 +81,7 @@
* from the raw setting value.
*/
public boolean getAutoDetectionEnabledBehavior() {
- return isAutoDetectionSupported() && mAutoDetectionEnabled;
+ return isAutoDetectionSupported() && mAutoDetectionEnabledSetting;
}
/** Returns the ID of the user this configuration is associated with. */
@@ -101,13 +101,13 @@
}
/** Returns true if user's location can be used generally. */
- public boolean isLocationEnabled() {
- return mLocationEnabled;
+ public boolean getLocationEnabledSetting() {
+ return mLocationEnabledSetting;
}
/** Returns the value of the geolocation time zone detection enabled setting. */
public boolean getGeoDetectionEnabledSetting() {
- return mGeoDetectionEnabled;
+ return mGeoDetectionEnabledSetting;
}
/**
@@ -117,7 +117,7 @@
public boolean getGeoDetectionEnabledBehavior() {
return getAutoDetectionEnabledBehavior()
&& isGeoDetectionSupported()
- && isLocationEnabled()
+ && getLocationEnabledSetting()
&& getGeoDetectionEnabledSetting();
}
@@ -154,7 +154,7 @@
final int configureGeolocationDetectionEnabledCapability;
if (!deviceHasLocationTimeZoneDetection) {
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_SUPPORTED;
- } else if (!mAutoDetectionEnabled || !isLocationEnabled()) {
+ } else if (!mAutoDetectionEnabledSetting || !getLocationEnabledSetting()) {
configureGeolocationDetectionEnabledCapability = CAPABILITY_NOT_APPLICABLE;
} else {
configureGeolocationDetectionEnabledCapability = CAPABILITY_POSSESSED;
@@ -195,10 +195,10 @@
public ConfigurationInternal merge(TimeZoneConfiguration newConfiguration) {
Builder builder = new Builder(this);
if (newConfiguration.hasIsAutoDetectionEnabled()) {
- builder.setAutoDetectionEnabled(newConfiguration.isAutoDetectionEnabled());
+ builder.setAutoDetectionEnabledSetting(newConfiguration.isAutoDetectionEnabled());
}
if (newConfiguration.hasIsGeoDetectionEnabled()) {
- builder.setGeoDetectionEnabled(newConfiguration.isGeoDetectionEnabled());
+ builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled());
}
return builder.build();
}
@@ -216,16 +216,16 @@
&& mUserConfigAllowed == that.mUserConfigAllowed
&& mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
&& mGeoDetectionSupported == that.mGeoDetectionSupported
- && mAutoDetectionEnabled == that.mAutoDetectionEnabled
- && mLocationEnabled == that.mLocationEnabled
- && mGeoDetectionEnabled == that.mGeoDetectionEnabled;
+ && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
+ && mLocationEnabledSetting == that.mLocationEnabledSetting
+ && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
}
@Override
public int hashCode() {
return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
- mGeoDetectionSupported, mAutoDetectionEnabled, mLocationEnabled,
- mGeoDetectionEnabled);
+ mGeoDetectionSupported, mAutoDetectionEnabledSetting, mLocationEnabledSetting,
+ mGeoDetectionEnabledSetting);
}
@Override
@@ -235,9 +235,9 @@
+ ", mUserConfigAllowed=" + mUserConfigAllowed
+ ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
+ ", mGeoDetectionSupported=" + mGeoDetectionSupported
- + ", mAutoDetectionEnabled=" + mAutoDetectionEnabled
- + ", mLocationEnabled=" + mLocationEnabled
- + ", mGeoDetectionEnabled=" + mGeoDetectionEnabled
+ + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
+ + ", mLocationEnabledSetting=" + mLocationEnabledSetting
+ + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
+ '}';
}
@@ -251,9 +251,9 @@
private boolean mUserConfigAllowed;
private boolean mTelephonyDetectionSupported;
private boolean mGeoDetectionSupported;
- private boolean mAutoDetectionEnabled;
- private boolean mLocationEnabled;
- private boolean mGeoDetectionEnabled;
+ private boolean mAutoDetectionEnabledSetting;
+ private boolean mLocationEnabledSetting;
+ private boolean mGeoDetectionEnabledSetting;
/**
* Creates a new Builder with only the userId set.
@@ -270,9 +270,9 @@
this.mUserConfigAllowed = toCopy.mUserConfigAllowed;
this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
- this.mAutoDetectionEnabled = toCopy.mAutoDetectionEnabled;
- this.mLocationEnabled = toCopy.mLocationEnabled;
- this.mGeoDetectionEnabled = toCopy.mGeoDetectionEnabled;
+ this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
+ this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
+ this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
}
/**
@@ -302,24 +302,24 @@
/**
* Sets the value of the automatic time zone detection enabled setting for this device.
*/
- public Builder setAutoDetectionEnabled(boolean enabled) {
- mAutoDetectionEnabled = enabled;
+ public Builder setAutoDetectionEnabledSetting(boolean enabled) {
+ mAutoDetectionEnabledSetting = enabled;
return this;
}
/**
* Sets the value of the location mode setting for this user.
*/
- public Builder setLocationEnabled(boolean enabled) {
- mLocationEnabled = enabled;
+ public Builder setLocationEnabledSetting(boolean enabled) {
+ mLocationEnabledSetting = enabled;
return this;
}
/**
* Sets the value of the geolocation time zone detection setting for this user.
*/
- public Builder setGeoDetectionEnabled(boolean enabled) {
- mGeoDetectionEnabled = enabled;
+ public Builder setGeoDetectionEnabledSetting(boolean enabled) {
+ mGeoDetectionEnabledSetting = enabled;
return this;
}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 9eb6a45..f8ba0d2 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -56,16 +56,11 @@
public static final @DetectionMode int DETECTION_MODE_GEO = 1;
public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 2;
- @NonNull
- private final ConfigurationInternal mConfigurationInternal;
- @NonNull
+ @NonNull private final ConfigurationInternal mConfigurationInternal;
private final int mDeviceTimeZoneIdOrdinal;
- @Nullable
- private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
- @Nullable
- private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
- @Nullable
- private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
+ @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
+ @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
+ @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
private MetricsTimeZoneDetectorState(
@NonNull ConfigurationInternal configurationInternal,
@@ -117,8 +112,8 @@
}
/** Returns true if user's location can be used generally. */
- public boolean isUserLocationEnabled() {
- return mConfigurationInternal.isLocationEnabled();
+ public boolean getUserLocationEnabledSetting() {
+ return mConfigurationInternal.getLocationEnabledSetting();
}
/** Returns the value of the geolocation time zone detection enabled setting. */
@@ -149,7 +144,6 @@
* Returns the ordinal for the device's currently set time zone ID.
* See {@link MetricsTimeZoneDetectorState} for information about ordinals.
*/
- @NonNull
public int getDeviceTimeZoneIdOrdinal() {
return mDeviceTimeZoneIdOrdinal;
}
@@ -281,6 +275,7 @@
return new MetricsTimeZoneSuggestion(null);
}
+ @NonNull
public static MetricsTimeZoneSuggestion createCertain(
@NonNull int[] zoneIdOrdinals) {
return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 6e63f59..be4b6bd 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -281,8 +281,8 @@
// later releases that start to support geo detection on the same hardware.
if (!getGeoDetectionSettingEnabledOverride().isPresent()
&& isGeoTimeZoneDetectionFeatureSupported()) {
- final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
- setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled);
+ final boolean geoDetectionEnabledSetting = configuration.isGeoDetectionEnabled();
+ setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
}
}
}
@@ -294,10 +294,10 @@
.setTelephonyDetectionFeatureSupported(
isTelephonyTimeZoneDetectionFeatureSupported())
.setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
- .setAutoDetectionEnabled(isAutoDetectionEnabled())
+ .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
.setUserConfigAllowed(isUserConfigAllowed(userId))
- .setLocationEnabled(isLocationEnabled(userId))
- .setGeoDetectionEnabled(isGeoDetectionEnabled(userId))
+ .setLocationEnabledSetting(getLocationEnabledSetting(userId))
+ .setGeoDetectionEnabledSetting(getGeoDetectionEnabledSetting(userId))
.build();
}
@@ -306,12 +306,12 @@
// a ConfigurationChangeListener callback triggering due to ContentObserver's still
// triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
// for stable behavior during tests.
- if (isAutoDetectionEnabled() != enabled) {
+ if (getAutoDetectionEnabledSetting() != enabled) {
Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME_ZONE, enabled ? 1 : 0);
}
}
- private boolean isLocationEnabled(@UserIdInt int userId) {
+ private boolean getLocationEnabledSetting(@UserIdInt int userId) {
return mLocationManager.isLocationEnabledForUser(UserHandle.of(userId));
}
@@ -320,11 +320,11 @@
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
}
- private boolean isAutoDetectionEnabled() {
+ private boolean getAutoDetectionEnabledSetting() {
return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0;
}
- private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
+ private boolean getGeoDetectionEnabledSetting(@UserIdInt int userId) {
// We may never use this, but it gives us a way to force location-based time zone detection
// on/off for testers (but only where their other settings would allow them to turn it on
// for themselves).
@@ -339,9 +339,9 @@
(geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
}
- private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) {
+ private void setGeoDetectionEnabledSettingIfRequired(@UserIdInt int userId, boolean enabled) {
// See comment in setAutoDetectionEnabledIfRequired. http://b/171953500
- if (isGeoDetectionEnabled(userId) != enabled) {
+ if (getGeoDetectionEnabledSetting(userId) != enabled) {
Settings.Secure.putIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
enabled ? 1 : 0, userId);
}
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..d0c6d13 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -47,6 +47,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 +612,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 +632,7 @@
}
// Send a null token immediately while reconnecting.
if (serviceState.mReconnecting) {
- sendSessionTokenToClientLocked(client, iAppServiceId, null, seq);
+ sendSessionTokenToClientLocked(client, iAppServiceId, null, null, seq);
return;
}
@@ -780,9 +781,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 +798,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 +887,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 +1140,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 +1160,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();
}
}
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 bc21f1b..71a6b22 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -16,6 +16,14 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IUidObserver;
@@ -90,6 +98,8 @@
@GuardedBy("mLock")
private int mHapticFeedbackIntensity;
@GuardedBy("mLock")
+ private int mHardwareFeedbackIntensity;
+ @GuardedBy("mLock")
private int mNotificationIntensity;
@GuardedBy("mLock")
private int mRingIntensity;
@@ -232,17 +242,20 @@
* @return The vibration intensity, one of Vibrator.VIBRATION_INTENSITY_*
*/
public int getDefaultIntensity(int usageHint) {
- if (isAlarm(usageHint)) {
+ if (usageHint == USAGE_ALARM) {
return Vibrator.VIBRATION_INTENSITY_HIGH;
}
synchronized (mLock) {
if (mVibrator != null) {
- if (isRingtone(usageHint)) {
- return mVibrator.getDefaultRingVibrationIntensity();
- } else if (isNotification(usageHint)) {
- return mVibrator.getDefaultNotificationVibrationIntensity();
- } else if (isHapticFeedback(usageHint)) {
- return mVibrator.getDefaultHapticFeedbackIntensity();
+ switch (usageHint) {
+ case USAGE_RINGTONE:
+ return mVibrator.getDefaultRingVibrationIntensity();
+ case USAGE_NOTIFICATION:
+ return mVibrator.getDefaultNotificationVibrationIntensity();
+ case USAGE_TOUCH:
+ case USAGE_HARDWARE_FEEDBACK:
+ case USAGE_PHYSICAL_EMULATION:
+ return mVibrator.getDefaultHapticFeedbackIntensity();
}
}
}
@@ -257,16 +270,20 @@
*/
public int getCurrentIntensity(int usageHint) {
synchronized (mLock) {
- if (isRingtone(usageHint)) {
- return mRingIntensity;
- } else if (isNotification(usageHint)) {
- return mNotificationIntensity;
- } else if (isHapticFeedback(usageHint)) {
- return mHapticFeedbackIntensity;
- } else if (isAlarm(usageHint)) {
- return Vibrator.VIBRATION_INTENSITY_HIGH;
- } else {
- return Vibrator.VIBRATION_INTENSITY_MEDIUM;
+ switch (usageHint) {
+ case USAGE_RINGTONE:
+ return mRingIntensity;
+ case USAGE_NOTIFICATION:
+ return mNotificationIntensity;
+ case USAGE_TOUCH:
+ return mHapticFeedbackIntensity;
+ case USAGE_HARDWARE_FEEDBACK:
+ case USAGE_PHYSICAL_EMULATION:
+ return mHardwareFeedbackIntensity;
+ case USAGE_ALARM:
+ return Vibrator.VIBRATION_INTENSITY_HIGH;
+ default:
+ return Vibrator.VIBRATION_INTENSITY_MEDIUM;
}
}
}
@@ -289,7 +306,7 @@
* for ringtone usage only. All other usages are allowed independently of ringer mode.
*/
public boolean shouldVibrateForRingerMode(int usageHint) {
- if (!isRingtone(usageHint)) {
+ if (usageHint != USAGE_RINGTONE) {
return true;
}
synchronized (mLock) {
@@ -324,8 +341,10 @@
* {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST} usages are allowed to vibrate.
*/
public boolean shouldVibrateForPowerMode(int usageHint) {
- return !mLowPowerMode || isRingtone(usageHint) || isAlarm(usageHint)
- || usageHint == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+ synchronized (mLock) {
+ return !mLowPowerMode || usageHint == USAGE_RINGTONE || usageHint == USAGE_ALARM
+ || usageHint == USAGE_COMMUNICATION_REQUEST;
+ }
}
/** Return {@code true} if input devices should vibrate instead of this device. */
@@ -338,22 +357,6 @@
return mZenMode != Settings.Global.ZEN_MODE_OFF;
}
- private static boolean isNotification(int usageHint) {
- return usageHint == VibrationAttributes.USAGE_NOTIFICATION;
- }
-
- private static boolean isRingtone(int usageHint) {
- return usageHint == VibrationAttributes.USAGE_RINGTONE;
- }
-
- private static boolean isHapticFeedback(int usageHint) {
- return usageHint == VibrationAttributes.USAGE_TOUCH;
- }
-
- private static boolean isAlarm(int usageHint) {
- return usageHint == VibrationAttributes.USAGE_ALARM;
- }
-
private static boolean isClassAlarm(int usageHint) {
return (usageHint & VibrationAttributes.USAGE_CLASS_MASK)
== VibrationAttributes.USAGE_CLASS_ALARM;
@@ -365,18 +368,35 @@
mVibrateWhenRinging = getSystemSetting(Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
mApplyRampingRinger = getGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0) != 0;
mHapticFeedbackIntensity = getSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+ getDefaultIntensity(USAGE_TOUCH));
+ mHardwareFeedbackIntensity = getSystemSetting(
+ Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+ getHardwareFeedbackIntensityWhenSettingIsMissing(mHapticFeedbackIntensity));
mNotificationIntensity = getSystemSetting(
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+ getDefaultIntensity(USAGE_NOTIFICATION));
mRingIntensity = getSystemSetting(Settings.System.RING_VIBRATION_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ getDefaultIntensity(USAGE_RINGTONE));
mVibrateInputDevices = getSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
mZenMode = getGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
}
notifyListeners();
}
+ /**
+ * Return the value to be used for {@link Settings.System#HARDWARE_HAPTIC_FEEDBACK_INTENSITY}
+ * when the value was not set by the user.
+ *
+ * <p>This should adapt the behavior preceding the introduction of this new setting key, which
+ * is to apply {@link Settings.System#HAPTIC_FEEDBACK_INTENSITY} unless it's disabled.
+ */
+ private int getHardwareFeedbackIntensityWhenSettingIsMissing(int hapticFeedbackIntensity) {
+ if (hapticFeedbackIntensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ return getDefaultIntensity(USAGE_HARDWARE_FEEDBACK);
+ }
+ return hapticFeedbackIntensity;
+ }
+
@Override
public String toString() {
return "VibrationSettings{"
@@ -389,18 +409,20 @@
+ ", mHapticChannelMaxVibrationAmplitude=" + getHapticChannelMaxVibrationAmplitude()
+ ", mRampStepDuration=" + mRampStepDuration
+ ", mRampDownDuration=" + mRampDownDuration
+ + ", mHardwareHapticFeedbackIntensity="
+ + intensityToString(getCurrentIntensity(USAGE_HARDWARE_FEEDBACK))
+ ", mHapticFeedbackIntensity="
- + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_TOUCH))
+ + intensityToString(getCurrentIntensity(USAGE_TOUCH))
+ ", mHapticFeedbackDefaultIntensity="
- + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_TOUCH))
+ + intensityToString(getDefaultIntensity(USAGE_TOUCH))
+ ", mNotificationIntensity="
- + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+ + intensityToString(getCurrentIntensity(USAGE_NOTIFICATION))
+ ", mNotificationDefaultIntensity="
- + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION))
+ + intensityToString(getDefaultIntensity(USAGE_NOTIFICATION))
+ ", mRingIntensity="
- + intensityToString(getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE))
+ + intensityToString(getCurrentIntensity(USAGE_RINGTONE))
+ ", mRingDefaultIntensity="
- + intensityToString(getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE))
+ + intensityToString(getDefaultIntensity(USAGE_RINGTONE))
+ '}';
}
@@ -410,15 +432,15 @@
proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
mHapticFeedbackIntensity);
proto.write(VibratorManagerServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
+ getDefaultIntensity(USAGE_TOUCH));
proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_INTENSITY,
mNotificationIntensity);
proto.write(VibratorManagerServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+ getDefaultIntensity(USAGE_NOTIFICATION));
proto.write(VibratorManagerServiceDumpProto.RING_INTENSITY,
mRingIntensity);
proto.write(VibratorManagerServiceDumpProto.RING_DEFAULT_INTENSITY,
- getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ getDefaultIntensity(USAGE_RINGTONE));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ea22d92..2a1bb0b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6224,15 +6224,15 @@
public boolean inputDispatchingTimedOut(String reason, int windowPid) {
ActivityRecord anrActivity;
WindowProcessController anrApp;
- boolean windowFromSameProcessAsActivity;
+ boolean blameActivityProcess;
synchronized (mAtmService.mGlobalLock) {
anrActivity = getWaitingHistoryRecordLocked();
anrApp = app;
- windowFromSameProcessAsActivity =
- !hasProcess() || app.getPid() == windowPid || windowPid == INVALID_PID;
+ blameActivityProcess = hasProcess()
+ && (app.getPid() == windowPid || windowPid == INVALID_PID);
}
- if (windowFromSameProcessAsActivity) {
+ if (blameActivityProcess) {
return mAtmService.mAmInternal.inputDispatchingTimedOut(anrApp.mOwner,
anrActivity.shortComponentName, anrActivity.info.applicationInfo,
shortComponentName, app, false, reason);
diff --git a/services/core/java/com/android/server/wm/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 42c8124..1f1fd34 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -218,6 +218,7 @@
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import com.android.internal.annotations.VisibleForTesting;
@@ -696,6 +697,14 @@
// well and thus won't change the top resumed / focused record
boolean mDontMoveToTop;
+ /**
+ * The policy controller of the windows that can be displayed on the virtual display.
+ *
+ * @see DisplayWindowPolicyController
+ */
+ @Nullable
+ DisplayWindowPolicyController mDisplayWindowPolicyController;
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -1631,11 +1640,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
@@ -2739,6 +2743,9 @@
if (newDisplayInfo != null) {
mDisplayInfo.copyFrom(newDisplayInfo);
}
+
+ mDisplayWindowPolicyController =
+ displayManagerInternal.getDisplayWindowPolicyController(mDisplayId);
}
updateBaseDisplayMetrics(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
@@ -2821,8 +2828,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"
@@ -2879,6 +2892,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
@@ -3420,6 +3440,10 @@
mInputMonitor.dump(pw, " ");
pw.println();
mInsetsStateController.dump(prefix, pw);
+ if (mDisplayWindowPolicyController != null) {
+ pw.println();
+ mDisplayWindowPolicyController.dump(prefix, pw);
+ }
}
@Override
@@ -4272,7 +4296,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));
@@ -4801,7 +4825,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} */
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 09de6b3..6d3e8d5 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1738,7 +1738,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 +1767,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;
}
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index badb1f5..963f326 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -139,9 +139,6 @@
setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
}
}
- if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) {
- mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
- }
}
private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 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/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/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 3de98f1..7a7fb65 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -169,21 +169,35 @@
}
}
+ /**
+ * Returns true when the app specific configuration is successfully stored or removed based on
+ * the current requested configuration. It will return false when the requested
+ * configuration is same as the pre-existing app-specific configuration.
+ */
@GuardedBy("mLock")
boolean updateFromImpl(String packageName, int userId,
PackageConfigurationUpdaterImpl impl) {
synchronized (mLock) {
- PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
- if (impl.getNightMode() != null) {
- record.mNightMode = impl.getNightMode();
+ boolean isRecordPresent = false;
+ PackageConfigRecord record = findRecord(mModified, packageName, userId);
+ if (record != null) {
+ isRecordPresent = true;
+ } else {
+ record = findRecordOrCreate(mModified, packageName, userId);
}
- if (impl.getLocales() != null) {
- record.mLocales = impl.getLocales();
- }
+ boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
+ boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+
if ((record.mNightMode == null || record.isResetNightMode())
&& (record.mLocales == null || record.mLocales.isEmpty())) {
// if all values default to system settings, we can remove the package.
removePackage(packageName, userId);
+ // if there was a pre-existing record for the package that was deleted,
+ // we return true (since it was successfully deleted), else false (since there was
+ // no change to the previous state).
+ return isRecordPresent;
+ } else if (!isNightModeChanged && !isLocalesChanged) {
+ return false;
} else {
final PackageConfigRecord pendingRecord =
findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -195,7 +209,8 @@
writeRecord = pendingRecord;
}
- if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
+ if (!updateNightMode(record.mNightMode, writeRecord)
+ && !updateLocales(record.mLocales, writeRecord)) {
return false;
}
@@ -203,24 +218,24 @@
Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
}
mPersisterQueue.addItem(new WriteProcessItem(writeRecord), false /* flush */);
+ return true;
}
- return true;
}
}
- private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
- if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+ private boolean updateNightMode(Integer requestedNightMode, PackageConfigRecord record) {
+ if (requestedNightMode == null || requestedNightMode.equals(record.mNightMode)) {
return false;
}
- writeRecord.mNightMode = record.mNightMode;
+ record.mNightMode = requestedNightMode;
return true;
}
- private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
- if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+ private boolean updateLocales(LocaleList requestedLocaleList, PackageConfigRecord record) {
+ if (requestedLocaleList == null || requestedLocaleList.equals(record.mLocales)) {
return false;
}
- writeRecord.mLocales = record.mLocales;
+ record.mLocales = requestedLocaleList;
return true;
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 600545e..7268610 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);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6d83fb6..29c27f9 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -31,10 +31,8 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
-import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskFragmentOrganizerController;
-import android.window.TaskFragmentAppearedInfo;
import android.window.TaskFragmentInfo;
import com.android.internal.protolog.common.ProtoLog;
@@ -135,11 +133,8 @@
void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
final TaskFragmentInfo info = tf.getTaskFragmentInfo();
- final SurfaceControl outSurfaceControl = new SurfaceControl(tf.getSurfaceControl(),
- "TaskFragmentOrganizerController.onTaskFragmentInfoAppeared");
try {
- organizer.onTaskFragmentAppeared(
- new TaskFragmentAppearedInfo(info, outSurfaceControl));
+ organizer.onTaskFragmentAppeared(info);
mLastSentTaskFragmentInfos.put(tf, info);
tf.mTaskFragmentAppearedSent = true;
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f175eec..7349594 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -244,11 +244,11 @@
}
mParticipants.add(wc);
if (info.mShowWallpaper) {
- // Collect the wallpaper so it is part of the sync set.
- final WindowContainer wallpaper =
+ // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
+ final WindowState wallpaper =
wc.getDisplayContent().mWallpaperController.getTopVisibleWallpaper();
if (wallpaper != null) {
- collect(wallpaper);
+ collect(wallpaper.mToken);
}
}
}
@@ -495,25 +495,35 @@
Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId);
return;
}
- int displayId = DEFAULT_DISPLAY;
- for (WindowContainer container : mParticipants) {
- if (container.mDisplayContent == null) continue;
- displayId = container.mDisplayContent.getDisplayId();
+ boolean hasWallpaper = false;
+ DisplayContent dc = null;
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = mParticipants.valueAt(i);
+ if (dc == null && wc.mDisplayContent != null) {
+ dc = wc.mDisplayContent;
+ }
+ if (!hasWallpaper && isWallpaper(wc)) {
+ hasWallpaper = true;
+ }
}
+ if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay();
if (mState == STATE_ABORT) {
mController.abort(this);
- mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
- .getPendingTransaction().merge(transaction);
+ dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
return;
}
+ // Ensure that wallpaper visibility is updated with the latest wallpaper target.
+ if (hasWallpaper) {
+ dc.mWallpaperController.adjustWallpaperWindows();
+ }
mState = STATE_PLAYING;
mController.moveToPlaying(this);
- if (mController.mAtm.mTaskSupervisor.getKeyguardController().isKeyguardLocked(displayId)) {
+ if (dc.isKeyguardLocked()) {
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
@@ -523,9 +533,9 @@
info.setAnimationOptions(mOverrideOptions);
// TODO(b/188669821): Move to animation impl in shell.
- handleLegacyRecentsStartBehavior(displayId, info);
+ handleLegacyRecentsStartBehavior(dc, info);
- handleNonAppWindowsInTransition(displayId, mType, mFlags);
+ handleNonAppWindowsInTransition(dc, mType, mFlags);
reportStartReasonsToLogger();
@@ -627,14 +637,11 @@
}
/** @see RecentsAnimationController#attachNavigationBarToApp */
- private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
+ private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
return;
}
- final DisplayContent dc =
- mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
- if (dc == null) return;
- mRecentsDisplayId = displayId;
+ mRecentsDisplayId = dc.mDisplayId;
// Recents has an input-consumer to grab input from the "live tile" app. Set that up here
final InputConsumerImpl recentsAnimationInputConsumer =
@@ -679,7 +686,7 @@
// Find the top-most non-home, closing app.
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change c = info.getChanges().get(i);
- if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
+ if (c.getTaskInfo() == null || c.getTaskInfo().displayId != mRecentsDisplayId
|| c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
|| !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
continue;
@@ -710,7 +717,7 @@
t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
}
if (mController.mStatusBar != null) {
- mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
+ mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, false);
}
}
@@ -760,13 +767,8 @@
}
}
- private void handleNonAppWindowsInTransition(int displayId,
+ private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
@TransitionType int transit, @TransitionFlags int flags) {
- final DisplayContent dc =
- mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
- if (dc == null) {
- return;
- }
if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
|| (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
&& !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 08b1a2f..e24be37 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -611,8 +611,9 @@
private void updateWallpaperTokens(boolean visible) {
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperWindows(visible);
- token.getDisplayContent().assignWindowLayers(false);
+ if (token.updateWallpaperWindows(visible)) {
+ token.mDisplayContent.assignWindowLayers(false /* setLayoutNeeded */);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 3a639f5..fe405e5 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -104,18 +104,21 @@
}
}
- void updateWallpaperWindows(boolean visible) {
+ /** Returns {@code true} if visibility is changed. */
+ boolean updateWallpaperWindows(boolean visible) {
+ boolean changed = false;
if (isVisible() != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
token, visible);
setVisibility(visible);
+ changed = true;
}
- final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
if (mTransitionController.isShellTransitionsEnabled()) {
- return;
+ return changed;
}
- final WindowState wallpaperTarget = wallpaperController.getWallpaperTarget();
+ final WindowState wallpaperTarget =
+ mDisplayContent.mWallpaperController.getWallpaperTarget();
if (visible && wallpaperTarget != null) {
final RecentsAnimationController recentsAnimationController =
@@ -137,6 +140,7 @@
}
setVisible(visible);
+ return changed;
}
private void setVisible(boolean visible) {
@@ -155,10 +159,12 @@
* transition. In that situation, make sure to call {@link #commitVisibility} when done.
*/
void setVisibility(boolean visible) {
- // Before setting mVisibleRequested so we can track changes.
- mTransitionController.collect(this);
+ if (mVisibleRequested != visible) {
+ // Before setting mVisibleRequested so we can track changes.
+ mTransitionController.collect(this);
- setVisibleRequested(visible);
+ setVisibleRequested(visible);
+ }
// If in a transition, defer commits for activities that are going invisible
if (!visible && (mTransitionController.inTransition()
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 81b241e..44edaa2 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;
}
/**
@@ -3559,10 +3554,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 +3829,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 +4722,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/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0a55003..e4c8871 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -29,6 +29,8 @@
#include <android/hardware/gnss/2.1/IGnssMeasurement.h>
#include <android/hardware/gnss/BnGnss.h>
#include <android/hardware/gnss/BnGnssCallback.h>
+#include <android/hardware/gnss/BnGnssGeofence.h>
+#include <android/hardware/gnss/BnGnssGeofenceCallback.h>
#include <android/hardware/gnss/BnGnssMeasurementCallback.h>
#include <android/hardware/gnss/BnGnssPowerIndicationCallback.h>
#include <android/hardware/gnss/BnGnssPsdsCallback.h>
@@ -191,6 +193,9 @@
using android::hardware::gnss::PsdsType;
using IGnssAidl = android::hardware::gnss::IGnss;
using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
+using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
+using IGnssGeofenceAidl = android::hardware::gnss::IGnssGeofence;
+using IGnssGeofenceCallbackAidl = android::hardware::gnss::IGnssGeofenceCallback;
using IGnssPsdsAidl = android::hardware::gnss::IGnssPsds;
using IGnssPsdsCallbackAidl = android::hardware::gnss::IGnssPsdsCallback;
using IGnssConfigurationAidl = android::hardware::gnss::IGnssConfiguration;
@@ -216,6 +221,8 @@
sp<IGnss_V2_0> gnssHal_V2_0 = nullptr;
sp<IGnss_V2_1> gnssHal_V2_1 = nullptr;
sp<IGnssAidl> gnssHalAidl = nullptr;
+sp<IGnssBatchingAidl> gnssBatchingAidlIface = nullptr;
+sp<IGnssGeofenceAidl> gnssGeofenceAidlIface = nullptr;
sp<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
@@ -709,35 +716,25 @@
return Void();
}
-/*
- * GnssGeofenceCallback class implements the callback methods for the
- * IGnssGeofence interface.
- */
-struct GnssGeofenceCallback : public IGnssGeofenceCallback {
- // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
- Return<void> gnssGeofenceTransitionCb(
- int32_t geofenceId,
- const GnssLocation_V1_0& location,
- GeofenceTransition transition,
- hardware::gnss::V1_0::GnssUtcTime timestamp) override;
- Return<void>
- gnssGeofenceStatusCb(
- GeofenceAvailability status,
- const GnssLocation_V1_0& location) override;
- Return<void> gnssGeofenceAddCb(int32_t geofenceId,
- GeofenceStatus status) override;
- Return<void> gnssGeofenceRemoveCb(int32_t geofenceId,
- GeofenceStatus status) override;
- Return<void> gnssGeofencePauseCb(int32_t geofenceId,
- GeofenceStatus status) override;
- Return<void> gnssGeofenceResumeCb(int32_t geofenceId,
- GeofenceStatus status) override;
+/** Util class for GnssGeofenceCallback methods. */
+struct GnssGeofenceCallbackUtil {
+ template <class T>
+ static void gnssGeofenceTransitionCb(int geofenceId, const T& location, int transition,
+ int64_t timestampMillis);
+ template <class T>
+ static void gnssGeofenceStatusCb(int availability, const T& lastLocation);
+ static void gnssGeofenceAddCb(int geofenceId, int status);
+ static void gnssGeofenceRemoveCb(int geofenceId, int status);
+ static void gnssGeofencePauseCb(int geofenceId, int status);
+ static void gnssGeofenceResumeCb(int geofenceId, int status);
+
+private:
+ GnssGeofenceCallbackUtil() = delete;
};
-Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
- int32_t geofenceId, const GnssLocation_V1_0& location,
- GeofenceTransition transition,
- hardware::gnss::V1_0::GnssUtcTime timestamp) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(int geofenceId, const T& location,
+ int transition, int64_t timestamp) {
JNIEnv* env = getJniEnv();
jobject jLocation = translateGnssLocation(env, location);
@@ -751,27 +748,22 @@
checkAndClearExceptionFromCallback(env, __FUNCTION__);
env->DeleteLocalRef(jLocation);
- return Void();
}
-Return<void>
-GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability status,
- const GnssLocation_V1_0& location) {
+template <class T>
+void GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(int availability, const T& lastLocation) {
JNIEnv* env = getJniEnv();
- jobject jLocation = translateGnssLocation(env, location);
+ jobject jLocation = translateGnssLocation(env, lastLocation);
- env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, status,
- jLocation);
+ env->CallVoidMethod(mCallbacksObj, method_reportGeofenceStatus, availability, jLocation);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
env->DeleteLocalRef(jLocation);
- return Void();
}
-Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId,
- GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceAddCb(int geofenceId, int status) {
JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+ if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
ALOGE("%s: Error in adding a Geofence: %d\n", __func__, status);
}
@@ -780,13 +772,11 @@
geofenceId,
status);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return Void();
}
-Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId,
- GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(int geofenceId, int status) {
JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+ if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
ALOGE("%s: Error in removing a Geofence: %d\n", __func__, status);
}
@@ -794,13 +784,11 @@
method_reportGeofenceRemoveStatus,
geofenceId, status);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return Void();
}
-Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId,
- GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofencePauseCb(int geofenceId, int status) {
JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+ if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
ALOGE("%s: Error in pausing Geofence: %d\n", __func__, status);
}
@@ -808,13 +796,11 @@
method_reportGeofencePauseStatus,
geofenceId, status);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return Void();
}
-Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId,
- GeofenceStatus status) {
+void GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(int geofenceId, int status) {
JNIEnv* env = getJniEnv();
- if (status != IGnssGeofenceCallback::GeofenceStatus::OPERATION_SUCCESS) {
+ if (status != IGnssGeofenceCallbackAidl::OPERATION_SUCCESS) {
ALOGE("%s: Error in resuming Geofence: %d\n", __func__, status);
}
@@ -822,6 +808,104 @@
method_reportGeofenceResumeStatus,
geofenceId, status);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+/*
+ * GnssGeofenceCallbackAidl class implements the callback methods for the IGnssGeofence AIDL
+ * interface.
+ */
+struct GnssGeofenceCallbackAidl : public android::hardware::gnss::BnGnssGeofenceCallback {
+ Status gnssGeofenceTransitionCb(int geofenceId, const GnssLocationAidl& location,
+ int transition, int64_t timestampMillis) override;
+ Status gnssGeofenceStatusCb(int availability, const GnssLocationAidl& lastLocation) override;
+ Status gnssGeofenceAddCb(int geofenceId, int status) override;
+ Status gnssGeofenceRemoveCb(int geofenceId, int status) override;
+ Status gnssGeofencePauseCb(int geofenceId, int status) override;
+ Status gnssGeofenceResumeCb(int geofenceId, int status) override;
+};
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceTransitionCb(int geofenceId,
+ const GnssLocationAidl& location,
+ int transition, int64_t timestampMillis) {
+ GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, transition,
+ timestampMillis);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceStatusCb(int availability,
+ const GnssLocationAidl& lastLocation) {
+ GnssGeofenceCallbackUtil::gnssGeofenceStatusCb(availability, lastLocation);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceAddCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceRemoveCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofencePauseCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, status);
+ return Status::ok();
+}
+
+Status GnssGeofenceCallbackAidl::gnssGeofenceResumeCb(int geofenceId, int status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, status);
+ return Status::ok();
+}
+
+/*
+ * GnssGeofenceCallback class implements the callback methods for the
+ * IGnssGeofence HIDL interface.
+ */
+struct GnssGeofenceCallback : public IGnssGeofenceCallback {
+ // Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
+ Return<void> gnssGeofenceTransitionCb(int32_t geofenceId, const GnssLocation_V1_0& location,
+ GeofenceTransition transition,
+ hardware::gnss::V1_0::GnssUtcTime timestamp) override;
+ Return<void> gnssGeofenceStatusCb(GeofenceAvailability status,
+ const GnssLocation_V1_0& location) override;
+ Return<void> gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) override;
+ Return<void> gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) override;
+ Return<void> gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) override;
+ Return<void> gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) override;
+};
+
+Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
+ int32_t geofenceId, const GnssLocation_V1_0& location, GeofenceTransition transition,
+ hardware::gnss::V1_0::GnssUtcTime timestamp) {
+ GnssGeofenceCallbackUtil::gnssGeofenceTransitionCb(geofenceId, location, (int)transition,
+ (int64_t)timestamp);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(GeofenceAvailability availability,
+ const GnssLocation_V1_0& location) {
+ GnssGeofenceCallbackUtil::gnssGeofenceStatusCb((int)availability, location);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceAddCb(int32_t geofenceId, GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceAddCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceRemoveCb(int32_t geofenceId, GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceRemoveCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofencePauseCb(int32_t geofenceId, GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofencePauseCb(geofenceId, (int)status);
+ return Void();
+}
+
+Return<void> GnssGeofenceCallback::gnssGeofenceResumeCb(int32_t geofenceId, GeofenceStatus status) {
+ GnssGeofenceCallbackUtil::gnssGeofenceResumeCb(geofenceId, (int)status);
return Void();
}
@@ -1279,11 +1363,13 @@
android_location_gnss_hal_GnssNative_set_gps_service_handle();
}
- if (gnssHal == nullptr) {
+ if (gnssHal == nullptr && gnssHalAidl == nullptr) {
ALOGE("Unable to get GPS service\n");
return;
}
+ // TODO: linkToDeath for AIDL HAL
+
gnssHalDeathRecipient = new GnssDeathRecipient();
hardware::Return<bool> linked = gnssHal->linkToDeath(gnssHalDeathRecipient, /*cookie*/ 0);
if (!linked.isOk()) {
@@ -1303,7 +1389,7 @@
} else {
ALOGD("Unable to get a handle to PSDS AIDL interface.");
}
- } else {
+ } else if (gnssHal != nullptr) {
auto gnssXtra = gnssHal->getExtensionXtra();
if (!gnssXtra.isOk()) {
ALOGD("Unable to get a handle to Xtra");
@@ -1320,7 +1406,7 @@
agnssRilIface_V2_0 = agnssRil_V2_0;
agnssRilIface = agnssRilIface_V2_0;
}
- } else {
+ } else if (gnssHal != nullptr) {
auto agnssRil_V1_0 = gnssHal->getExtensionAGnssRil();
if (!agnssRil_V1_0.isOk()) {
ALOGD("Unable to get a handle to AGnssRil");
@@ -1336,7 +1422,7 @@
} else {
agnssIface_V2_0 = agnss_V2_0;
}
- } else {
+ } else if (gnssHal != nullptr) {
auto agnss_V1_0 = gnssHal->getExtensionAGnss();
if (!agnss_V1_0.isOk()) {
ALOGD("Unable to get a handle to AGnss");
@@ -1345,11 +1431,13 @@
}
}
- auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
- if (!gnssNavigationMessage.isOk()) {
- ALOGD("Unable to get a handle to GnssNavigationMessage");
- } else {
- gnssNavigationMessageIface = gnssNavigationMessage;
+ if (gnssHal != nullptr) {
+ auto gnssNavigationMessage = gnssHal->getExtensionGnssNavigationMessage();
+ if (!gnssNavigationMessage.isOk()) {
+ ALOGD("Unable to get a handle to GnssNavigationMessage");
+ } else {
+ gnssNavigationMessageIface = gnssNavigationMessage;
+ }
}
// Allow all causal combinations between IGnss.hal and IGnssMeasurement.hal. That means,
@@ -1387,12 +1475,12 @@
std::make_unique<android::gnss::GnssMeasurement_V1_1>(gnssMeasurement);
}
}
- if (gnssMeasurementIface == nullptr) {
- auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
- if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
- gnssMeasurementIface =
- std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
- }
+ if (gnssHal != nullptr && gnssMeasurementIface == nullptr) {
+ auto gnssMeasurement = gnssHal->getExtensionGnssMeasurement();
+ if (checkHidlReturn(gnssMeasurement, "Unable to get a handle to GnssMeasurement_V1_0")) {
+ gnssMeasurementIface =
+ std::make_unique<android::gnss::GnssMeasurement_V1_0>(gnssMeasurement);
+ }
}
if (gnssHal_V2_1 != nullptr) {
@@ -1434,7 +1522,7 @@
gnssDebugIface = gnssDebugIface_V2_0;
}
}
- if (gnssDebugIface == nullptr) {
+ if (gnssHal != nullptr && gnssDebugIface == nullptr) {
auto gnssDebug = gnssHal->getExtensionGnssDebug();
if (!gnssDebug.isOk()) {
ALOGD("Unable to get a handle to GnssDebug");
@@ -1443,11 +1531,13 @@
}
}
- auto gnssNi = gnssHal->getExtensionGnssNi();
- if (!gnssNi.isOk()) {
- ALOGD("Unable to get a handle to GnssNi");
- } else {
- gnssNiIface = gnssNi;
+ if (gnssHal != nullptr) {
+ auto gnssNi = gnssHal->getExtensionGnssNi();
+ if (!gnssNi.isOk()) {
+ ALOGD("Unable to get a handle to GnssNi");
+ } else {
+ gnssNiIface = gnssNi;
+ }
}
if (gnssHalAidl != nullptr) {
@@ -1488,11 +1578,17 @@
}
}
- auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
- if (!gnssGeofencing.isOk()) {
- ALOGD("Unable to get a handle to GnssGeofencing");
- } else {
- gnssGeofencingIface = gnssGeofencing;
+ if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+ sp<IGnssGeofenceAidl> gnssGeofenceAidl;
+ auto status = gnssHalAidl->getExtensionGnssGeofence(&gnssGeofenceAidl);
+ if (checkAidlStatus(status, "Unable to get a handle to GnssGeofence interface.")) {
+ gnssGeofenceAidlIface = gnssGeofenceAidl;
+ }
+ } else if (gnssHal != nullptr) {
+ auto gnssGeofencing = gnssHal->getExtensionGnssGeofencing();
+ if (checkHidlReturn(gnssGeofencing, "Unable to get a handle to GnssGeofencing")) {
+ gnssGeofencingIface = gnssGeofencing;
+ }
}
if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
@@ -1507,7 +1603,7 @@
gnssBatchingIface = std::make_unique<gnss::GnssBatching_V2_0>(gnssBatching_V2_0);
}
}
- if (gnssBatchingIface == nullptr) {
+ if (gnssHal != nullptr && gnssBatchingIface == nullptr) {
auto gnssBatching_V1_0 = gnssHal->getExtensionGnssBatching();
if (checkHidlReturn(gnssBatching_V1_0, "Unable to get a handle to GnssBatching")) {
gnssBatchingIface = std::make_unique<gnss::GnssBatching_V1_0>(gnssBatching_V1_0);
@@ -1568,7 +1664,7 @@
/*
* Fail if the main interface fails to initialize
*/
- if (gnssHal == nullptr) {
+ if (gnssHal == nullptr && gnssHalAidl == nullptr) {
ALOGE("Unable to initialize GNSS HAL.");
return JNI_FALSE;
}
@@ -1583,7 +1679,7 @@
result = gnssHal_V2_0->setCallback_2_0(gnssCbIface);
} else if (gnssHal_V1_1 != nullptr) {
result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
- } else {
+ } else if (gnssHal != nullptr) {
result = gnssHal->setCallback(gnssCbIface);
}
@@ -1630,10 +1726,18 @@
}
// Set IGnssGeofencing.hal callback.
- sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
- if (gnssGeofencingIface != nullptr) {
+ if (gnssGeofenceAidlIface != nullptr) {
+ sp<IGnssGeofenceCallbackAidl> gnssGeofenceCallbackAidl = new GnssGeofenceCallbackAidl();
+ auto status = gnssGeofenceAidlIface->setCallback(gnssGeofenceCallbackAidl);
+ if (!checkAidlStatus(status, "IGnssGeofenceAidl setCallback() failed.")) {
+ gnssGeofenceAidlIface = nullptr;
+ }
+ } else if (gnssGeofencingIface != nullptr) {
+ sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
auto status = gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
- checkHidlReturn(status, "IGnssGeofencing setCallback() failed.");
+ if (!checkHidlReturn(status, "IGnssGeofencing setCallback() failed.")) {
+ gnssGeofencingIface = nullptr;
+ }
} else {
ALOGI("Unable to initialize IGnssGeofencing interface.");
}
@@ -1693,12 +1797,15 @@
}
static void android_location_gnss_hal_GnssNative_cleanup(JNIEnv* /* env */, jclass) {
- if (gnssHal == nullptr) {
- return;
+ if (gnssHalAidl != nullptr) {
+ auto status = gnssHalAidl->close();
+ checkAidlStatus(status, "IGnssAidl close() failed.");
}
- auto result = gnssHal->cleanup();
- checkHidlReturn(result, "IGnss cleanup() failed.");
+ if (gnssHal != nullptr) {
+ auto result = gnssHal->cleanup();
+ checkHidlReturn(result, "IGnss cleanup() failed.");
+ }
}
static jboolean android_location_gnss_hal_GnssNative_set_position_mode(
@@ -2177,57 +2284,85 @@
static jboolean android_location_gnss_hal_GnssNative_is_geofence_supported(JNIEnv* /* env */,
jclass) {
- return (gnssGeofencingIface != nullptr) ? JNI_TRUE : JNI_FALSE;
+ if (gnssGeofencingIface == nullptr && gnssGeofenceAidlIface == nullptr) {
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
}
static jboolean android_location_gnss_hal_GnssNative_add_geofence(
JNIEnv* /* env */, jclass, jint geofenceId, jdouble latitude, jdouble longitude,
jdouble radius, jint last_transition, jint monitor_transition,
jint notification_responsiveness, jint unknown_timer) {
- if (gnssGeofencingIface == nullptr) {
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ if (gnssGeofenceAidlIface != nullptr) {
+ auto status =
+ gnssGeofenceAidlIface->addGeofence(geofenceId, latitude, longitude, radius,
+ last_transition, monitor_transition,
+ notification_responsiveness, unknown_timer);
+ return checkAidlStatus(status, "IGnssGeofenceAidl addGeofence() failed.");
}
- auto result = gnssGeofencingIface->addGeofence(
- geofenceId, latitude, longitude, radius,
- static_cast<IGnssGeofenceCallback::GeofenceTransition>(last_transition),
- monitor_transition, notification_responsiveness, unknown_timer);
- return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+ if (gnssGeofencingIface != nullptr) {
+ auto result = gnssGeofencingIface
+ ->addGeofence(geofenceId, latitude, longitude, radius,
+ static_cast<IGnssGeofenceCallback::GeofenceTransition>(
+ last_transition),
+ monitor_transition, notification_responsiveness,
+ unknown_timer);
+ return checkHidlReturn(result, "IGnssGeofencing addGeofence() failed.");
+ }
+
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
static jboolean android_location_gnss_hal_GnssNative_remove_geofence(JNIEnv* /* env */, jclass,
jint geofenceId) {
- if (gnssGeofencingIface == nullptr) {
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ if (gnssGeofenceAidlIface != nullptr) {
+ auto status = gnssGeofenceAidlIface->removeGeofence(geofenceId);
+ return checkAidlStatus(status, "IGnssGeofenceAidl removeGeofence() failed.");
}
- auto result = gnssGeofencingIface->removeGeofence(geofenceId);
- return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+ if (gnssGeofencingIface != nullptr) {
+ auto result = gnssGeofencingIface->removeGeofence(geofenceId);
+ return checkHidlReturn(result, "IGnssGeofencing removeGeofence() failed.");
+ }
+
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
static jboolean android_location_gnss_hal_GnssNative_pause_geofence(JNIEnv* /* env */, jclass,
jint geofenceId) {
- if (gnssGeofencingIface == nullptr) {
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ if (gnssGeofenceAidlIface != nullptr) {
+ auto status = gnssGeofenceAidlIface->pauseGeofence(geofenceId);
+ return checkAidlStatus(status, "IGnssGeofenceAidl pauseGeofence() failed.");
}
- auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
- return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+ if (gnssGeofencingIface != nullptr) {
+ auto result = gnssGeofencingIface->pauseGeofence(geofenceId);
+ return checkHidlReturn(result, "IGnssGeofencing pauseGeofence() failed.");
+ }
+
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
static jboolean android_location_gnss_hal_GnssNative_resume_geofence(JNIEnv* /* env */, jclass,
jint geofenceId,
jint monitor_transition) {
- if (gnssGeofencingIface == nullptr) {
- ALOGE("%s: IGnssGeofencing interface not available.", __func__);
- return JNI_FALSE;
+ if (gnssGeofenceAidlIface != nullptr) {
+ auto status = gnssGeofenceAidlIface->resumeGeofence(geofenceId, monitor_transition);
+ return checkAidlStatus(status, "IGnssGeofenceAidl resumeGeofence() failed.");
}
- auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
- return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+ if (gnssGeofencingIface != nullptr) {
+ auto result = gnssGeofencingIface->resumeGeofence(geofenceId, monitor_transition);
+ return checkHidlReturn(result, "IGnssGeofencing resumeGeofence() failed.");
+ }
+
+ ALOGE("%s: IGnssGeofencing interface not available.", __func__);
+ return JNI_FALSE;
}
static jboolean android_location_gnss_hal_GnssNative_is_antenna_info_supported(JNIEnv* env,
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..efac4d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10587,7 +10587,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 +10601,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..3ede408 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -398,6 +398,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";
@@ -2716,6 +2718,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/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/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/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 086e3c0..a83d51b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -68,6 +68,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.MagnificationConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -650,8 +651,12 @@
final float scale = 1.8f;
final float centerX = 50.5f;
final float centerY = 100.5f;
- when(mMockMagnificationProcessor.setScaleAndCenter(displayId,
- scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+ MagnificationConfig config = new MagnificationConfig.Builder()
+ .setScale(scale)
+ .setCenterX(centerX)
+ .setCenterY(centerY).build();
+ when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true,
+ SERVICE_ID)).thenReturn(true);
when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
@@ -665,8 +670,12 @@
final float scale = 1.8f;
final float centerX = 50.5f;
final float centerY = 100.5f;
- when(mMockMagnificationProcessor.setScaleAndCenter(displayId,
- scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+ MagnificationConfig config = new MagnificationConfig.Builder()
+ .setScale(scale)
+ .setCenterX(centerX)
+ .setCenterY(centerY).build();
+ when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true,
+ SERVICE_ID)).thenReturn(true);
when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index c412b94..2fd2816 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -16,25 +16,32 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.MagnificationConfig.FULLSCREEN_MODE;
+import static android.accessibilityservice.MagnificationConfig.WINDOW_MODE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.accessibilityservice.MagnificationConfig;
import android.graphics.Region;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationProcessor;
+import com.android.server.accessibility.magnification.WindowMagnificationManager;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-
+import org.mockito.stubbing.Answer;
/**
* Tests for the {@link MagnificationProcessor}
@@ -42,57 +49,115 @@
public class MagnificationProcessorTest {
private static final int TEST_DISPLAY = 0;
-
+ private static final int SERVICE_ID = 42;
+ private static final float TEST_SCALE = 1.8f;
+ private static final float TEST_CENTER_X = 50.5f;
+ private static final float TEST_CENTER_Y = 100.5f;
private MagnificationProcessor mMagnificationProcessor;
@Mock
private MagnificationController mMockMagnificationController;
@Mock
private FullScreenMagnificationController mMockFullScreenMagnificationController;
+ @Mock
+ private WindowMagnificationManager mMockWindowMagnificationManager;
+ FullScreenMagnificationControllerStub mFullScreenMagnificationControllerStub;
+ WindowMagnificationManagerStub mWindowMagnificationManagerStub;
@Before
+
public void setup() {
MockitoAnnotations.initMocks(this);
-
+ mFullScreenMagnificationControllerStub = new FullScreenMagnificationControllerStub(
+ mMockFullScreenMagnificationController);
+ mWindowMagnificationManagerStub = new WindowMagnificationManagerStub(
+ mMockWindowMagnificationManager);
when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn(
mMockFullScreenMagnificationController);
+ when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn(
+ mMockWindowMagnificationManager);
mMagnificationProcessor = new MagnificationProcessor(mMockMagnificationController);
}
@Test
- public void getScale() {
- final float result = 2;
- when(mMockFullScreenMagnificationController.getScale(TEST_DISPLAY)).thenReturn(result);
+ public void getScale_fullscreenMode_expectedValue() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setScale(TEST_SCALE).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
- assertEquals(scale, result, 0);
+ assertEquals(scale, TEST_SCALE, 0);
}
@Test
- public void getCenterX_canControlMagnification_returnCenterX() {
- final float result = 200;
- when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(result);
+ public void getScale_windowMode_expectedValue() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(WINDOW_MODE)
+ .setScale(TEST_SCALE).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
+
+ assertEquals(scale, TEST_SCALE, 0);
+ }
+
+ @Test
+ public void getCenterX_canControlFullscreenMagnification_returnCenterX() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setCenterX(TEST_CENTER_X).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
float centerX = mMagnificationProcessor.getCenterX(
TEST_DISPLAY, /* canControlMagnification= */true);
- assertEquals(centerX, result, 0);
+ assertEquals(centerX, TEST_CENTER_X, 0);
}
@Test
- public void getCenterY_canControlMagnification_returnCenterY() {
- final float result = 300;
- when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(result);
+ public void getCenterX_canControlWindowMagnification_returnCenterX() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(WINDOW_MODE)
+ .setCenterX(TEST_CENTER_X).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ float centerX = mMagnificationProcessor.getCenterX(
+ TEST_DISPLAY, /* canControlMagnification= */true);
+
+ assertEquals(centerX, TEST_CENTER_X, 0);
+ }
+
+ @Test
+ public void getCenterY_canControlFullscreenMagnification_returnCenterY() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
float centerY = mMagnificationProcessor.getCenterY(
TEST_DISPLAY, /* canControlMagnification= */false);
- assertEquals(centerY, result, 0);
+ assertEquals(centerY, TEST_CENTER_Y, 0);
}
@Test
- public void getMagnificationRegion_canControlMagnification_returnRegion() {
+ public void getCenterY_canControlWindowMagnification_returnCenterY() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(WINDOW_MODE)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ float centerY = mMagnificationProcessor.getCenterY(
+ TEST_DISPLAY, /* canControlMagnification= */false);
+
+ assertEquals(centerY, TEST_CENTER_Y, 0);
+ }
+
+ @Test
+ public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
final Region region = new Region(10, 20, 100, 200);
+ setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
region, /* canControlMagnification= */true);
@@ -101,14 +166,25 @@
}
@Test
- public void getMagnificationRegion_notRegistered_shouldRegisterThenUnregister() {
+ public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
final Region region = new Region(10, 20, 100, 200);
+ setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+ mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+ region, /* canControlMagnification= */true);
+
+ verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
+ eq(region));
+ }
+
+ @Test
+ public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+ final Region region = new Region(10, 20, 100, 200);
+ setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
doAnswer((invocation) -> {
((Region) invocation.getArguments()[1]).set(region);
return null;
}).when(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY),
any());
- when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
final Region result = new Region();
mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
@@ -119,44 +195,242 @@
}
@Test
- public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() {
- final float centerX = 480.0f;
- when(mMockFullScreenMagnificationController.getCenterX(TEST_DISPLAY)).thenReturn(centerX);
- when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+ public void getMagnificationCenterX_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setCenterX(TEST_CENTER_X).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
final float result = mMagnificationProcessor.getCenterX(
TEST_DISPLAY, /* canControlMagnification= */ true);
- assertEquals(centerX, result, 0);
+ assertEquals(TEST_CENTER_X, result, 0);
verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY);
}
@Test
- public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() {
- final float centerY = 640.0f;
- when(mMockFullScreenMagnificationController.getCenterY(TEST_DISPLAY)).thenReturn(centerY);
- when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+ public void getMagnificationCenterY_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
final float result = mMagnificationProcessor.getCenterY(
TEST_DISPLAY, /* canControlMagnification= */ true);
- assertEquals(centerY, result, 0);
+ assertEquals(TEST_CENTER_Y, result, 0);
verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
verify(mMockFullScreenMagnificationController).unregister(TEST_DISPLAY);
}
@Test
- public void setMagnificationScaleAndCenter_notRegistered_shouldRegister() {
- final int serviceId = 42;
- final float scale = 1.8f;
- final float centerX = 50.5f;
- final float centerY = 100.5f;
- when(mMockFullScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
- scale, centerX, centerY, true, serviceId)).thenReturn(true);
- when(mMockFullScreenMagnificationController.isRegistered(TEST_DISPLAY)).thenReturn(false);
+ public void getCurrentMode_configDefaultMode_returnActivatedMode() {
+ final int targetMode = WINDOW_MODE;
+ setMagnificationActivated(TEST_DISPLAY, targetMode);
- final boolean result = mMagnificationProcessor.setScaleAndCenter(
- TEST_DISPLAY, scale, centerX, centerY, true, serviceId);
+ int currentMode = mMagnificationProcessor.getControllingMode(TEST_DISPLAY);
+
+ assertEquals(WINDOW_MODE, currentMode);
+ }
+
+ @Test
+ public void reset_fullscreenMagnificationActivated() {
+ setMagnificationActivated(TEST_DISPLAY, FULLSCREEN_MODE);
+
+ mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+
+ verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false);
+ }
+
+ @Test
+ public void reset_windowMagnificationActivated() {
+ setMagnificationActivated(TEST_DISPLAY, WINDOW_MODE);
+
+ mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+
+ verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY);
+ }
+
+ @Test
+ public void setMagnificationConfig_fullscreenModeNotRegistered_shouldRegister() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ final boolean result = mMagnificationProcessor.setMagnificationConfig(
+ TEST_DISPLAY, config, true, SERVICE_ID);
assertTrue(result);
verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
}
+
+ @Test
+ public void setMagnificationConfig_windowMode_enableMagnification() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(WINDOW_MODE)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ final boolean result = mMagnificationProcessor.setMagnificationConfig(
+ TEST_DISPLAY, config, true, SERVICE_ID);
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(FULLSCREEN_MODE)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+ // mMockFullScreenMagnificationController.unregister(TEST_DISPLAY);
+ mMagnificationProcessor.setMagnificationConfig(
+ TEST_DISPLAY, config, true, SERVICE_ID);
+
+ final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
+ TEST_DISPLAY);
+
+ assertConfigEquals(config, result);
+ }
+
+ @Test
+ public void setMagnificationConfig_windowEnabled_expectedConfigValues() {
+ final MagnificationConfig config = new MagnificationConfig.Builder()
+ .setMode(WINDOW_MODE)
+ .setScale(TEST_SCALE)
+ .setCenterX(TEST_CENTER_X)
+ .setCenterY(TEST_CENTER_Y).build();
+ setMagnificationActivated(TEST_DISPLAY, config);
+
+ mMagnificationProcessor.setMagnificationConfig(
+ TEST_DISPLAY, config, true, SERVICE_ID);
+
+ final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
+ TEST_DISPLAY);
+
+ assertConfigEquals(config, result);
+ }
+
+ private void setMagnificationActivated(int displayId, int configMode) {
+ setMagnificationActivated(displayId,
+ new MagnificationConfig.Builder().setMode(configMode).build());
+ }
+
+ private void setMagnificationActivated(int displayId, MagnificationConfig config) {
+ when(mMockMagnificationController.isActivated(displayId, config.getMode())).thenReturn(
+ true);
+ mMagnificationProcessor.setMagnificationConfig(displayId, config, false, SERVICE_ID);
+ if (config.getMode() == FULLSCREEN_MODE) {
+ when(mMockMagnificationController.isActivated(displayId, WINDOW_MODE)).thenReturn(
+ false);
+ mFullScreenMagnificationControllerStub.resetAndStubMethods();
+ mMockFullScreenMagnificationController.setScaleAndCenter(displayId, config.getScale(),
+ config.getCenterX(), config.getCenterY(), true, SERVICE_ID);
+ } else if (config.getMode() == WINDOW_MODE) {
+ when(mMockMagnificationController.isActivated(displayId, FULLSCREEN_MODE)).thenReturn(
+ false);
+ mWindowMagnificationManagerStub.resetAndStubMethods();
+ mMockWindowMagnificationManager.enableWindowMagnification(displayId, config.getScale(),
+ config.getCenterX(), config.getCenterY());
+ }
+ }
+
+ private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig actual) {
+ assertEquals(expected.getMode(), actual.getMode());
+ assertEquals(expected.getScale(), actual.getScale(), 0);
+ assertEquals(expected.getCenterX(), actual.getCenterX(), 0);
+ assertEquals(expected.getCenterY(), actual.getCenterY(), 0);
+ }
+
+ private static class FullScreenMagnificationControllerStub {
+ private final FullScreenMagnificationController mScreenMagnificationController;
+ private float mScale = 1.0f;
+ private float mCenterX = 0;
+ private float mCenterY = 0;
+ private boolean mIsRegistered = false;
+
+ FullScreenMagnificationControllerStub(
+ FullScreenMagnificationController screenMagnificationController) {
+ mScreenMagnificationController = screenMagnificationController;
+ }
+
+ private void stubMethods() {
+ doAnswer(invocation -> mScale).when(mScreenMagnificationController).getScale(
+ TEST_DISPLAY);
+ doAnswer(invocation -> mCenterX).when(mScreenMagnificationController).getCenterX(
+ TEST_DISPLAY);
+ doAnswer(invocation -> mCenterY).when(mScreenMagnificationController).getCenterY(
+ TEST_DISPLAY);
+ doAnswer(invocation -> mIsRegistered).when(mScreenMagnificationController).isRegistered(
+ TEST_DISPLAY);
+ Answer enableMagnificationStubAnswer = invocation -> {
+ mScale = invocation.getArgument(1);
+ mCenterX = invocation.getArgument(2);
+ mCenterY = invocation.getArgument(3);
+ return true;
+ };
+ doAnswer(enableMagnificationStubAnswer).when(
+ mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY), anyFloat(),
+ anyFloat(), anyFloat(), eq(true), eq(SERVICE_ID));
+
+ Answer registerStubAnswer = invocation -> {
+ mIsRegistered = true;
+ return true;
+ };
+ doAnswer(registerStubAnswer).when(
+ mScreenMagnificationController).register(eq(TEST_DISPLAY));
+
+ Answer unregisterStubAnswer = invocation -> {
+ mIsRegistered = false;
+ return true;
+ };
+ doAnswer(unregisterStubAnswer).when(
+ mScreenMagnificationController).unregister(eq(TEST_DISPLAY));
+ }
+
+ public void resetAndStubMethods() {
+ Mockito.reset(mScreenMagnificationController);
+ stubMethods();
+ }
+ }
+
+ private static class WindowMagnificationManagerStub {
+ private final WindowMagnificationManager mWindowMagnificationManager;
+ private float mScale = 1.0f;
+ private float mCenterX = 0;
+ private float mCenterY = 0;
+
+ WindowMagnificationManagerStub(
+ WindowMagnificationManager windowMagnificationManager) {
+ mWindowMagnificationManager = windowMagnificationManager;
+ }
+
+ private void stubMethods() {
+ doAnswer(invocation -> mScale).when(mWindowMagnificationManager).getScale(
+ TEST_DISPLAY);
+ doAnswer(invocation -> mCenterX).when(mWindowMagnificationManager).getCenterX(
+ TEST_DISPLAY);
+ doAnswer(invocation -> mCenterY).when(mWindowMagnificationManager).getCenterY(
+ TEST_DISPLAY);
+ Answer enableWindowMagnificationStubAnswer = invocation -> {
+ mScale = invocation.getArgument(1);
+ mCenterX = invocation.getArgument(2);
+ mCenterY = invocation.getArgument(3);
+ return true;
+ };
+ doAnswer(enableWindowMagnificationStubAnswer).when(
+ mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
+ anyFloat(), anyFloat(), anyFloat());
+ }
+
+ public void resetAndStubMethods() {
+ Mockito.reset(mWindowMagnificationManager);
+ stubMethods();
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index 15ba358..02c0aca 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
@@ -377,6 +377,17 @@
}
@Test
+ public void resetMagnification_enabled_windowMagnifierDisabled() {
+ mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+ mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3f, NaN, NaN);
+ assertTrue(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+
+ mWindowMagnificationManager.reset(TEST_DISPLAY);
+
+ assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
+ }
+
+ @Test
public void onScreenOff_windowMagnifierIsEnabled_removeButtonAndDisableWindowMagnification()
throws RemoteException {
mWindowMagnificationManager.requestConnection(true);
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/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/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/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index b76c279..ea0c073 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -207,6 +207,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/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 117680b..809b2f9 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -48,13 +48,13 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
{
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
.build();
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -79,7 +79,7 @@
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
.build();
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -110,13 +110,13 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
{
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
.build();
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -142,7 +142,7 @@
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
.build();
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -174,13 +174,13 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
{
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
.build();
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -203,7 +203,7 @@
}
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
.build();
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
@@ -236,13 +236,13 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(false)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
{
ConfigurationInternal autoOnConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
.build();
assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
@@ -266,7 +266,7 @@
}
{
ConfigurationInternal autoOffConfig = new ConfigurationInternal.Builder(baseConfig)
- .setAutoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
.build();
assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
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 ac2b27f..19269e0 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());
@@ -369,16 +372,16 @@
}
private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
- // Default geo detection settings from auto detection settings - they are not important to
- // the tests.
+ // Default geo detection settings from the auto detection setting - they are not important
+ // to the tests.
final boolean geoDetectionEnabled = autoDetectionEnabled;
return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
- .setAutoDetectionEnabled(autoDetectionEnabled)
- .setLocationEnabled(geoDetectionEnabled)
- .setGeoDetectionEnabled(geoDetectionEnabled)
+ .setAutoDetectionEnabledSetting(autoDetectionEnabled)
+ .setLocationEnabledSetting(geoDetectionEnabled)
+ .setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index e6036c4..91e8d16 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -89,9 +89,9 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(false)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
.build();
private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
@@ -99,9 +99,9 @@
.setUserConfigAllowed(false)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
@@ -109,9 +109,9 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(false)
.setGeoDetectionFeatureSupported(false)
- .setAutoDetectionEnabled(false)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
.build();
private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
@@ -119,9 +119,9 @@
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(false)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(false)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
@@ -129,9 +129,9 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(false)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(false)
.build();
private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
@@ -139,9 +139,9 @@
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
.setUserConfigAllowed(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
@@ -531,7 +531,7 @@
boolean geoDetectionEnabled) {
ConfigurationInternal geoTzEnabledConfig =
new ConfigurationInternal.Builder(CONFIG_AUTO_ENABLED_GEO_DISABLED)
- .setGeoDetectionEnabled(geoDetectionEnabled)
+ .setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID)
@@ -792,8 +792,8 @@
// Update the config and confirm that the config metrics state updates also.
expectedInternalConfig = new ConfigurationInternal.Builder(expectedInternalConfig)
- .setAutoDetectionEnabled(true)
- .setGeoDetectionEnabled(true)
+ .setAutoDetectionEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(true)
.build();
expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
@@ -963,7 +963,7 @@
Script simulateSetAutoMode(boolean autoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
- .setAutoDetectionEnabled(autoDetectionEnabled)
+ .setAutoDetectionEnabledSetting(autoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
return this;
@@ -975,7 +975,7 @@
Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
mFakeEnvironment.getCurrentUserConfigurationInternal())
- .setGeoDetectionEnabled(geoDetectionEnabled)
+ .setGeoDetectionEnabledSetting(geoDetectionEnabled)
.build();
simulateConfigurationInternalChange(newConfig);
return this;
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 16ac1d6..e3da90e 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -41,14 +41,14 @@
}
private static ConfigurationInternal createUserConfig(
- @UserIdInt int userId, boolean geoDetectionEnabled) {
+ @UserIdInt int userId, boolean geoDetectionEnabledSetting) {
return new ConfigurationInternal.Builder(userId)
.setUserConfigAllowed(true)
.setTelephonyDetectionFeatureSupported(true)
.setGeoDetectionFeatureSupported(true)
- .setAutoDetectionEnabled(true)
- .setLocationEnabled(true)
- .setGeoDetectionEnabled(geoDetectionEnabled)
+ .setAutoDetectionEnabledSetting(true)
+ .setLocationEnabledSetting(true)
+ .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
.build();
}
}
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 7d24a2f..a9cbad2 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -16,6 +16,19 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_PHYSICAL_EMULATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+import static android.os.Vibrator.VIBRATION_INTENSITY_HIGH;
+import static android.os.Vibrator.VIBRATION_INTENSITY_LOW;
+import static android.os.Vibrator.VIBRATION_INTENSITY_MEDIUM;
+import static android.os.Vibrator.VIBRATION_INTENSITY_OFF;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,9 +52,7 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
-import android.os.VibrationAttributes;
import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
@@ -70,16 +81,19 @@
public class VibrationSettingsTest {
private static final int UID = 1;
- private static final int USER_OPERATION_TIMEOUT_MILLIS = 60_000; // 1 min
private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
.setBatterySaverEnabled(true).build();
- @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
- @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
- @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
- @Mock private PowerManagerInternal mPowerManagerInternalMock;
+ @Mock
+ private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternalMock;
private TestLooper mTestLooper;
private ContextWrapper mContextSpy;
@@ -129,14 +143,12 @@
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
setGlobalSetting(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_ALARMS);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- verify(mListenerMock, times(7)).onChange();
+ verify(mListenerMock, times(8)).onChange();
}
@Test
@@ -171,89 +183,83 @@
VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
new Handler(mTestLooper.getLooper()));
- assertFalse(vibrationSettings.shouldVibrateForRingerMode(
- VibrationAttributes.USAGE_RINGTONE));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
- VibrationAttributes.USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+ assertFalse(vibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
}
@Test
public void shouldVibrateForRingerMode_withoutRingtoneUsage_returnsTrue() {
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(VibrationAttributes.USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
- VibrationAttributes.USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_ALARM));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_TOUCH));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_NOTIFICATION));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_COMMUNICATION_REQUEST));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_HARDWARE_FEEDBACK));
}
@Test
public void shouldVibrateForRingerMode_withVibrateWhenRinging_ignoreSettingsForSilentMode() {
- int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
}
@Test
public void shouldVibrateForRingerMode_withApplyRampingRinger_ignoreSettingsForSilentMode() {
- int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
}
@Test
public void shouldVibrateForRingerMode_withAllSettingsOff_onlyVibratesForVibrateMode() {
- int usageRingtone = VibrationAttributes.USAGE_RINGTONE;
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
- assertTrue(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertTrue(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_SILENT);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_MAX);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
setRingerMode(AudioManager.RINGER_MODE_NORMAL);
- assertFalse(mVibrationSettings.shouldVibrateForRingerMode(usageRingtone));
+ assertFalse(mVibrationSettings.shouldVibrateForRingerMode(USAGE_RINGTONE));
}
@Test
public void shouldVibrateForUid_withForegroundOnlyUsage_returnsTrueWhInForeground() {
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+ assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
mVibrationSettings.mUidObserver.onUidStateChanged(
UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- assertFalse(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_TOUCH));
+ assertFalse(mVibrationSettings.shouldVibrateForUid(UID, USAGE_TOUCH));
}
@Test
@@ -261,38 +267,32 @@
mVibrationSettings.mUidObserver.onUidStateChanged(
UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID,
- VibrationAttributes.USAGE_NOTIFICATION));
- assertTrue(mVibrationSettings.shouldVibrateForUid(UID, VibrationAttributes.USAGE_RINGTONE));
+ assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_ALARM));
+ assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_COMMUNICATION_REQUEST));
+ assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_NOTIFICATION));
+ assertTrue(mVibrationSettings.shouldVibrateForUid(UID, USAGE_RINGTONE));
}
@Test
public void shouldVibrateForPowerMode_withLowPowerAndAllowedUsage_returnTrue() {
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_ALARM));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
- VibrationAttributes.USAGE_RINGTONE));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
- VibrationAttributes.USAGE_COMMUNICATION_REQUEST));
+ assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_ALARM));
+ assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_RINGTONE));
+ assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_COMMUNICATION_REQUEST));
}
@Test
public void shouldVibrateForPowerMode_withRestrictedUsage_returnsFalseWhileInLowPowerMode() {
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
- assertTrue(mVibrationSettings.shouldVibrateForPowerMode(
- VibrationAttributes.USAGE_NOTIFICATION));
+ assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+ assertTrue(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- assertFalse(mVibrationSettings.shouldVibrateForPowerMode(VibrationAttributes.USAGE_TOUCH));
- assertFalse(mVibrationSettings.shouldVibrateForPowerMode(
- VibrationAttributes.USAGE_NOTIFICATION));
+ assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_TOUCH));
+ assertFalse(mVibrationSettings.shouldVibrateForPowerMode(USAGE_NOTIFICATION));
}
@Test
@@ -324,108 +324,128 @@
@Test
public void getDefaultIntensity_beforeSystemReady_returnsMediumToAllExceptAlarm() {
- mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
- mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
+ mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+ mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_HIGH);
+ mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_HIGH);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
VibrationSettings vibrationSettings = new VibrationSettings(mContextSpy,
new Handler(mTestLooper.getLooper()));
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- vibrationSettings.getDefaultIntensity(
- VibrationAttributes.USAGE_PHYSICAL_EMULATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- vibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ vibrationSettings.getDefaultIntensity(USAGE_ALARM));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ vibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
}
@Test
public void getDefaultIntensity_returnsIntensityFromVibratorService() {
- mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_HIGH);
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
- mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+ mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_HIGH);
+ mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_MEDIUM);
+ mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_LOW);
- setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_ALARM));
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_UNKNOWN));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getDefaultIntensity(
- VibrationAttributes.USAGE_PHYSICAL_EMULATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
- mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getDefaultIntensity(USAGE_ALARM));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getDefaultIntensity(USAGE_TOUCH));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getDefaultIntensity(USAGE_HARDWARE_FEEDBACK));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getDefaultIntensity(USAGE_PHYSICAL_EMULATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getDefaultIntensity(USAGE_NOTIFICATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getDefaultIntensity(USAGE_UNKNOWN));
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getDefaultIntensity(USAGE_RINGTONE));
}
@Test
public void getCurrentIntensity_returnsIntensityFromSettings() {
- mFakeVibrator.setDefaultHapticFeedbackIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
- mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
- mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
+ mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_OFF);
+ mFakeVibrator.setDefaultNotificationVibrationIntensity(VIBRATION_INTENSITY_OFF);
+ mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ setUserSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_LOW);
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_LOW);
+ VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_ALARM));
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_UNKNOWN));
- assertEquals(Vibrator.VIBRATION_INTENSITY_MEDIUM,
- mVibrationSettings.getCurrentIntensity(
- VibrationAttributes.USAGE_PHYSICAL_EMULATION));
- assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+ assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_ALARM));
+ assertEquals(VIBRATION_INTENSITY_HIGH, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getCurrentIntensity(USAGE_NOTIFICATION));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getCurrentIntensity(USAGE_UNKNOWN));
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
}
@Test
public void getCurrentIntensity_updateTriggeredAfterUserSwitched() {
- mFakeVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
- Vibrator.VIBRATION_INTENSITY_HIGH);
- assertEquals(Vibrator.VIBRATION_INTENSITY_HIGH,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+ mFakeVibrator.setDefaultRingVibrationIntensity(VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
// Switching user is not working with FakeSettingsProvider.
// Testing the broadcast flow manually.
Settings.System.putIntForUser(mContextSpy.getContentResolver(),
- Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW,
+ Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
UserHandle.USER_CURRENT);
mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
new Intent(Intent.ACTION_USER_SWITCHED));
- assertEquals(Vibrator.VIBRATION_INTENSITY_LOW,
- mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+ }
+
+ @Test
+ public void getCurrentIntensity_noHardwareFeedbackValueUsesHapticFeedbackValue() {
+ mFakeVibrator.setDefaultHapticFeedbackIntensity(VIBRATION_INTENSITY_MEDIUM);
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ assertEquals(VIBRATION_INTENSITY_OFF, mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+ // If haptic feedback is off, fallback to default value.
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+ assertEquals(VIBRATION_INTENSITY_MEDIUM,
+ mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
+
+ // Switching user is not working with FakeSettingsProvider.
+ // Testing the broadcast flow manually.
+ Settings.System.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_HIGH,
+ UserHandle.USER_CURRENT);
+ mVibrationSettings.mUserReceiver.onReceive(mContextSpy,
+ new Intent(Intent.ACTION_USER_SWITCHED));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getCurrentIntensity(USAGE_TOUCH));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getCurrentIntensity(USAGE_HARDWARE_FEEDBACK));
+ assertEquals(VIBRATION_INTENSITY_HIGH,
+ mVibrationSettings.getCurrentIntensity(USAGE_PHYSICAL_EMULATION));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 1266b2e..afc2b87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -854,12 +854,19 @@
ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
DEFAULT_USER_ID);
+
+ // committing empty locales, when no config is set should return false.
+ assertFalse(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit());
+
// committing new configuration returns true;
assertTrue(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.commit());
// applying the same configuration returns false.
assertFalse(packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
.commit());
+
+ // committing empty locales and undefined nightMode should return true (deletes the
+ // pre-existing record) if some config was previously set.
assertTrue(packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit());
}
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/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index f366f57..caaf4e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,7 +54,6 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.window.ITransitionPlayer;
import androidx.test.filters.SmallTest;
@@ -313,11 +312,7 @@
wallpaperWindow.setHasSurface(true);
// Set-up mock shell transitions
- final IBinder mockBinder = mock(IBinder.class);
- final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class);
- doReturn(mockBinder).when(mockPlayer).asBinder();
- mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer,
- null /* appThread */);
+ registerTestTransitionPlayer();
Transition transit =
mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN);
@@ -338,10 +333,21 @@
assertFalse(token.isVisibleRequested());
assertTrue(token.isVisible());
- transit.onTransactionReady(transit.getSyncId(), mock(SurfaceControl.Transaction.class));
- transit.finishTransition();
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ token.finishSync(t, false /* cancel */);
+ transit.onTransactionReady(transit.getSyncId(), t);
+ dc.mTransitionController.finishTransition(transit);
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
+
+ // Assume wallpaper was visible. When transaction is ready without wallpaper target,
+ // wallpaper should be requested to be invisible.
+ token.setVisibility(true);
+ transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE);
+ dc.mTransitionController.collect(token);
+ transit.onTransactionReady(transit.getSyncId(), t);
+ assertFalse(token.isVisibleRequested());
+ assertTrue(token.isVisible());
}
private WindowState createWallpaperTargetWindow(DisplayContent dc) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/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/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index f4f06fd..96c78bc 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -191,7 +191,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiver(mBroadcastReceiver, filter, null, handler,
- Context.RECEIVER_NOT_EXPORTED);
+ Context.RECEIVER_EXPORTED);
}
public boolean showSessionLocked(Bundle args, int flags,
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index 7d857a2..fabe612 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -856,9 +856,10 @@
mExecutor.execute(new Runnable() {
@Override
public void run() {
+ // TODO(b/207392528: use portIndex API once implemented)
int result =
- EuiccService.this.onSwitchToSubscriptionWithPort(
- slotId, portIndex, iccid, forceDeactivateSim);
+ EuiccService.this.onSwitchToSubscription(
+ slotId, iccid, forceDeactivateSim);
try {
callback.onComplete(result);
} catch (RemoteException e) {
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 23cf511..e88106c 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -1,6 +1,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
import android.telecom.Connection;
import android.telephony.data.ApnSetting;
@@ -664,4 +666,59 @@
TelephonyManager.THERMAL_MITIGATION_RESULT_INVALID_STATE,
TelephonyManager.THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR})
public @interface ThermalMitigationResult {}
+
+ /**
+ * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+ * android.net.NetworkCapabilities.NetCapability here. Must update here when new capabilities
+ * are added in {@link NetworkCapabilities}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+ NetworkCapabilities.NET_CAPABILITY_MMS,
+ NetworkCapabilities.NET_CAPABILITY_SUPL,
+ NetworkCapabilities.NET_CAPABILITY_DUN,
+ NetworkCapabilities.NET_CAPABILITY_FOTA,
+ NetworkCapabilities.NET_CAPABILITY_IMS,
+ NetworkCapabilities.NET_CAPABILITY_CBS,
+ NetworkCapabilities.NET_CAPABILITY_WIFI_P2P,
+ NetworkCapabilities.NET_CAPABILITY_IA,
+ NetworkCapabilities.NET_CAPABILITY_RCS,
+ NetworkCapabilities.NET_CAPABILITY_XCAP,
+ NetworkCapabilities.NET_CAPABILITY_EIMS,
+ NetworkCapabilities.NET_CAPABILITY_NOT_METERED,
+ NetworkCapabilities.NET_CAPABILITY_INTERNET,
+ NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED,
+ NetworkCapabilities.NET_CAPABILITY_TRUSTED,
+ NetworkCapabilities.NET_CAPABILITY_NOT_VPN,
+ NetworkCapabilities.NET_CAPABILITY_VALIDATED,
+ NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL,
+ NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
+ NetworkCapabilities.NET_CAPABILITY_FOREGROUND,
+ NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED,
+ NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED,
+ NetworkCapabilities.NET_CAPABILITY_OEM_PAID,
+ NetworkCapabilities.NET_CAPABILITY_MCX,
+ NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+ NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE,
+ NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL,
+ NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED,
+ NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
+ NetworkCapabilities.NET_CAPABILITY_VSIM,
+ NetworkCapabilities.NET_CAPABILITY_BIP,
+ NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT,
+ })
+ public @interface NetCapability { }
+
+ /**
+ * Per Android API guideline 8.15, annotation can't be public APIs. So duplicate
+ * android.net.NetworkAgent.ValidationStatus here. Must update here when new validation status
+ * are added in {@link NetworkAgent}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "VALIDATION_STATUS_" }, value = {
+ NetworkAgent.VALIDATION_STATUS_VALID,
+ NetworkAgent.VALIDATION_STATUS_NOT_VALID
+ })
+ public @interface ValidationStatus {}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3f0f50c..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",
@@ -5951,9 +5957,10 @@
"capabilities=eims, retry_interval=1000, maximum_retries=20",
"fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|"
+ "2254, maximum_retries=0", // No retry for those causes
- "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2000, "
- + "backoff=true, maximum_retries=13",
- "capabilities=mms|supl|cbs, retry_interval=2000"
+ "capabilities=mms|supl|cbs, retry_interval=2000",
+ "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
+ + "5000|10000|15000|20000|40000|60000|120000|240000|"
+ + "600000|1200000|1800000, maximum_retries=20"
});
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY, new String[0]);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 88efe1f..56bf303 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1076,6 +1076,13 @@
*/
public static final int SERVICE_TEMPORARILY_UNAVAILABLE = 0x10009;
+ /**
+ * The request is not supported by the vendor.
+ *
+ * @hide
+ */
+ public static final int REQUEST_NOT_SUPPORTED = 0x1000A;
+
private static final Map<Integer, String> sFailCauseMap;
static {
sFailCauseMap = new HashMap<>();
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index 7dfe450..0dfab18 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -156,9 +156,11 @@
@Nullable
@Deprecated
public String getIccId() {
- if (mIccIdAccessRestricted) {
- throw new UnsupportedOperationException("getIccId from UiccPortInfo");
- }
+ // Temporarily bypassing exception
+ // TODO: add exception once refactoring completed.
+ //if (mIccIdAccessRestricted) {
+ // throw new UnsupportedOperationException("getIccId from UiccPortInfo");
+ //}
//always return ICCID from first port.
return getPorts().stream().findFirst().get().getIccId();
}
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 2b1c8c8..a8668e7 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -159,9 +159,11 @@
*/
@Deprecated
public boolean getIsActive() {
- if (mLogicalSlotAccessRestricted) {
- throw new UnsupportedOperationException("get port status from UiccPortInfo");
- }
+ // Temporarily bypassing exception
+ // TODO: add exception once refactoring completed.
+ //if (mLogicalSlotAccessRestricted) {
+ // throw new UnsupportedOperationException("get port status from UiccPortInfo");
+ //}
//always return status from first port.
return getPorts().stream().findFirst().get().isActive();
}
@@ -196,9 +198,11 @@
*/
@Deprecated
public int getLogicalSlotIdx() {
- if (mLogicalSlotAccessRestricted) {
- throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
- }
+ // Temporarily bypassing exception
+ // TODO: add exception once refactoring completed.
+ //if (mLogicalSlotAccessRestricted) {
+ // throw new UnsupportedOperationException("get logical slot index from UiccPortInfo");
+ //}
//always return logical slot index from first port.
//portList always have at least one element.
return getPorts().stream().findFirst().get().getLogicalSlotIndex();
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index 93903d2..c1d16a9 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -18,13 +18,16 @@
import static android.telephony.data.ApnSetting.ProtocolType;
+import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.NetworkCapabilities;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.NetCapability;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.NetworkTypeBitMask;
import android.telephony.data.ApnSetting.AuthType;
@@ -66,7 +69,13 @@
private final @Nullable TrafficDescriptor mTrafficDescriptor;
- private final boolean mPreferred;
+ private boolean mPreferred;
+
+ /**
+ * The last timestamp of this data profile being used for data network setup. Never add this
+ * to {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ private @ElapsedRealtimeLong long mSetupTimestamp;
private DataProfile(@NonNull Builder builder) {
mApnSetting = builder.mApnSetting;
@@ -99,6 +108,7 @@
mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
mPreferred = source.readBoolean();
+ mSetupTimestamp = source.readLong();
}
/**
@@ -291,6 +301,16 @@
}
/**
+ * Set the preferred flag for the data profile.
+ *
+ * @param preferred {@code true} if this data profile is preferred for internet.
+ * @hide
+ */
+ public void setPreferred(boolean preferred) {
+ mPreferred = preferred;
+ }
+
+ /**
* @return {@code true} if this data profile was used to bring up the last default
* (i.e internet) data connection successfully, or the one chosen by the user in Settings'
* APN editor. For one carrier there can be only one profiled preferred.
@@ -315,6 +335,94 @@
return mTrafficDescriptor;
}
+ /**
+ * Check if this data profile can satisfy certain network capabilities
+ *
+ * @param networkCapabilities The network capabilities. Note that the non-APN-type capabilities
+ * will be ignored.
+ *
+ * @return {@code true} if this data profile can satisfy the given network capabilities.
+ * @hide
+ */
+ public boolean canSatisfy(@NonNull @NetCapability int[] networkCapabilities) {
+ if (mApnSetting != null) {
+ for (int netCap : networkCapabilities) {
+ if (!canSatisfy(netCap)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if this data profile can satisfy a certain network capability.
+ *
+ * @param networkCapability The network capability. Note that the non-APN-type capability
+ * will always be satisfied.
+ * @return {@code true} if this data profile can satisfy the given network capability.
+ * @hide
+ */
+ public boolean canSatisfy(@NetCapability int networkCapability) {
+ return mApnSetting != null && mApnSetting.canHandleType(
+ networkCapabilityToApnType(networkCapability));
+ }
+
+ /**
+ * Convert network capability into APN type.
+ *
+ * @param networkCapability Network capability.
+ * @return APN type.
+ * @hide
+ */
+ private static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) {
+ switch (networkCapability) {
+ case NetworkCapabilities.NET_CAPABILITY_MMS:
+ return ApnSetting.TYPE_MMS;
+ case NetworkCapabilities.NET_CAPABILITY_SUPL:
+ return ApnSetting.TYPE_SUPL;
+ case NetworkCapabilities.NET_CAPABILITY_DUN:
+ return ApnSetting.TYPE_DUN;
+ case NetworkCapabilities.NET_CAPABILITY_FOTA:
+ return ApnSetting.TYPE_FOTA;
+ case NetworkCapabilities.NET_CAPABILITY_IMS:
+ return ApnSetting.TYPE_IMS;
+ case NetworkCapabilities.NET_CAPABILITY_CBS:
+ return ApnSetting.TYPE_CBS;
+ case NetworkCapabilities.NET_CAPABILITY_XCAP:
+ return ApnSetting.TYPE_XCAP;
+ case NetworkCapabilities.NET_CAPABILITY_EIMS:
+ return ApnSetting.TYPE_EMERGENCY;
+ case NetworkCapabilities.NET_CAPABILITY_INTERNET:
+ return ApnSetting.TYPE_DEFAULT;
+ case NetworkCapabilities.NET_CAPABILITY_MCX:
+ return ApnSetting.TYPE_MCX;
+ case NetworkCapabilities.NET_CAPABILITY_IA:
+ return ApnSetting.TYPE_IA;
+ default:
+ return ApnSetting.TYPE_NONE;
+ }
+ }
+
+ /**
+ * Set the timestamp of this data profile being used for data network setup.
+ *
+ * @hide
+ */
+ public void setLastSetupTimestamp(@ElapsedRealtimeLong long timestamp) {
+ mSetupTimestamp = timestamp;
+ }
+
+ /**
+ * @return the timestamp of this data profile being used for data network setup.
+ *
+ * @hide
+ */
+ public @ElapsedRealtimeLong long getLastSetupTimestamp() {
+ return mSetupTimestamp;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -323,8 +431,8 @@
@NonNull
@Override
public String toString() {
- return "DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
- + mPreferred;
+ return "[DataProfile=" + mApnSetting + ", " + mTrafficDescriptor + ", preferred="
+ + mPreferred + "]";
}
@Override
@@ -333,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/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