Merge "Preserve the controlling state of appearance and behavior"
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5b86e8d..4fb283a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -144,15 +144,13 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizePrimarySplitScreen(@NonNull android.graphics.Rect, @NonNull android.graphics.Rect);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
- method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingModeSplitScreenPrimary(int, int, boolean, boolean, android.graphics.Rect, boolean) throws java.lang.SecurityException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean setTaskWindowingModeSplitScreenPrimary(int, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
method public static boolean supportsMultiWindow(android.content.Context);
method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
field public static final int DEFAULT_MINIMAL_SPLIT_SCREEN_DISPLAY_SIZE_DP = 440; // 0x1b8
field public static final int INVALID_STACK_ID = -1; // 0xffffffff
- field public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1; // 0x1
- field public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0; // 0x0
}
public class ActivityView extends android.view.ViewGroup {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a2b9157..f541e1a 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,7 +17,6 @@
package android.app;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
@@ -256,13 +255,6 @@
"android.activity.freezeRecentTasksReordering";
/**
- * Where the split-screen-primary stack should be positioned.
- * @hide
- */
- private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
- "android:activity.splitScreenCreateMode";
-
- /**
* Determines whether to disallow the outgoing activity from entering picture-in-picture as the
* result of a new activity being launched.
* @hide
@@ -373,7 +365,6 @@
private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
private int mLaunchTaskId = -1;
private int mPendingIntentLaunchFlags;
- private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
private boolean mLockTaskMode = false;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mApplyActivityFlagsForBubbles;
@@ -1049,8 +1040,6 @@
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false);
mFreezeRecentTasksReordering = opts.getBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, false);
- mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
- SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
mApplyActivityFlagsForBubbles = opts.getBoolean(
@@ -1469,14 +1458,9 @@
}
/** @hide */
- public int getSplitScreenCreateMode() {
- return mSplitScreenCreateMode;
- }
-
- /** @hide */
@UnsupportedAppUsage
public void setSplitScreenCreateMode(int splitScreenCreateMode) {
- mSplitScreenCreateMode = splitScreenCreateMode;
+ // Remove this method after @UnsupportedAppUsage can be removed.
}
/** @hide */
@@ -1709,9 +1693,6 @@
if (mFreezeRecentTasksReordering) {
b.putBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, mFreezeRecentTasksReordering);
}
- if (mSplitScreenCreateMode != SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT) {
- b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
- }
if (mDisallowEnterPictureInPictureWhileLaunching) {
b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
mDisallowEnterPictureInPictureWhileLaunching);
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 2060252..fbc3b0d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
@@ -58,20 +60,6 @@
public static final int INVALID_TASK_ID = -1;
/**
- * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
- * specifies the position of the created docked stack at the top half of the screen if
- * in portrait mode or at the left half of the screen if in landscape mode.
- */
- public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
-
- /**
- * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
- * specifies the position of the created docked stack at the bottom half of the screen if
- * in portrait mode or at the right half of the screen if in landscape mode.
- */
- public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
-
- /**
* Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
* that the resize doesn't need to preserve the window, and can be skipped if bounds
* is unchanged. This mode is used by window manager in most cases.
@@ -199,28 +187,12 @@
/**
* Moves the input task to the primary-split-screen stack.
* @param taskId Id of task to move.
- * @param createMode The mode the primary split screen stack should be created in if it doesn't
- * exist already. See
- * {@link ActivityTaskManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
- * and
- * {@link android.app.ActivityManager
- * #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
* @param toTop If the task and stack should be moved to the top.
- * @param animate Whether we should play an animation for the moving the task
- * @param initialBounds If the primary stack gets created, it will use these bounds for the
- * docked stack. Pass {@code null} to use default bounds.
- * @param showRecents If the recents activity should be shown on the other side of the task
- * going into split-screen mode.
* @return Whether the task was successfully put into splitscreen.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
- boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException {
- try {
- return getService().setTaskWindowingModeSplitScreenPrimary(taskId, toTop);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop) {
+ return setTaskWindowingMode(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, toTop);
}
/**
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 523c155..1d65711 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -218,7 +218,7 @@
*/
boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
- boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop);
+
/**
* Removes root tasks in the input windowing modes from the system if they are of activity type
* ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index ddcfb92..b1ca12cd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -18,6 +18,9 @@
import android.annotation.IntDef;
import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
@@ -27,6 +30,7 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.Printer;
import java.lang.annotation.Retention;
@@ -866,6 +870,47 @@
};
/**
+ * This change id forces the packages it is applied to to be resizable. We only allow resizing
+ * in fullscreen windowing mode, but not forcing the app into resizable multi-windowing mode.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ public static final long FORCE_RESIZE_APP = 174042936L; // number refers to buganizer id
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity does not
+ * support size changes.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_UNSUPPORTED = 0;
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity supports size
+ * changes due to the android.supports_size_changes metadata flag being set either on
+ * application or on activity level.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_SUPPORTED_METADATA = 1;
+
+ /**
+ * Return value for {@link #supportsSizeChanges()} indicating that this activity has been
+ * overridden to support size changes through the compat framework change id
+ * {@link #FORCE_RESIZE_APP}.
+ * @hide
+ */
+ public static final int SIZE_CHANGES_SUPPORTED_OVERRIDE = 2;
+
+ /** @hide */
+ @IntDef(prefix = { "SIZE_CHANGES_" }, value = {
+ SIZE_CHANGES_UNSUPPORTED,
+ SIZE_CHANGES_SUPPORTED_METADATA,
+ SIZE_CHANGES_SUPPORTED_OVERRIDE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SizeChangesSupportMode {}
+
+ /**
* Convert Java change bits to native.
*
* @hide
@@ -1146,6 +1191,25 @@
return (flags & FLAG_SUPPORTS_PICTURE_IN_PICTURE) != 0;
}
+ /**
+ * Returns whether the activity supports size changes.
+ * @hide
+ */
+ @SizeChangesSupportMode
+ public int supportsSizeChanges() {
+ if (supportsSizeChanges) {
+ return SIZE_CHANGES_SUPPORTED_METADATA;
+ }
+
+ if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP,
+ applicationInfo.packageName,
+ UserHandle.getUserHandleForUid(applicationInfo.uid))) {
+ return SIZE_CHANGES_SUPPORTED_OVERRIDE;
+ }
+
+ return SIZE_CHANGES_UNSUPPORTED;
+ }
+
/** @hide */
@UnsupportedAppUsage
public static boolean isResizeableMode(int mode) {
@@ -1186,6 +1250,20 @@
}
}
+ /** @hide */
+ public static String sizeChangesSupportModeToString(@SizeChangesSupportMode int mode) {
+ switch (mode) {
+ case SIZE_CHANGES_UNSUPPORTED:
+ return "SIZE_CHANGES_UNSUPPORTED";
+ case SIZE_CHANGES_SUPPORTED_METADATA:
+ return "SIZE_CHANGES_SUPPORTED_METADATA";
+ case SIZE_CHANGES_SUPPORTED_OVERRIDE:
+ return "SIZE_CHANGES_SUPPORTED_OVERRIDE";
+ default:
+ return "unknown=" + mode;
+ }
+ }
+
public void dump(Printer pw, String prefix) {
dump(pw, prefix, DUMP_FLAG_ALL);
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a89de01..b846142 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -985,6 +985,15 @@
*/
public abstract void getDeferredJobsLineLocked(StringBuilder sb, int which);
+ /**
+ * Returns the measured energy in microjoules that the display consumed while the screen
+ * was on and uid active.
+ * Will return {@link #ENERGY_DATA_UNAVAILABLE} if data is unavailable
+ *
+ * {@hide}
+ */
+ public abstract long getScreenOnEnergy();
+
public static abstract class Sensor {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 58268e2..0fe8894 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -68,6 +68,8 @@
*/
@SystemApi
public class DynamicSystemClient {
+ private static final String TAG = "DynamicSystemClient";
+
/** @hide */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_UNKNOWN,
@@ -92,8 +94,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface StatusChangedCause {}
- private static final String TAG = "DynSystemClient";
-
/** Listener for installation status updates. */
public interface OnStatusChangedListener {
/**
@@ -240,7 +240,7 @@
private class DynSystemServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName className, IBinder service) {
- Slog.v(TAG, "DynSystemService connected");
+ Slog.v(TAG, "onServiceConnected: " + className);
mService = new Messenger(service);
@@ -262,7 +262,7 @@
}
public void onServiceDisconnected(ComponentName className) {
- Slog.v(TAG, "DynSystemService disconnected");
+ Slog.v(TAG, "onServiceDisconnected: " + className);
mService = null;
}
}
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 7f01cad..e8e4785 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -269,4 +269,16 @@
throw new RuntimeException(e.toString());
}
}
+
+ /**
+ * Returns the suggested scratch partition size for overlayFS.
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+ public long suggestScratchSize() {
+ try {
+ return mService.suggestScratchSize();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
}
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index df0a69b..a5a40ad 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -125,4 +125,9 @@
* valid VBMeta block to retrieve the AVB key from.
*/
boolean getAvbPublicKey(out AvbPublicKey dst);
+
+ /**
+ * Returns the suggested scratch partition size for overlayFS.
+ */
+ long suggestScratchSize();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0fa0df6..93dff9f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -8648,6 +8648,15 @@
}
}
+ @Override
+ public long getScreenOnEnergy() {
+ if (mUidMeasuredEnergyStats == null) {
+ return ENERGY_DATA_UNAVAILABLE;
+ }
+ return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(
+ MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+ }
+
void initNetworkActivityLocked() {
detachIfNotNull(mNetworkByteActivityCounters);
mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index 8086ff2..e0d159b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -171,6 +171,7 @@
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
context.getPackageManager(), requestedBatterySipper);
long totalScreenMeasuredEnergyUJ = batteryStats.getScreenOnEnergy();
+ long uidScreenMeasuredEnergyUJ = requestedBatterySipper.uidObj.getScreenOnEnergy();
addEntry("Total power", EntryType.POWER,
requestedBatterySipper.totalSmearedPowerMah, totalSmearedPowerMah);
@@ -180,11 +181,12 @@
requestedBatterySipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
addEntry("Screen, smeared", EntryType.POWER,
requestedBatterySipper.screenPowerMah, totalScreenPower);
- if (totalScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
- final double measuredCharge = UJ_2_MAH * totalScreenMeasuredEnergyUJ;
- final double ratio = measuredCharge / totalScreenPower;
- addEntry("Screen, smeared (PowerStatsHal adjusted)", EntryType.POWER,
- requestedBatterySipper.screenPowerMah * ratio, measuredCharge);
+ if (uidScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE
+ && totalScreenMeasuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+ final double measuredCharge = UJ_2_MAH * uidScreenMeasuredEnergyUJ;
+ final double totalMeasuredCharge = UJ_2_MAH * totalScreenMeasuredEnergyUJ;
+ addEntry("Screen, measured", EntryType.POWER,
+ measuredCharge, totalMeasuredCharge);
}
addEntry("Other, smeared", EntryType.POWER,
requestedBatterySipper.proportionalSmearMah, totalProportionalSmearMah);
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index a3a3e7c..5031ff9 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -2,3 +2,10 @@
per-file *MotionEventTest.* = michaelwr@google.com, svv@google.com
per-file *KeyEventTest.* = michaelwr@google.com, svv@google.com
per-file VelocityTest.java = michaelwr@google.com, svv@google.com
+
+# WindowManager
+per-file *Display* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index a9d4094..f708298 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -60,7 +60,6 @@
byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem);
List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem);
void setCredentialManagementApp(String packageName, in AppUriAuthenticationPolicy policy);
- void updateCredentialManagementAppPolicy(in AppUriAuthenticationPolicy policy);
boolean hasCredentialManagementApp();
String getCredentialManagementAppPackageName();
AppUriAuthenticationPolicy getCredentialManagementAppPolicy();
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index c6e72b0..2f444b3 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -31,6 +31,7 @@
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
@@ -854,10 +855,26 @@
@WorkerThread
public static KeyChainConnection bindAsUser(@NonNull Context context, UserHandle user)
throws InterruptedException {
+ return bindAsUser(context, null, user);
+ }
+
+ /**
+ * Bind to KeyChainService in the target user.
+ * Caller should call unbindService on the result when finished.
+ *
+ * @throws InterruptedException if interrupted during binding.
+ * @throws AssertionError if unable to bind to KeyChainService.
+ * @hide
+ */
+ public static KeyChainConnection bindAsUser(@NonNull Context context, @Nullable Handler handler,
+ UserHandle user) throws InterruptedException {
+
if (context == null) {
throw new NullPointerException("context == null");
}
- ensureNotOnMainThread(context);
+ if (handler == null) {
+ ensureNotOnMainThread(context);
+ }
if (!UserManager.get(context).isUserUnlocked(user)) {
throw new IllegalStateException("User must be unlocked");
}
@@ -884,9 +901,19 @@
};
Intent intent = new Intent(IKeyChainService.class.getName());
ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
+ if (comp == null) {
+ throw new AssertionError("could not resolve KeyChainService");
+ }
intent.setComponent(comp);
- if (comp == null || !context.bindServiceAsUser(
- intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user)) {
+ final boolean bindSucceed;
+ if (handler != null) {
+ bindSucceed = context.bindServiceAsUser(
+ intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, handler, user);
+ } else {
+ bindSucceed = context.bindServiceAsUser(
+ intent, keyChainServiceConnection, Context.BIND_AUTO_CREATE, user);
+ }
+ if (!bindSucceed) {
throw new AssertionError("could not bind to KeyChainService");
}
countDownLatch.await();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index d88696d..6b79a36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -503,31 +503,31 @@
|| isSplitActive()) {
return false;
}
-
- // Try fetching the top running task.
- final List<RunningTaskInfo> runningTasks =
- ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
- if (runningTasks == null || runningTasks.isEmpty()) {
- return false;
- }
- // Note: The set of running tasks from the system is ordered by recency.
- final RunningTaskInfo topRunningTask = runningTasks.get(0);
- final int activityType = topRunningTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- return false;
- }
-
- if (!topRunningTask.supportsSplitScreenMultiWindow) {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- return false;
- }
-
- return ActivityTaskManager.getService().setTaskWindowingModeSplitScreenPrimary(
- topRunningTask.taskId, true /* onTop */);
} catch (RemoteException e) {
return false;
}
+
+ // Try fetching the top running task.
+ final List<RunningTaskInfo> runningTasks =
+ ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
+ if (runningTasks == null || runningTasks.isEmpty()) {
+ return false;
+ }
+ // Note: The set of running tasks from the system is ordered by recency.
+ final RunningTaskInfo topRunningTask = runningTasks.get(0);
+ final int activityType = topRunningTask.getActivityType();
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ return false;
+ }
+
+ if (!topRunningTask.supportsSplitScreenMultiWindow) {
+ Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
+ Toast.LENGTH_SHORT).show();
+ return false;
+ }
+
+ return ActivityTaskManager.getInstance().setTaskWindowingModeSplitScreenPrimary(
+ topRunningTask.taskId, true /* onTop */);
}
@Override
diff --git a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl b/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl
deleted file mode 100644
index 16aa848..0000000
--- a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProvider.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-/**
- * Binder interface for location time zone provider implementations. Do not implement this
- * directly, extend {@link com.android.location.timezone.provider.LocationTimeZoneProviderBase}
- * instead.
- * @hide
- */
-interface ILocationTimeZoneProvider {
-
- oneway void setLocationTimeZoneProviderManager(in ILocationTimeZoneProviderManager manager);
-
- oneway void setRequest(in LocationTimeZoneProviderRequest request);
-}
diff --git a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl b/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl
deleted file mode 100644
index b5450b7..0000000
--- a/location/java/com/android/internal/location/timezone/ILocationTimeZoneProviderManager.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-import com.android.internal.location.timezone.LocationTimeZoneEvent;
-
-/**
- * Binder interface for the manager of location time zone provider implementations.
- * @hide
- */
-interface ILocationTimeZoneProviderManager {
- void onLocationTimeZoneEvent(in LocationTimeZoneEvent locationTimeZoneEvent);
-}
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl b/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl
deleted file mode 100644
index 199e067..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-parcelable LocationTimeZoneEvent;
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java
deleted file mode 100644
index 31c27d1..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneEvent.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An event containing location time zone information.
- *
- * @hide
- */
-public final class LocationTimeZoneEvent implements Parcelable {
-
- @IntDef({ EVENT_TYPE_UNKNOWN, EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS,
- EVENT_TYPE_UNCERTAIN })
- public @interface EventType {}
-
- /** Uninitialized value for {@link #mEventType} - must not be used for real events. */
- private static final int EVENT_TYPE_UNKNOWN = 0;
-
- /**
- * Indicates there was a permanent failure. This is not generally expected, and probably means a
- * required backend service has been turned down, or the client is unreasonably old.
- */
- public static final int EVENT_TYPE_PERMANENT_FAILURE = 1;
-
- /**
- * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
- * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
- */
- public static final int EVENT_TYPE_SUCCESS = 2;
-
- /**
- * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
- * the provider is unable to detect location, or there was a problem when resolving the location
- * to a time zone.
- */
- public static final int EVENT_TYPE_UNCERTAIN = 3;
-
- private static final int EVENT_TYPE_MAX = EVENT_TYPE_UNCERTAIN;
-
- @EventType
- private final int mEventType;
-
- @NonNull
- private final List<String> mTimeZoneIds;
-
- private final long mElapsedRealtimeMillis;
-
- private LocationTimeZoneEvent(@EventType int eventType, @NonNull List<String> timeZoneIds,
- long elapsedRealtimeMillis) {
- mEventType = checkValidEventType(eventType);
- mTimeZoneIds = immutableList(timeZoneIds);
-
- boolean emptyTimeZoneIdListExpected = eventType != EVENT_TYPE_SUCCESS;
- Preconditions.checkState(!emptyTimeZoneIdListExpected || timeZoneIds.isEmpty());
-
- mElapsedRealtimeMillis = elapsedRealtimeMillis;
- }
-
- /**
- * Returns the time of this fix, in elapsed real-time since system boot.
- *
- * <p>This value can be reliably compared to {@link
- * android.os.SystemClock#elapsedRealtime()}, to calculate the age of a fix and to compare
- * {@link LocationTimeZoneEvent} instances.
- *
- * @return elapsed real-time of fix, in milliseconds
- */
- public long getElapsedRealtimeMillis() {
- return mElapsedRealtimeMillis;
- }
-
- /**
- * Returns the event type.
- */
- @Nullable
- public @EventType int getEventType() {
- return mEventType;
- }
-
- /**
- * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
- * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
- */
- @NonNull
- public List<String> getTimeZoneIds() {
- return mTimeZoneIds;
- }
-
- @Override
- public String toString() {
- return "LocationTimeZoneEvent{"
- + "mEventType=" + mEventType
- + ", mTimeZoneIds=" + mTimeZoneIds
- + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
- + "(" + Duration.ofMillis(mElapsedRealtimeMillis) + ")"
- + '}';
- }
-
- public static final @NonNull Parcelable.Creator<LocationTimeZoneEvent> CREATOR =
- new Parcelable.Creator<LocationTimeZoneEvent>() {
- @Override
- public LocationTimeZoneEvent createFromParcel(Parcel in) {
- int eventType = in.readInt();
- @SuppressWarnings("unchecked")
- ArrayList<String> timeZoneIds =
- (ArrayList<String>) in.readArrayList(null /* classLoader */);
- long elapsedRealtimeMillis = in.readLong();
- return new LocationTimeZoneEvent(eventType, timeZoneIds, elapsedRealtimeMillis);
- }
-
- @Override
- public LocationTimeZoneEvent[] newArray(int size) {
- return new LocationTimeZoneEvent[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeInt(mEventType);
- parcel.writeList(mTimeZoneIds);
- parcel.writeLong(mElapsedRealtimeMillis);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- LocationTimeZoneEvent that = (LocationTimeZoneEvent) o;
- return mEventType == that.mEventType
- && mElapsedRealtimeMillis == that.mElapsedRealtimeMillis
- && mTimeZoneIds.equals(that.mTimeZoneIds);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mEventType, mTimeZoneIds, mElapsedRealtimeMillis);
- }
-
- /** @hide */
- public static final class Builder {
-
- private @EventType int mEventType = EVENT_TYPE_UNKNOWN;
- private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
- private long mElapsedRealtimeMillis;
-
- public Builder() {
- }
-
- /**
- * Sets the contents of this from the supplied instance.
- */
- public Builder(@NonNull LocationTimeZoneEvent ltz) {
- mEventType = ltz.mEventType;
- mTimeZoneIds = ltz.mTimeZoneIds;
- mElapsedRealtimeMillis = ltz.mElapsedRealtimeMillis;
- }
-
- /**
- * Set the time zone ID of this event.
- */
- public Builder setEventType(@EventType int eventType) {
- checkValidEventType(eventType);
- mEventType = eventType;
- return this;
- }
-
- /**
- * Sets the time zone IDs of this event.
- */
- public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
- mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
- return this;
- }
-
- /**
- * Sets the time of this event, in elapsed real-time since system boot.
- */
- public Builder setElapsedRealtimeMillis(long time) {
- mElapsedRealtimeMillis = time;
- return this;
- }
-
- /**
- * Builds a {@link LocationTimeZoneEvent} instance.
- */
- public LocationTimeZoneEvent build() {
- return new LocationTimeZoneEvent(mEventType, mTimeZoneIds, mElapsedRealtimeMillis);
- }
- }
-
- private static int checkValidEventType(int eventType) {
- if (eventType <= EVENT_TYPE_UNKNOWN || eventType > EVENT_TYPE_MAX) {
- throw new IllegalStateException("eventType " + eventType + " unknown");
- }
- return eventType;
- }
-
- @NonNull
- private static List<String> immutableList(@NonNull List<String> list) {
- Objects.requireNonNull(list);
- if (list.isEmpty()) {
- return Collections.emptyList();
- } else {
- return Collections.unmodifiableList(new ArrayList<>(list));
- }
- }
-}
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl
deleted file mode 100644
index bb59457..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-parcelable LocationTimeZoneProviderRequest;
diff --git a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java b/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
deleted file mode 100644
index 5c9d290..0000000
--- a/location/java/com/android/internal/location/timezone/LocationTimeZoneProviderRequest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.location.timezone;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Objects;
-
-/**
- * A request passed to a location time zone provider to configure it.
- *
- * @hide
- */
-public final class LocationTimeZoneProviderRequest implements Parcelable {
-
- public static final LocationTimeZoneProviderRequest EMPTY_REQUEST =
- new LocationTimeZoneProviderRequest(
- false /* reportLocationTimeZone */,
- 0 /* initializationTimeoutMillis */);
-
- public static final Creator<LocationTimeZoneProviderRequest> CREATOR =
- new Creator<LocationTimeZoneProviderRequest>() {
- @Override
- public LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
- return LocationTimeZoneProviderRequest.createFromParcel(in);
- }
-
- @Override
- public LocationTimeZoneProviderRequest[] newArray(int size) {
- return new LocationTimeZoneProviderRequest[size];
- }
- };
-
- private final boolean mReportLocationTimeZone;
-
- private final long mInitializationTimeoutMillis;
-
- private LocationTimeZoneProviderRequest(
- boolean reportLocationTimeZone, long initializationTimeoutMillis) {
- mReportLocationTimeZone = reportLocationTimeZone;
- mInitializationTimeoutMillis = initializationTimeoutMillis;
- }
-
- /**
- * Returns {@code true} if the provider should report events related to the device's current
- * time zone, {@code false} otherwise.
- */
- public boolean getReportLocationTimeZone() {
- return mReportLocationTimeZone;
- }
-
- // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to
- // be passed to the LocationTimeZoneProvider and remove if it is not useful.
- /**
- * Returns the maximum time that the provider is allowed to initialize before it is expected to
- * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
- * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
- * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
- */
- public long getInitializationTimeoutMillis() {
- return mInitializationTimeoutMillis;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeBoolean(mReportLocationTimeZone);
- parcel.writeLong(mInitializationTimeoutMillis);
- }
-
- static LocationTimeZoneProviderRequest createFromParcel(Parcel in) {
- boolean reportLocationTimeZone = in.readBoolean();
- long initializationTimeoutMillis = in.readLong();
- return new LocationTimeZoneProviderRequest(
- reportLocationTimeZone, initializationTimeoutMillis);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- LocationTimeZoneProviderRequest that = (LocationTimeZoneProviderRequest) o;
- return mReportLocationTimeZone == that.mReportLocationTimeZone
- && mInitializationTimeoutMillis == that.mInitializationTimeoutMillis;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mReportLocationTimeZone, mInitializationTimeoutMillis);
- }
-
- @Override
- public String toString() {
- return "LocationTimeZoneProviderRequest{"
- + "mReportLocationTimeZone=" + mReportLocationTimeZone
- + ", mInitializationTimeoutMillis=" + mInitializationTimeoutMillis
- + "}";
- }
-
- /** @hide */
- public static final class Builder {
-
- private boolean mReportLocationTimeZone;
- private long mInitializationTimeoutMillis;
-
- /**
- * Sets the property that enables / disables the provider. This is set to {@code false} by
- * default.
- */
- public Builder setReportLocationTimeZone(boolean reportLocationTimeZone) {
- mReportLocationTimeZone = reportLocationTimeZone;
- return this;
- }
-
- /**
- * Sets the initialization timeout. See {@link
- * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()} for details.
- */
- public Builder setInitializationTimeoutMillis(long timeoutMillis) {
- mInitializationTimeoutMillis = timeoutMillis;
- return this;
- }
-
- /** Builds the {@link LocationTimeZoneProviderRequest} instance. */
- @NonNull
- public LocationTimeZoneProviderRequest build() {
- return new LocationTimeZoneProviderRequest(
- mReportLocationTimeZone, mInitializationTimeoutMillis);
- }
- }
-}
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index d4f7558..4e13487 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -67,35 +67,3 @@
}
-package com.android.location.timezone.provider {
-
- public final class LocationTimeZoneEventUnbundled {
- method public int getEventType();
- method @NonNull public java.util.List<java.lang.String> getTimeZoneIds();
- field public static final int EVENT_TYPE_PERMANENT_FAILURE = 1; // 0x1
- field public static final int EVENT_TYPE_SUCCESS = 2; // 0x2
- field public static final int EVENT_TYPE_UNCERTAIN = 3; // 0x3
- }
-
- public static final class LocationTimeZoneEventUnbundled.Builder {
- ctor public LocationTimeZoneEventUnbundled.Builder();
- method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled build();
- method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setEventType(int);
- method @NonNull public com.android.location.timezone.provider.LocationTimeZoneEventUnbundled.Builder setTimeZoneIds(@NonNull java.util.List<java.lang.String>);
- }
-
- public abstract class LocationTimeZoneProviderBase {
- ctor public LocationTimeZoneProviderBase(android.content.Context, String);
- method public final android.os.IBinder getBinder();
- method protected final android.content.Context getContext();
- method protected abstract void onSetRequest(@NonNull com.android.location.timezone.provider.LocationTimeZoneProviderRequestUnbundled);
- method protected final void reportLocationTimeZoneEvent(@NonNull com.android.location.timezone.provider.LocationTimeZoneEventUnbundled);
- }
-
- public final class LocationTimeZoneProviderRequestUnbundled {
- method @IntRange(from=0) public long getInitializationTimeoutMillis();
- method public boolean getReportLocationTimeZone();
- }
-
-}
-
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
deleted file mode 100644
index 55f5545..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneEventUnbundled.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.location.timezone.provider;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.SystemClock;
-
-import com.android.internal.location.timezone.LocationTimeZoneEvent;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * An event from a {@link LocationTimeZoneProviderBase} sent while determining a device's time zone
- * using its location.
- */
-public final class LocationTimeZoneEventUnbundled {
-
- @IntDef({ EVENT_TYPE_PERMANENT_FAILURE, EVENT_TYPE_SUCCESS, EVENT_TYPE_UNCERTAIN })
- @interface EventType {}
-
- /**
- * Indicates there was a permanent failure. This is not generally expected, and probably means a
- * required backend service has been turned down, or the client is unreasonably old.
- */
- public static final int EVENT_TYPE_PERMANENT_FAILURE =
- LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE;
-
- /**
- * Indicates a successful geolocation time zone detection event. {@link #getTimeZoneIds()} will
- * be non-null but can legitimately be empty, e.g. for disputed areas, oceans.
- */
- public static final int EVENT_TYPE_SUCCESS = LocationTimeZoneEvent.EVENT_TYPE_SUCCESS;
-
- /**
- * Indicates the time zone is not known because of an expected runtime state or error, e.g. when
- * the provider is unable to detect location, or there was a problem when resolving the location
- * to a time zone.
- */
- public static final int EVENT_TYPE_UNCERTAIN = LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN;
-
- @NonNull
- private final LocationTimeZoneEvent mDelegate;
-
- private LocationTimeZoneEventUnbundled(@NonNull LocationTimeZoneEvent delegate) {
- mDelegate = Objects.requireNonNull(delegate);
- }
-
- /**
- * Returns the event type.
- */
- public @EventType int getEventType() {
- return mDelegate.getEventType();
- }
-
- /**
- * Gets the time zone IDs of this event. Contains zero or more IDs for a successful lookup.
- * The value is undefined for an unsuccessful lookup. See also {@link #getEventType()}.
- */
- @NonNull
- public List<String> getTimeZoneIds() {
- return mDelegate.getTimeZoneIds();
- }
-
- /**
- * Returns the information from this as a {@link LocationTimeZoneEvent}.
- * @hide
- */
- @NonNull
- public LocationTimeZoneEvent getInternalLocationTimeZoneEvent() {
- return mDelegate;
- }
-
- @Override
- public String toString() {
- return "LocationTimeZoneEventUnbundled{"
- + "mDelegate=" + mDelegate
- + '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- LocationTimeZoneEventUnbundled that = (LocationTimeZoneEventUnbundled) o;
- return mDelegate.equals(that.mDelegate);
- }
-
- @Override
- public int hashCode() {
- return mDelegate.hashCode();
- }
-
- /**
- * A builder of {@link LocationTimeZoneEventUnbundled} instances.
- */
- public static final class Builder {
-
- private @EventType int mEventType;
- private @NonNull List<String> mTimeZoneIds = Collections.emptyList();
-
- /**
- * Set the time zone ID of this event.
- */
- @NonNull
- public Builder setEventType(@EventType int eventType) {
- checkValidEventType(eventType);
- mEventType = eventType;
- return this;
- }
-
- /**
- * Sets the time zone IDs of this event.
- */
- @NonNull
- public Builder setTimeZoneIds(@NonNull List<String> timeZoneIds) {
- mTimeZoneIds = Objects.requireNonNull(timeZoneIds);
- return this;
- }
-
- /**
- * Builds a {@link LocationTimeZoneEventUnbundled} instance.
- */
- @NonNull
- public LocationTimeZoneEventUnbundled build() {
- final int internalEventType = this.mEventType;
- LocationTimeZoneEvent event = new LocationTimeZoneEvent.Builder()
- .setEventType(internalEventType)
- .setTimeZoneIds(mTimeZoneIds)
- .setElapsedRealtimeMillis(SystemClock.elapsedRealtime())
- .build();
- return new LocationTimeZoneEventUnbundled(event);
- }
- }
-
- private static int checkValidEventType(int eventType) {
- if (eventType != EVENT_TYPE_SUCCESS
- && eventType != EVENT_TYPE_UNCERTAIN
- && eventType != EVENT_TYPE_PERMANENT_FAILURE) {
- throw new IllegalStateException("eventType=" + eventType);
- }
- return eventType;
- }
-}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
deleted file mode 100644
index 68ae722..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderBase.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.location.timezone.provider;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.location.timezone.ILocationTimeZoneProvider;
-import com.android.internal.location.timezone.ILocationTimeZoneProviderManager;
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-import java.util.Objects;
-
-/**
- * A base class for location time zone providers implemented as unbundled services.
- *
- * <p>Provider implementations are enabled / disabled via a call to {@link
- * #onSetRequest(LocationTimeZoneProviderRequestUnbundled)}.
- *
- * <p>Once enabled, providers are expected to detect the time zone if possible, and report the
- * result via {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} with a type of
- * either {@link LocationTimeZoneEventUnbundled#EVENT_TYPE_UNCERTAIN} or {@link
- * LocationTimeZoneEventUnbundled#EVENT_TYPE_SUCCESS}. Providers may also report that they have
- * permanently failed by sending an event of type {@link
- * LocationTimeZoneEventUnbundled#EVENT_TYPE_PERMANENT_FAILURE}. See the javadocs for each event
- * type for details.
- *
- * <p>Providers are expected to issue their first event within {@link
- * LocationTimeZoneProviderRequest#getInitializationTimeoutMillis()}.
- *
- * <p>Once disabled or have failed, providers are required to stop producing events.
- *
- * <p>Threading:
- *
- * <p>Calls to {@link #reportLocationTimeZoneEvent(LocationTimeZoneEventUnbundled)} can be made on
- * on any thread, but may be processed asynchronously by the system server. Similarly, calls to
- * {@link #onSetRequest(LocationTimeZoneProviderRequestUnbundled)} may occur on any thread.
- *
- * <p>IMPORTANT: This class is effectively a public API for unbundled applications, and must remain
- * API stable.
- */
-public abstract class LocationTimeZoneProviderBase {
-
- private final Context mContext;
- private final String mTag;
- private final IBinder mBinder;
-
- // write locked on mBinder, read lock is optional depending on atomicity requirements
- @Nullable private volatile ILocationTimeZoneProviderManager mManager;
-
- public LocationTimeZoneProviderBase(Context context, String tag) {
- mContext = context;
- mTag = tag;
- mBinder = new Service();
- mManager = null;
- }
-
- protected final Context getContext() {
- return mContext;
- }
-
- public final IBinder getBinder() {
- return mBinder;
- }
-
- /**
- * Reports a new location time zone event from this provider.
- */
- protected final void reportLocationTimeZoneEvent(
- @NonNull LocationTimeZoneEventUnbundled event) {
- ILocationTimeZoneProviderManager manager = mManager;
- if (manager != null) {
- try {
- manager.onLocationTimeZoneEvent(event.getInternalLocationTimeZoneEvent());
- } catch (RemoteException | RuntimeException e) {
- Log.w(mTag, e);
- }
- }
- }
-
- /**
- * Set the {@link LocationTimeZoneProviderRequestUnbundled} requirements for this provider. Each
- * call to this method overrides all previous requests. This method might trigger the provider
- * to start returning location time zones, or to stop returning location time zones, depending
- * on the parameters in the request.
- */
- protected abstract void onSetRequest(@NonNull LocationTimeZoneProviderRequestUnbundled request);
-
- private final class Service extends ILocationTimeZoneProvider.Stub {
-
- @Override
- public void setLocationTimeZoneProviderManager(ILocationTimeZoneProviderManager manager) {
- mManager = Objects.requireNonNull(manager);
- }
-
- @Override
- public void setRequest(LocationTimeZoneProviderRequest request) {
- onSetRequest(new LocationTimeZoneProviderRequestUnbundled(request));
- }
- }
-}
diff --git a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java b/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
deleted file mode 100644
index 10d1038..0000000
--- a/location/lib/java/com/android/location/timezone/provider/LocationTimeZoneProviderRequestUnbundled.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.location.timezone.provider;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.location.timezone.LocationTimeZoneProviderRequest;
-
-import java.util.Objects;
-
-/**
- * This class is an interface to LocationTimeZoneProviderRequest for provider implementations.
- *
- * <p>IMPORTANT: This class is effectively a public API for unbundled code, and must remain API
- * stable.
- */
-public final class LocationTimeZoneProviderRequestUnbundled {
-
- private final LocationTimeZoneProviderRequest mRequest;
-
- /** @hide */
- public LocationTimeZoneProviderRequestUnbundled(
- @NonNull LocationTimeZoneProviderRequest request) {
- mRequest = Objects.requireNonNull(request);
- }
-
- /**
- * Returns {@code true} if the provider should report events related to the device's current
- * time zone, {@code false} otherwise.
- */
- public boolean getReportLocationTimeZone() {
- return mRequest.getReportLocationTimeZone();
- }
-
- /**
- * Returns the maximum time that the provider is allowed to initialize before it is expected to
- * send an event of any sort. Only valid when {@link #getReportLocationTimeZone()} is {@code
- * true}. Failure to send an event in this time (with some fuzz) may be interpreted as if the
- * provider is uncertain of the time zone, and/or it could lead to the provider being disabled.
- */
- @IntRange(from = 0)
- public long getInitializationTimeoutMillis() {
- return mRequest.getInitializationTimeoutMillis();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- LocationTimeZoneProviderRequestUnbundled that =
- (LocationTimeZoneProviderRequestUnbundled) o;
- return mRequest.equals(that.mRequest);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mRequest);
- }
-
- @Override
- public String toString() {
- return mRequest.toString();
- }
-}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index a4896cb2..7f19662 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -74,7 +74,7 @@
public class DynamicSystemInstallationService extends Service
implements InstallationAsyncTask.ProgressListener {
- private static final String TAG = "DynSystemInstallationService";
+ private static final String TAG = "DynamicSystemInstallationService";
// TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index ac73f35..4ef5e2b 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -20,6 +20,7 @@
import android.gsi.AvbPublicKey;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.image.DynamicSystemManager;
@@ -51,7 +52,8 @@
private static final long MIN_PROGRESS_TO_PUBLISH = 1 << 27;
private static final List<String> UNSUPPORTED_PARTITIONS =
- Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
+ Arrays.asList(
+ "vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other", "scratch");
private class UnsupportedUrlException extends Exception {
private UnsupportedUrlException(String message) {
@@ -196,6 +198,22 @@
return null;
}
+ if (Build.IS_DEBUGGABLE) {
+ // If host is debuggable, then install a scratch partition so that we can do
+ // adb remount in the guest system.
+ try {
+ installScratch();
+ } catch (IOException e) {
+ // Failing to install overlayFS scratch shouldn't be fatal.
+ // Just ignore the error and skip installing the scratch partition.
+ Log.w(TAG, e.toString(), e);
+ }
+ if (isCancelled()) {
+ mDynSystem.remove();
+ return null;
+ }
+ }
+
mDynSystem.finishInstallation();
} catch (Exception e) {
Log.e(TAG, e.toString(), e);
@@ -302,12 +320,53 @@
}
}
- private void installUserdata() throws Exception {
+ private void installScratch() throws IOException, InterruptedException {
+ final long scratchSize = mDynSystem.suggestScratchSize();
+ Thread thread = new Thread() {
+ @Override
+ public void run() {
+ mInstallationSession =
+ mDynSystem.createPartition("scratch", scratchSize, /* readOnly= */ false);
+ }
+ };
+
+ Log.d(TAG, "Creating partition: scratch, size = " + scratchSize);
+ thread.start();
+
+ Progress progress = new Progress("scratch", scratchSize, mNumInstalledPartitions++);
+
+ while (thread.isAlive()) {
+ if (isCancelled()) {
+ return;
+ }
+
+ final long installedSize = mDynSystem.getInstallationProgress().bytes_processed;
+
+ if (installedSize > progress.installedSize + MIN_PROGRESS_TO_PUBLISH) {
+ progress.installedSize = installedSize;
+ publishProgress(progress);
+ }
+
+ Thread.sleep(100);
+ }
+
+ if (mInstallationSession == null) {
+ throw new IOException(
+ "Failed to start installation with requested size: " + scratchSize);
+ }
+ // Reset installation session and verify that installation completes successfully.
+ mInstallationSession = null;
+ if (!mDynSystem.closePartition()) {
+ throw new IOException("Failed to complete partition installation: scratch");
+ }
+ }
+
+ private void installUserdata() throws IOException, InterruptedException {
Thread thread = new Thread(() -> {
mInstallationSession = mDynSystem.createPartition("userdata", mUserdataSize, false);
});
- Log.d(TAG, "Creating partition: userdata");
+ Log.d(TAG, "Creating partition: userdata, size = " + mUserdataSize);
thread.start();
Progress progress = new Progress("userdata", mUserdataSize, mNumInstalledPartitions++);
@@ -324,7 +383,7 @@
publishProgress(progress);
}
- Thread.sleep(10);
+ Thread.sleep(100);
}
if (mInstallationSession == null) {
@@ -445,7 +504,7 @@
return;
}
- Thread.sleep(10);
+ Thread.sleep(100);
}
if (mInstallationSession == null) {
diff --git a/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml b/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
new file mode 100644
index 0000000..6521bc9
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/drawable/ic_settings_accent.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorAccent">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
index b4b4c63..5ff0dc7 100644
--- a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -92,16 +92,32 @@
android:textAlignment="viewEnd"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="1"
+ android:visibility="gone"
android:ellipsize="end"/>
</LinearLayout>
- <ProgressBar
- android:id="@android:id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:max="100"
- android:visibility="gone"/>
</LinearLayout>
+ <LinearLayout
+ android:id="@+id/radio_extra_widget_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <View
+ android:layout_width=".75dp"
+ android:layout_height="match_parent"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp"
+ android:background="?android:attr/dividerVertical" />
+ <ImageView
+ android:id="@+id/radio_extra_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:src="@drawable/ic_settings_accent"
+ android:contentDescription="@string/settings_label"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackground" />
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml b/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
new file mode 100644
index 0000000..ff3f90c
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Content description for RadioButton with extra gear icon [CHAR LIMIT=NONE] -->
+ <string name="settings_label">Settings</string>
+
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
index 05e008c..f50127f 100644
--- a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
+++ b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
@@ -20,7 +20,7 @@
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.TextView;
+import android.widget.ImageView;
import androidx.preference.CheckBoxPreference;
import androidx.preference.PreferenceViewHolder;
@@ -34,6 +34,9 @@
* In other words, there's no "RadioButtonPreferenceGroup" in this
* implementation. When you check one RadioButtonPreference, if you want to
* uncheck all the other preferences, you should do that by code yourself.
+ *
+ * RadioButtonPreference can assign a extraWidgetListener to show a gear icon
+ * on the right side that can open another page.
*/
public class RadioButtonPreference extends CheckBoxPreference {
@@ -53,6 +56,10 @@
private View mAppendix;
private int mAppendixVisibility = -1;
+ private View mExtraWidgetContainer;
+ private ImageView mExtraWidget;
+
+ private View.OnClickListener mExtraWidgetOnClickListener;
/**
* Perform inflation from XML and apply a class-specific base style.
@@ -69,7 +76,6 @@
init();
}
-
/**
* Perform inflation from XML and apply a class-specific base style.
*
@@ -136,11 +142,10 @@
}
}
- TextView title = (TextView) holder.findViewById(android.R.id.title);
- if (title != null) {
- title.setSingleLine(false);
- title.setMaxLines(3);
- }
+ mExtraWidget = (ImageView) holder.findViewById(R.id.radio_extra_widget);
+ mExtraWidgetContainer = holder.findViewById(R.id.radio_extra_widget_container);
+
+ setExtraWidgetOnClickListener(mExtraWidgetOnClickListener);
}
/**
@@ -155,6 +160,24 @@
mAppendixVisibility = visibility;
}
+ /**
+ * Sets the callback to be invoked when extra widget is clicked by the user.
+ *
+ * @param listener The callback to be invoked
+ */
+ public void setExtraWidgetOnClickListener(View.OnClickListener listener) {
+ mExtraWidgetOnClickListener = listener;
+
+ if (mExtraWidget == null || mExtraWidgetContainer == null) {
+ return;
+ }
+
+ mExtraWidget.setOnClickListener(mExtraWidgetOnClickListener);
+
+ mExtraWidgetContainer.setVisibility((mExtraWidgetOnClickListener != null)
+ ? View.VISIBLE : View.GONE);
+ }
+
private void init() {
setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
setLayoutResource(R.layout.preference_radio);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
index d58e68a..a5028ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
@@ -40,10 +40,30 @@
private Application mContext;
private RadioButtonPreference mPreference;
+ private View mExtraWidgetContainer;
+ private View mExtraWidget;
+
+ private boolean mIsClickListenerCalled;
+ private View.OnClickListener mClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mIsClickListenerCalled = true;
+ }
+ };
+
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mPreference = new RadioButtonPreference(mContext);
+
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.preference_radio, null /* root */);
+ PreferenceViewHolder preferenceViewHolder =
+ PreferenceViewHolder.createInstanceForTests(view);
+ mPreference.onBindViewHolder(preferenceViewHolder);
+
+ mExtraWidgetContainer = view.findViewById(R.id.radio_extra_widget_container);
+ mExtraWidget = view.findViewById(R.id.radio_extra_widget);
}
@Test
@@ -57,26 +77,30 @@
}
@Test
- public void summary_containerShouldBeVisible() {
+ public void onBindViewHolder_withSummary_containerShouldBeVisible() {
mPreference.setSummary("some summary");
View summaryContainer = new View(mContext);
View view = mock(View.class);
when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
PreferenceViewHolder preferenceViewHolder =
PreferenceViewHolder.createInstanceForTests(view);
+
mPreference.onBindViewHolder(preferenceViewHolder);
+
assertEquals(View.VISIBLE, summaryContainer.getVisibility());
}
@Test
- public void emptySummary_containerShouldBeGone() {
+ public void onBindViewHolder_emptySummary_containerShouldBeGone() {
mPreference.setSummary("");
View summaryContainer = new View(mContext);
View view = mock(View.class);
when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
PreferenceViewHolder preferenceViewHolder =
PreferenceViewHolder.createInstanceForTests(view);
+
mPreference.onBindViewHolder(preferenceViewHolder);
+
assertEquals(View.GONE, summaryContainer.getVisibility());
}
@@ -93,11 +117,36 @@
}
@Test
- public void hideAppendix_shouldBeGone() {
+ public void setAppendixVisibility_setGone_shouldBeGone() {
mPreference.setAppendixVisibility(View.GONE);
- View view = LayoutInflater.from(mContext).inflate(R.layout.preference_radio, null);
+
+ View view = LayoutInflater.from(mContext)
+ .inflate(R.layout.preference_radio, null /* root */);
PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
mPreference.onBindViewHolder(holder);
assertThat(holder.findViewById(R.id.appendix).getVisibility()).isEqualTo(View.GONE);
}
+
+ @Test
+ public void setExtraWidgetListener_setNull_extraWidgetShouldInvisible() {
+ mPreference.setExtraWidgetOnClickListener(null);
+
+ assertEquals(View.GONE, mExtraWidgetContainer.getVisibility());
+ }
+
+ @Test
+ public void setExtraWidgetListener_extraWidgetShouldVisible() {
+ mPreference.setExtraWidgetOnClickListener(mClickListener);
+
+ assertEquals(View.VISIBLE, mExtraWidgetContainer.getVisibility());
+ }
+
+ @Test
+ public void onClickListener_setExtraWidgetOnClickListener_ShouldCalled() {
+ mPreference.setExtraWidgetOnClickListener(mClickListener);
+
+ assertThat(mIsClickListenerCalled).isFalse();
+ mExtraWidget.callOnClick();
+ assertThat(mIsClickListenerCalled).isTrue();
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index 1a71f11..d672c2c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -16,8 +16,6 @@
package com.android.systemui.shared.system;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -48,9 +46,6 @@
options.setLaunchWindowingMode(isPrimary
? WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
: WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- options.setSplitScreenCreateMode(dockTopLeft
- ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
- : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
return options;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index d3066b4..b38270c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -83,6 +83,11 @@
public static final int WINDOWING_MODE_FREEFORM = WindowConfiguration.WINDOWING_MODE_FREEFORM;
public static final int ITYPE_EXTRA_NAVIGATION_BAR = InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+ public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = InsetsState.ITYPE_LEFT_TAPPABLE_ELEMENT;
+ public static final int ITYPE_TOP_TAPPABLE_ELEMENT = InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+ public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = InsetsState.ITYPE_RIGHT_TAPPABLE_ELEMENT;
+ public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT =
+ InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index d58b95c..d1494df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -23,10 +23,16 @@
import androidx.annotation.Nullable;
+import com.android.systemui.Gefingerpoken;
+
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
+ private final List<Gefingerpoken> mMotionEventListener = new ArrayList<>();
public KeyguardInputView(Context context) {
super(context);
@@ -56,4 +62,25 @@
boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
}
+
+ void addMotionEventListener(Gefingerpoken listener) {
+ mMotionEventListener.add(listener);
+ }
+
+ void removeMotionEventListener(Gefingerpoken listener) {
+ mMotionEventListener.remove(listener);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return mMotionEventListener.stream().anyMatch(listener -> listener.onTouchEvent(event))
+ || super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return mMotionEventListener.stream().anyMatch(
+ listener -> listener.onInterceptTouchEvent(event))
+ || super.onInterceptTouchEvent(event);
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 6aa5e0d..1c691e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -26,6 +26,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -155,6 +156,7 @@
private final Resources mResources;
private LiftToActivateListener mLiftToActivateListener;
private TelephonyManager mTelephonyManager;
+ private final FalsingCollector mFalsingCollector;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -163,7 +165,7 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
@Main Resources resources, LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager) {
+ TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -173,6 +175,7 @@
mResources = resources;
mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
+ mFalsingCollector = falsingCollector;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -191,17 +194,17 @@
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener);
+ mLiftToActivateListener, mFalsingCollector);
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager);
+ mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager);
+ mLiftToActivateListener, mTelephonyManager, mFalsingCollector);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 4d0ebff..f247948 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -25,12 +25,15 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
private final LiftToActivateListener mLiftToActivateListener;
+ private final FalsingCollector mFalsingCollector;
protected PasswordTextView mPasswordEntry;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -40,13 +43,26 @@
return false;
};
- private final OnTouchListener mOnTouchListener = (v, event) -> {
+ private final OnTouchListener mActionButtonTouchListener = (v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mView.doHapticKeyClick();
}
return false;
};
+ private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ mFalsingCollector.avoidGesture();
+ return false;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
+ };
+
protected KeyguardPinBasedInputViewController(T view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode,
@@ -54,10 +70,12 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener) {
+ LiftToActivateListener liftToActivateListener,
+ FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker);
mLiftToActivateListener = liftToActivateListener;
+ mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
}
@@ -65,11 +83,13 @@
protected void onViewAttached() {
super.onViewAttached();
+ mView.addMotionEventListener(mGlobalTouchListener);
+
mPasswordEntry.setOnKeyListener(mOnKeyListener);
mPasswordEntry.setUserActivityListener(this::onUserInput);
View deleteButton = mView.findViewById(R.id.delete_button);
- deleteButton.setOnTouchListener(mOnTouchListener);
+ deleteButton.setOnTouchListener(mActionButtonTouchListener);
deleteButton.setOnClickListener(v -> {
// check for time-based lockouts
if (mPasswordEntry.isEnabled()) {
@@ -87,7 +107,7 @@
View okButton = mView.findViewById(R.id.key_enter);
if (okButton != null) {
- okButton.setOnTouchListener(mOnTouchListener);
+ okButton.setOnTouchListener(mActionButtonTouchListener);
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -101,6 +121,12 @@
}
@Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mView.removeMotionEventListener(mGlobalTouchListener);
+ }
+
+ @Override
public void onResume(int reason) {
super.onResume(reason);
mPasswordEntry.requestFocus();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index fb0d6be..c0aa2af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -22,6 +22,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
public class KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
@@ -32,10 +33,11 @@
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener) {
+ LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 5b4a7ff..b218141 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -38,6 +38,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -76,11 +77,11 @@
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager) {
+ LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index eafb33f..890a17c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.classifier.FalsingCollector;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -83,11 +84,11 @@
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener,
- TelephonyManager telephonyManager) {
+ LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ TelephonyManager telephonyManager, FalsingCollector falsingCollector) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener);
+ messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ falsingCollector);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index e78057f..6572ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -18,6 +18,7 @@
import static com.android.systemui.classifier.FalsingManagerProxy.FALSING_SUCCESS;
import static com.android.systemui.classifier.FalsingModule.BRIGHT_LINE_GESTURE_CLASSIFERS;
+import static com.android.systemui.classifier.FalsingModule.DOUBLE_TAP_TIMEOUT_MS;
import android.net.Uri;
import android.os.Build;
@@ -28,23 +29,26 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.classifier.FalsingDataProvider.SessionListener;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.dock.DockManager;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.ThresholdSensor;
-import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
@@ -65,7 +69,8 @@
private final SingleTapClassifier mSingleTapClassifier;
private final DoubleTapClassifier mDoubleTapClassifier;
private final HistoryTracker mHistoryTracker;
- private final SystemClock mSystemClock;
+ private final DelayableExecutor mDelayableExecutor;
+ private final long mDoubleTapTimeMs;
private final boolean mTestHarness;
private final MetricsLogger mMetricsLogger;
private int mIsFalseTouchCalls;
@@ -90,23 +95,44 @@
private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
new FalsingDataProvider.GestureCompleteListener() {
- @Override
- public void onGestureComplete() {
- mHistoryTracker.addResults(
- mClassifiers.stream().map(FalsingClassifier::classifyGesture)
- .collect(Collectors.toCollection(ArrayList::new)),
- mSystemClock.uptimeMillis());
+ @Override
+ public void onGestureComplete(long completionTimeMs) {
+ if (mPriorResults != null) {
+ // Single taps that may become double taps don't get added right away.
+ if (mClassifyAsSingleTap) {
+ Collection<FalsingClassifier.Result> singleTapResults = mPriorResults;
+ mSingleTapHistoryCanceller = mDelayableExecutor.executeDelayed(
+ () -> {
+ mSingleTapHistoryCanceller = null;
+ mHistoryTracker.addResults(singleTapResults, completionTimeMs);
+ },
+ mDoubleTapTimeMs);
+ mClassifyAsSingleTap = false; // Don't treat things as single taps by default.
+ } else {
+ mHistoryTracker.addResults(mPriorResults, completionTimeMs);
+ }
+ mPriorResults = null;
+ } else {
+ // Gestures that were not classified get treated as a false.
+ mHistoryTracker.addResults(
+ Collections.singleton(
+ FalsingClassifier.Result.falsed(.8, "unclassified")),
+ completionTimeMs);
+ }
}
};
- private boolean mPreviousResult = false;
+ private Collection<FalsingClassifier.Result> mPriorResults;
+ private boolean mClassifyAsSingleTap;
+ private Runnable mSingleTapHistoryCanceller;
@Inject
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
DockManager dockManager, MetricsLogger metricsLogger,
@Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
- HistoryTracker historyTracker, SystemClock systemClock,
+ HistoryTracker historyTracker, @Main DelayableExecutor delayableExecutor,
+ @Named(DOUBLE_TAP_TIMEOUT_MS) long doubleTapTimeMs,
@TestHarness boolean testHarness) {
mDataProvider = falsingDataProvider;
mDockManager = dockManager;
@@ -115,7 +141,8 @@
mSingleTapClassifier = singleTapClassifier;
mDoubleTapClassifier = doubleTapClassifier;
mHistoryTracker = historyTracker;
- mSystemClock = systemClock;
+ mDelayableExecutor = delayableExecutor;
+ mDoubleTapTimeMs = doubleTapTimeMs;
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
@@ -129,38 +156,51 @@
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+ boolean result;
+
+ mClassifyAsSingleTap = false;
mDataProvider.setInteractionType(interactionType);
- if (!mDataProvider.isDirty()) {
- return mPreviousResult;
+
+ if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
+ Stream<FalsingClassifier.Result> results =
+ mClassifiers.stream().map(falsingClassifier -> {
+ FalsingClassifier.Result classifierResult =
+ falsingClassifier.classifyGesture(
+ mHistoryTracker.falsePenalty(),
+ mHistoryTracker.falseConfidence());
+ if (classifierResult.isFalse()) {
+ logInfo(String.format(
+ (Locale) null,
+ "{classifier=%s, interactionType=%d}",
+ falsingClassifier.getClass().getName(),
+ mDataProvider.getInteractionType()));
+ String reason = classifierResult.getReason();
+ if (reason != null) {
+ logInfo(reason);
+ }
+ } else {
+ logDebug(falsingClassifier.getClass().getName() + ": false");
+ }
+ return classifierResult;
+ });
+ mPriorResults = new ArrayList<>();
+ final boolean[] localResult = {false};
+ results.forEach(classifierResult -> {
+ localResult[0] |= classifierResult.isFalse();
+ mPriorResults.add(classifierResult);
+ });
+ result = localResult[0];
+ } else {
+ result = false;
+ mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
}
- mPreviousResult = !mTestHarness
- && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()
- && mClassifiers.stream().anyMatch(falsingClassifier -> {
- FalsingClassifier.Result result = falsingClassifier.classifyGesture(
- mHistoryTracker.falsePenalty(), mHistoryTracker.falseConfidence());
- if (result.isFalse()) {
- logInfo(String.format(
- (Locale) null,
- "{classifier=%s, interactionType=%d}",
- falsingClassifier.getClass().getName(),
- mDataProvider.getInteractionType()));
- String reason = result.getReason();
- if (reason != null) {
- logInfo(reason);
- }
- } else {
- logDebug(falsingClassifier.getClass().getName() + ": false");
- }
- return result.isFalse();
- });
-
- logDebug("Is false touch? " + mPreviousResult);
+ logDebug("Is false touch? " + result);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
// Copy motion events, as the passed in list gets emptied out elsewhere in the code.
RECENT_SWIPES.add(new DebugSwipeRecord(
- mPreviousResult,
+ result,
mDataProvider.getInteractionType(),
mDataProvider.getRecentMotionEvents().stream().map(
motionEvent -> new XYDt(
@@ -173,13 +213,16 @@
}
}
- return mPreviousResult;
+ return result;
}
@Override
public boolean isFalseTap(boolean robustCheck) {
+ mClassifyAsSingleTap = true;
+
FalsingClassifier.Result singleTapResult =
mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
+ mPriorResults = Collections.singleton(singleTapResult);
if (singleTapResult.isFalse()) {
logInfo(String.format(
(Locale) null, "{classifier=%s}", mSingleTapClassifier.getClass().getName()));
@@ -192,7 +235,12 @@
// TODO(b/172655679): More heuristics to come. For now, allow touches through if face-authed
if (robustCheck) {
- return !mDataProvider.isJustUnlockedWithFace();
+ boolean result = !mDataProvider.isJustUnlockedWithFace();
+ mPriorResults = Collections.singleton(
+ result ? FalsingClassifier.Result.falsed(0.1, "no face detected")
+ : FalsingClassifier.Result.passed(1));
+
+ return result;
}
return false;
@@ -200,7 +248,9 @@
@Override
public boolean isFalseDoubleTap() {
+ mClassifyAsSingleTap = false;
FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
+ mPriorResults = Collections.singleton(result);
if (result.isFalse()) {
logInfo(String.format(
(Locale) null, "{classifier=%s}", mDoubleTapClassifier.getClass().getName()));
@@ -208,6 +258,12 @@
if (reason != null) {
logInfo(reason);
}
+ } else {
+ // A valid double tap prevents an invalid single tap from going into history.
+ if (mSingleTapHistoryCanceller != null) {
+ mSingleTapHistoryCanceller.run();
+ mSingleTapHistoryCanceller = null;
+ }
}
return result.isFalse();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index fe47162..b0bbab3 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -116,6 +116,9 @@
void onTouchEvent(MotionEvent ev);
/** */
+ void avoidGesture();
+
+ /** */
void cleanup();
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index fd05989..12a0604 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -147,6 +147,10 @@
}
@Override
+ public void avoidGesture() {
+ }
+
+ @Override
public void cleanup() {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4c11ecf..e08b43b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -49,6 +49,8 @@
private boolean mShowingAod;
private boolean mScreenOn;
private boolean mSessionStarted;
+ private MotionEvent mPendingDownEvent;
+ private boolean mAvoidGesture;
private final ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent;
@@ -245,7 +247,32 @@
@Override
public void onTouchEvent(MotionEvent ev) {
- mFalsingDataProvider.onMotionEvent(ev);
+ // We delay processing down events to see if another component wants to process them.
+ // If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
+ // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
+ // avoidGesture must be called immediately following the MotionEvent.ACTION_DOWN, before
+ // any other events are processed, otherwise the whole gesture will be recorded.
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ // Make a copy of ev, since it will be recycled after we exit this method.
+ mPendingDownEvent = MotionEvent.obtain(ev);
+ mAvoidGesture = false;
+ } else if (!mAvoidGesture) {
+ if (mPendingDownEvent != null) {
+ mFalsingDataProvider.onMotionEvent(mPendingDownEvent);
+ mPendingDownEvent.recycle();
+ mPendingDownEvent = null;
+ }
+ mFalsingDataProvider.onMotionEvent(ev);
+ }
+ }
+
+ @Override
+ public void avoidGesture() {
+ if (mPendingDownEvent != null) {
+ mAvoidGesture = true;
+ mPendingDownEvent.recycle();
+ mPendingDownEvent = null;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index deb9e6d..4bacc15 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -90,10 +90,8 @@
}
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
- if (!mRecentMotionEvents.isEmpty()) {
- mExtendedMotionEvents.addFirst(mRecentMotionEvents);
- mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
- }
+ completePriorGesture();
+ mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
mRecentMotionEvents.addAll(motionEvents);
@@ -101,9 +99,25 @@
mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
+ // We explicitly do not complete a gesture on UP or CANCEL events.
+ // We wait for the next gesture to start before marking the prior gesture as complete. This
+ // has multiple benefits. First, it makes it trivial to track the "current" or "recent"
+ // gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly,
+ // it ensures that the current gesture doesn't get added to this HistoryTracker before it
+ // is analyzed.
+
mDirty = true;
}
+ private void completePriorGesture() {
+ if (!mRecentMotionEvents.isEmpty()) {
+ mGestuerCompleteListeners.forEach(listener -> listener.onGestureComplete(
+ mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
+
+ mExtendedMotionEvents.addFirst(mRecentMotionEvents);
+ }
+ }
+
/** Returns screen width in pixels. */
public int getWidthPixels() {
return mWidthPixels;
@@ -146,13 +160,6 @@
}
}
- /**
- * Returns true if new data has been supplied since the last time this class has been accessed.
- */
- public boolean isDirty() {
- return mDirty;
- }
-
/** Return the interaction type that is being compared against for falsing. */
public final int getInteractionType() {
return mInteractionType;
@@ -387,6 +394,6 @@
/** Callback to be alerted when the current gesture ends. */
public interface GestureCompleteListener {
/** */
- void onGestureComplete();
+ void onGestureComplete(long completionTimeMs);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
new file mode 100644
index 0000000..2719d3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.os.FileUtils.closeQuietly;
+
+import android.annotation.IntRange;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.exifinterface.media.ExifInterface;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+class ImageExporter {
+ private static final String TAG = LogConfig.logTag(ImageExporter.class);
+
+ static final Duration PENDING_ENTRY_TTL = Duration.ofHours(24);
+
+ // ex: 'Screenshot_20201215-090626.png'
+ private static final String FILENAME_PATTERN = "Screenshot_%1$tY%<tm%<td-%<tH%<tM%<tS.%2$s";
+ private static final String SCREENSHOTS_PATH = Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS;
+
+ private static final String RESOLVER_INSERT_RETURNED_NULL =
+ "ContentResolver#insert returned null.";
+ private static final String RESOLVER_OPEN_FILE_RETURNED_NULL =
+ "ContentResolver#openFile returned null.";
+ private static final String RESOLVER_OPEN_FILE_EXCEPTION =
+ "ContentResolver#openFile threw an exception.";
+ private static final String OPEN_OUTPUT_STREAM_EXCEPTION =
+ "ContentResolver#openOutputStream threw an exception.";
+ private static final String EXIF_READ_EXCEPTION =
+ "ExifInterface threw an exception reading from the file descriptor.";
+ private static final String EXIF_WRITE_EXCEPTION =
+ "ExifInterface threw an exception writing to the file descriptor.";
+ private static final String RESOLVER_UPDATE_ZERO_ROWS =
+ "Failed to publishEntry. ContentResolver#update reported no rows updated.";
+ private static final String IMAGE_COMPRESS_RETURNED_FALSE =
+ "Bitmap.compress returned false. (Failure unknown)";
+
+ private final ContentResolver mResolver;
+ private CompressFormat mCompressFormat = CompressFormat.PNG;
+ private int mQuality = 100;
+
+ @Inject
+ ImageExporter(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ /**
+ * Adjusts the output image format. This also determines extension of the filename created. The
+ * default is {@link CompressFormat#PNG PNG}.
+ *
+ * @see CompressFormat
+ *
+ * @param format the image format for export
+ */
+ void setFormat(CompressFormat format) {
+ mCompressFormat = format;
+ }
+
+ /**
+ * Sets the quality format. The exact meaning is dependent on the {@link CompressFormat} used.
+ *
+ * @param quality the 'quality' level between 0 and 100
+ */
+ void setQuality(@IntRange(from = 0, to = 100) int quality) {
+ mQuality = quality;
+ }
+
+ /**
+ * Export the image using the given executor.
+ *
+ * @param executor the thread for execution
+ * @param bitmap the bitmap to export
+ *
+ * @return a listenable future result
+ */
+ ListenableFuture<Uri> export(Executor executor, Bitmap bitmap) {
+ return export(executor, bitmap, ZonedDateTime.now());
+ }
+
+ /**
+ * Export the image using the given executor.
+ *
+ * @param executor the thread for execution
+ * @param bitmap the bitmap to export
+ *
+ * @return a listenable future result
+ */
+ ListenableFuture<Uri> export(Executor executor, Bitmap bitmap, ZonedDateTime captureTime) {
+ final Task task = new Task(mResolver, bitmap, captureTime, mCompressFormat, mQuality);
+ return CallbackToFutureAdapter.getFuture(
+ (completer) -> {
+ executor.execute(() -> {
+ try {
+ completer.set(task.execute());
+ } catch (ImageExportException | InterruptedException e) {
+ completer.setException(e);
+ }
+ });
+ return task;
+ }
+ );
+ }
+
+ private static class Task {
+ private final ContentResolver mResolver;
+ private final ZonedDateTime mCaptureTime;
+ private final CompressFormat mFormat;
+ private final int mQuality;
+ private final Bitmap mBitmap;
+
+ Task(ContentResolver resolver, Bitmap bitmap, ZonedDateTime captureTime,
+ CompressFormat format, int quality) {
+ mResolver = resolver;
+ mBitmap = bitmap;
+ mCaptureTime = captureTime;
+ mFormat = format;
+ mQuality = quality;
+ }
+
+ public Uri execute() throws ImageExportException, InterruptedException {
+ Trace.beginSection("ImageExporter_execute");
+ Uri uri = null;
+ Instant start = null;
+ try {
+ if (LogConfig.DEBUG_STORAGE) {
+ Log.d(TAG, "image export started");
+ start = Instant.now();
+ }
+ uri = createEntry(mFormat, mCaptureTime);
+ throwIfInterrupted();
+
+ writeImage(mBitmap, mFormat, mQuality, uri);
+ throwIfInterrupted();
+
+ writeExif(uri, mBitmap.getWidth(), mBitmap.getHeight(), mCaptureTime);
+ throwIfInterrupted();
+
+ publishEntry(uri);
+
+ if (LogConfig.DEBUG_STORAGE) {
+ Log.d(TAG, "image export completed: "
+ + Duration.between(start, Instant.now()).toMillis() + " ms");
+ }
+ } catch (ImageExportException e) {
+ if (uri != null) {
+ mResolver.delete(uri, null);
+ }
+ throw e;
+ } finally {
+ Trace.endSection();
+ }
+ return uri;
+ }
+
+ Uri createEntry(CompressFormat format, ZonedDateTime time) throws ImageExportException {
+ Trace.beginSection("ImageExporter_createEntry");
+ try {
+ final ContentValues values = createMetadata(time, format);
+ Uri uri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ if (uri == null) {
+ throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
+ }
+ return uri;
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ void writeImage(Bitmap bitmap, CompressFormat format, int quality,
+ Uri contentUri) throws ImageExportException {
+ Trace.beginSection("ImageExporter_writeImage");
+ try (OutputStream out = mResolver.openOutputStream(contentUri)) {
+ long start = SystemClock.elapsedRealtime();
+ if (!bitmap.compress(format, quality, out)) {
+ throw new ImageExportException(IMAGE_COMPRESS_RETURNED_FALSE);
+ } else if (LogConfig.DEBUG_STORAGE) {
+ Log.d(TAG, "Bitmap.compress took "
+ + (SystemClock.elapsedRealtime() - start) + " ms");
+ }
+ } catch (IOException ex) {
+ throw new ImageExportException(OPEN_OUTPUT_STREAM_EXCEPTION, ex);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ void writeExif(Uri uri, int width, int height, ZonedDateTime captureTime)
+ throws ImageExportException {
+ Trace.beginSection("ImageExporter_writeExif");
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = mResolver.openFile(uri, "rw", null);
+ if (pfd == null) {
+ throw new ImageExportException(RESOLVER_OPEN_FILE_RETURNED_NULL);
+ }
+ ExifInterface exif;
+ try {
+ exif = new ExifInterface(pfd.getFileDescriptor());
+ } catch (IOException e) {
+ throw new ImageExportException(EXIF_READ_EXCEPTION, e);
+ }
+
+ updateExifAttributes(exif, width, height, captureTime);
+ try {
+ exif.saveAttributes();
+ } catch (IOException e) {
+ throw new ImageExportException(EXIF_WRITE_EXCEPTION, e);
+ }
+ } catch (FileNotFoundException e) {
+ throw new ImageExportException(RESOLVER_OPEN_FILE_EXCEPTION, e);
+ } finally {
+ closeQuietly(pfd);
+ Trace.endSection();
+ }
+ }
+
+ void publishEntry(Uri uri) throws ImageExportException {
+ Trace.beginSection("ImageExporter_publishEntry");
+ try {
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
+ final int rowsUpdated = mResolver.update(uri, values, /* extras */ null);
+ if (rowsUpdated < 1) {
+ throw new ImageExportException(RESOLVER_UPDATE_ZERO_ROWS);
+ }
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "compress [" + mBitmap + "] to [" + mFormat + "] at quality " + mQuality;
+ }
+ }
+
+ static String createFilename(ZonedDateTime time, CompressFormat format) {
+ return String.format(FILENAME_PATTERN, time, fileExtension(format));
+ }
+
+ static ContentValues createMetadata(ZonedDateTime captureTime, CompressFormat format) {
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, SCREENSHOTS_PATH);
+ values.put(MediaStore.MediaColumns.DISPLAY_NAME, createFilename(captureTime, format));
+ values.put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(format));
+ values.put(MediaStore.MediaColumns.DATE_ADDED, captureTime.toEpochSecond());
+ values.put(MediaStore.MediaColumns.DATE_MODIFIED, captureTime.toEpochSecond());
+ values.put(MediaStore.MediaColumns.DATE_EXPIRES,
+ captureTime.plus(PENDING_ENTRY_TTL).toEpochSecond());
+ values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+ return values;
+ }
+
+ static void updateExifAttributes(ExifInterface exif, int width, int height,
+ ZonedDateTime captureTime) {
+ exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY);
+ exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
+ exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
+
+ String dateTime = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(captureTime);
+ String subSec = DateTimeFormatter.ofPattern("SSS").format(captureTime);
+ String timeZone = DateTimeFormatter.ofPattern("xxx").format(captureTime);
+
+ exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime);
+ exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subSec);
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, timeZone);
+
+ exif.setAttribute(ExifInterface.TAG_DATETIME_DIGITIZED, dateTime);
+ exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, subSec);
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED, timeZone);
+ }
+
+ static String getMimeType(CompressFormat format) {
+ switch (format) {
+ case JPEG:
+ return "image/jpeg";
+ case PNG:
+ return "image/png";
+ case WEBP:
+ case WEBP_LOSSLESS:
+ case WEBP_LOSSY:
+ return "image/webp";
+ default:
+ throw new IllegalArgumentException("Unknown CompressFormat!");
+ }
+ }
+
+ static String fileExtension(CompressFormat format) {
+ switch (format) {
+ case JPEG:
+ return "jpg";
+ case PNG:
+ return "png";
+ case WEBP:
+ case WEBP_LOSSY:
+ case WEBP_LOSSLESS:
+ return "webp";
+ default:
+ throw new IllegalArgumentException("Unknown CompressFormat!");
+ }
+ }
+
+ private static void throwIfInterrupted() throws InterruptedException {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ }
+
+ static final class ImageExportException extends IOException {
+ ImageExportException(String message) {
+ super(message);
+ }
+
+ ImageExportException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
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 a991d3615..f9c7799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -34,7 +34,6 @@
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
-import static com.android.systemui.shared.system.WindowManagerWrapper.NAV_BAR_POS_INVALID;
import static com.android.systemui.statusbar.LightRevealScrimKt.getEnableLightReveal;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
@@ -176,7 +175,6 @@
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.brightness.BrightnessSlider;
import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CommandQueue;
@@ -1582,11 +1580,6 @@
return true;
}
- final int navbarPos = WindowManagerWrapper.getInstance().getNavBarPosition(mDisplayId);
- if (navbarPos == NAV_BAR_POS_INVALID) {
- return false;
- }
-
if (legacySplitScreen.splitPrimaryTask()) {
if (metricsDockAction != -1) {
mMetricsLogger.action(metricsDockAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 08e70a9..6ae5e90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -105,6 +105,9 @@
default void onExtremeBatterySaverChanged(boolean isExtreme) {
}
+
+ default void onWirelessChargingChanged(boolean isWirlessCharging) {
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 8c67072..da9aa97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -77,7 +77,7 @@
private boolean mCharged;
protected boolean mPowerSave;
private boolean mAodPowerSave;
- protected boolean mWirelessCharging;
+ private boolean mWirelessCharging;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -155,6 +155,7 @@
cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
+ cb.onWirelessChargingChanged(mWirelessCharging);
}
@Override
@@ -179,8 +180,12 @@
BatteryManager.BATTERY_STATUS_UNKNOWN);
mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
- mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
- == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ if (mWirelessCharging != (mCharging
+ && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
+ == BatteryManager.BATTERY_PLUGGED_WIRELESS)) {
+ mWirelessCharging = !mWirelessCharging;
+ fireWirelessChargingChanged();
+ }
boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
boolean unknown = !present;
@@ -227,6 +232,13 @@
}
}
+ private void fireWirelessChargingChanged() {
+ synchronized (mChangeCallbacks) {
+ mChangeCallbacks.forEach(batteryStateChangeCallback ->
+ batteryStateChangeCallback.onWirelessChargingChanged(mWirelessCharging));
+ }
+ }
+
@Override
public boolean isPluggedIn() {
return mPluggedIn;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
index 477424c..c9bc7e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java
@@ -91,6 +91,7 @@
private void openInternalNotificationPanel(String action) {
Intent intent = new Intent(mContext, TvNotificationPanelActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setAction(action);
mContext.startActivityAsUser(intent, UserHandle.SYSTEM);
}
@@ -113,6 +114,7 @@
if (ri != null && ri.activityInfo != null) {
if (ri.activityInfo.permission != null && ri.activityInfo.permission.equals(
Manifest.permission.STATUS_BAR_SERVICE)) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
} else {
Log.e(TAG,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4944284..31cc7bb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -31,6 +31,8 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.classifier.FalsingCollectorFake;
import org.junit.Before;
import org.junit.Test;
@@ -65,6 +67,7 @@
private LatencyTracker mLatencyTracker;
@Mock
private LiftToActivateListener mLiftToactivateListener;
+ private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
private View mDeleteButton;
@Mock
@@ -88,7 +91,8 @@
.thenReturn(mOkButton);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) {
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
+ mFalsingCollector) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 8c547b1..5709ce30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -16,12 +16,15 @@
package com.android.systemui.classifier;
-import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.any;
-
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,6 +38,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -45,7 +49,6 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,6 +56,8 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class BrightLineClassifierTest extends SysuiTestCase {
+ private static final long DOUBLE_TAP_TIMEOUT_MS = 1000;
+
private BrightLineFalsingManager mBrightLineFalsingManager;
@Mock
private FalsingDataProvider mFalsingDataProvider;
@@ -69,24 +74,35 @@
private FalsingClassifier mClassifierB;
private final List<MotionEvent> mMotionEventList = new ArrayList<>();
@Mock
- private HistoryTracker mHistoryTracker;
- private FakeSystemClock mSystemClock = new FakeSystemClock();
+ private HistoryTracker mHistoryTracker;;
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
+ private GestureCompleteListener mGestureCompleteListener;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
when(mClassifierB.classifyGesture(anyDouble(), anyDouble())).thenReturn(mPassedResult);
+ when(mSingleTapClassfier.isTap(any(List.class))).thenReturn(mPassedResult);
+ when(mDoubleTapClassifier.classifyGesture()).thenReturn(mPassedResult);
mClassifiers.add(mClassifierA);
mClassifiers.add(mClassifierB);
- when(mFalsingDataProvider.isDirty()).thenReturn(true);
when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
- mHistoryTracker, mSystemClock, false);
+ mHistoryTracker, mFakeExecutor, DOUBLE_TAP_TIMEOUT_MS, false);
+
+
+ ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
+ ArgumentCaptor.forClass(GestureCompleteListener.class);
+
+ verify(mFalsingDataProvider).addGestureCompleteListener(
+ gestureCompleteListenerCaptor.capture());
+
+ mGestureCompleteListener = gestureCompleteListenerCaptor.getValue();
}
@Test
@@ -179,15 +195,57 @@
@Test
public void testHistory() {
- ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
- ArgumentCaptor.forClass(GestureCompleteListener.class);
+ mGestureCompleteListener.onGestureComplete(1000);
- verify(mFalsingDataProvider).addGestureCompleteListener(
- gestureCompleteListenerCaptor.capture());
+ verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+ }
- GestureCompleteListener gestureCompleteListener = gestureCompleteListenerCaptor.getValue();
- gestureCompleteListener.onGestureComplete();
+ @Test
+ public void testHistory_singleTap() {
+ // When trying to classify single taps, we don't immediately add results to history.
+ mBrightLineFalsingManager.isFalseTap(false);
+ mGestureCompleteListener.onGestureComplete(1000);
- verify(mHistoryTracker).addResults(any(Collection.class), eq(mSystemClock.uptimeMillis()));
+ verify(mHistoryTracker, never()).addResults(any(), anyLong());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+
+ verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+ }
+
+ @Test
+ public void testHistory_multipleSingleTaps() {
+ // When trying to classify single taps, we don't immediately add results to history.
+ mBrightLineFalsingManager.isFalseTap(false);
+ mGestureCompleteListener.onGestureComplete(1000);
+ mBrightLineFalsingManager.isFalseTap(false);
+ mGestureCompleteListener.onGestureComplete(2000);
+
+ verify(mHistoryTracker, never()).addResults(any(), anyLong());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runNextReady();
+ verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
+ reset(mHistoryTracker);
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runNextReady();
+ verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
+ }
+
+ @Test
+ public void testHistory_doubleTap() {
+ // When trying to classify single taps, we don't immediately add results to history.
+ mBrightLineFalsingManager.isFalseTap(false);
+ mGestureCompleteListener.onGestureComplete(1000);
+ // Before checking for double tap, we may check for single-tap on the second gesture.
+ mBrightLineFalsingManager.isFalseTap(false);
+ mBrightLineFalsingManager.isFalseDoubleTap();
+ mGestureCompleteListener.onGestureComplete(2000);
+
+ // Double tap is immediately added to history. Single tap is never added.
+ verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
+
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index af5e789..23ef865 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -17,12 +17,15 @@
package com.android.systemui.classifier;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -38,6 +41,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -66,7 +70,6 @@
mKeyguardUpdateMonitor, mProximitySensor, mStatusBarStateController);
}
-
@Test
public void testRegisterSensor() {
mFalsingCollector.onScreenTurningOn();
@@ -110,7 +113,6 @@
@Test
public void testUnregisterSensor_StateTransition() {
-
ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor =
ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture());
@@ -120,4 +122,37 @@
stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE);
verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class));
}
+
+ @Test
+ public void testPassThroughGesture() {
+ MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ // Nothing passed initially
+ mFalsingCollector.onTouchEvent(down);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+ // Up event flushes the down event.
+ mFalsingCollector.onTouchEvent(up);
+ InOrder orderedCalls = inOrder(mFalsingDataProvider);
+ // We can't simply use "eq" or similar because the collector makes a copy of "down".
+ orderedCalls.verify(mFalsingDataProvider).onMotionEvent(
+ argThat(argument -> argument.getActionMasked() == MotionEvent.ACTION_DOWN));
+ orderedCalls.verify(mFalsingDataProvider).onMotionEvent(up);
+ }
+
+ @Test
+ public void testAvoidGesture() {
+ MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+
+ // Nothing passed initially
+ mFalsingCollector.onTouchEvent(down);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+
+ mFalsingCollector.avoidGesture();
+ // Up event would flush, but we were told to avoid.
+ mFalsingCollector.onTouchEvent(up);
+ verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
new file mode 100644
index 0000000..f566880
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.testing.AndroidTestingRunner;
+
+import androidx.exifinterface.media.ExifInterface;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@MediumTest // file I/O
+public class ImageExporterTest extends SysuiTestCase {
+
+ /** Executes directly in the caller's thread */
+ private static final Executor DIRECT_EXECUTOR = Runnable::run;
+ private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
+
+ private static final ZonedDateTime CAPTURE_TIME =
+ ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
+
+ @Test
+ public void testImageFilename() {
+ assertEquals("image file name", "Screenshot_20201215-131500.png",
+ ImageExporter.createFilename(CAPTURE_TIME, CompressFormat.PNG));
+ }
+
+ @Test
+ public void testUpdateExifAttributes_timeZoneUTC() throws IOException {
+ ExifInterface exifInterface = new ExifInterface(new ByteArrayInputStream(EXIF_FILE_TAG),
+ ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
+
+ ImageExporter.updateExifAttributes(exifInterface, 100, 100,
+ ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 18, 15), ZoneId.of("UTC")));
+
+ assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00",
+ exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+ assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00",
+ exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+ }
+
+ @Test
+ public void testImageExport() throws ExecutionException, InterruptedException, IOException {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ ContentResolver contentResolver = context.getContentResolver();
+ ImageExporter exporter = new ImageExporter(contentResolver);
+
+ Bitmap original = createCheckerBitmap(10, 10, 10);
+
+ ListenableFuture<Uri> direct = exporter.export(DIRECT_EXECUTOR, original, CAPTURE_TIME);
+ assertTrue("future should be done", direct.isDone());
+ assertFalse("future should not be canceled", direct.isCancelled());
+ Uri result = direct.get();
+
+ assertNotNull("Uri should not be null", result);
+ Bitmap decoded = null;
+ try (InputStream in = contentResolver.openInputStream(result)) {
+ decoded = BitmapFactory.decodeStream(in);
+ assertNotNull("decoded image should not be null", decoded);
+ assertTrue("original and decoded image should be identical", original.sameAs(decoded));
+
+ try (ParcelFileDescriptor pfd = contentResolver.openFile(result, "r", null)) {
+ assertNotNull(pfd);
+ ExifInterface exifInterface = new ExifInterface(pfd.getFileDescriptor());
+
+ assertEquals("Exif " + ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY,
+ exifInterface.getAttribute(ExifInterface.TAG_SOFTWARE));
+
+ assertEquals("Exif " + ExifInterface.TAG_IMAGE_WIDTH, 100,
+ exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0));
+ assertEquals("Exif " + ExifInterface.TAG_IMAGE_LENGTH, 100,
+ exifInterface.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0));
+
+ assertEquals("Exif " + ExifInterface.TAG_DATETIME_ORIGINAL, "2020:12:15 13:15:00",
+ exifInterface.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+ assertEquals("Exif " + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, "000",
+ exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL));
+ assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "-05:00",
+ exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL));
+
+ assertEquals("Exif " + ExifInterface.TAG_DATETIME_DIGITIZED, "2020:12:15 13:15:00",
+ exifInterface.getAttribute(ExifInterface.TAG_DATETIME_DIGITIZED));
+ assertEquals("Exif " + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, "000",
+ exifInterface.getAttribute(ExifInterface.TAG_SUBSEC_TIME_DIGITIZED));
+ assertEquals("Exif " + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, "-05:00",
+ exifInterface.getAttribute(ExifInterface.TAG_OFFSET_TIME_DIGITIZED));
+ }
+ } finally {
+ if (decoded != null) {
+ decoded.recycle();
+ }
+ contentResolver.delete(result, null);
+ }
+ }
+
+ @Test
+ public void testMediaStoreMetadata() {
+ ContentValues values = ImageExporter.createMetadata(CAPTURE_TIME, CompressFormat.PNG);
+ assertEquals("Pictures/Screenshots",
+ values.getAsString(MediaStore.MediaColumns.RELATIVE_PATH));
+ assertEquals("Screenshot_20201215-131500.png",
+ values.getAsString(MediaStore.MediaColumns.DISPLAY_NAME));
+ assertEquals("image/png", values.getAsString(MediaStore.MediaColumns.MIME_TYPE));
+ assertEquals(Long.valueOf(1608056100L),
+ values.getAsLong(MediaStore.MediaColumns.DATE_ADDED));
+ assertEquals(Long.valueOf(1608056100L),
+ values.getAsLong(MediaStore.MediaColumns.DATE_MODIFIED));
+ assertEquals(Integer.valueOf(1), values.getAsInteger(MediaStore.MediaColumns.IS_PENDING));
+ assertEquals(Long.valueOf(1608056100L + 86400L), // +1 day
+ values.getAsLong(MediaStore.MediaColumns.DATE_EXPIRES));
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private Bitmap createCheckerBitmap(int tileSize, int w, int h) {
+ Bitmap bitmap = Bitmap.createBitmap(w * tileSize, h * tileSize, Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(bitmap);
+ Paint paint = new Paint();
+ paint.setStyle(Paint.Style.FILL);
+
+ for (int i = 0; i < h; i++) {
+ int top = i * tileSize;
+ for (int j = 0; j < w; j++) {
+ int left = j * tileSize;
+ paint.setColor(paint.getColor() == Color.WHITE ? Color.BLACK : Color.WHITE);
+ c.drawRect(left, top, left + tileSize, top + tileSize, paint);
+ }
+ }
+ return bitmap;
+ }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 4e75a9e..1378776 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3597,8 +3597,8 @@
private boolean isNetworkPotentialSatisfier(
@NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) {
// listen requests won't keep up a network satisfying it. If this is not a multilayer
- // request, we can return immediately. For multilayer requests, we have to check to see if
- // any of the multilayer requests may have a potential satisfier.
+ // request, return immediately. For multilayer requests, check to see if any of the
+ // multilayer requests may have a potential satisfier.
if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
return false;
}
@@ -8234,6 +8234,13 @@
final IBinder iCb = cb.asBinder();
final NetworkRequestInfo nri = cbInfo.mRequestInfo;
+ // Connectivity Diagnostics are meant to be used with a single network request. It would be
+ // confusing for these networks to change when an NRI is satisfied in another layer.
+ if (nri.isMultilayerRequest()) {
+ throw new IllegalArgumentException("Connectivity Diagnostics do not support multilayer "
+ + "network requests.");
+ }
+
// This means that the client registered the same callback multiple times. Do
// not override the previous entry, and exit silently.
if (mConnectivityDiagnosticsCallbacks.containsKey(iCb)) {
@@ -8260,7 +8267,8 @@
synchronized (mNetworkForNetId) {
for (int i = 0; i < mNetworkForNetId.size(); i++) {
final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
- if (nai.satisfies(nri.request)) {
+ // Connectivity Diagnostics rejects multilayer requests at registration hence get(0)
+ if (nai.satisfies(nri.mRequests.get(0))) {
matchingNetworks.add(nai);
}
}
@@ -8388,7 +8396,8 @@
mConnectivityDiagnosticsCallbacks.entrySet()) {
final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
final NetworkRequestInfo nri = cbInfo.mRequestInfo;
- if (nai.satisfies(nri.request)) {
+ // Connectivity Diagnostics rejects multilayer requests at registration hence get(0).
+ if (nai.satisfies(nri.mRequests.get(0))) {
if (checkConnectivityDiagnosticsPermissions(
nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
results.add(entry.getValue().mCb);
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 500e768..f2b63a6 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -236,4 +236,9 @@
throw new RuntimeException(e.toString());
}
}
+
+ @Override
+ public long suggestScratchSize() throws RemoteException {
+ return getGsiService().suggestScratchSize();
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1841d67..8cb9b0a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14052,18 +14052,25 @@
callerApp = mPidsSelfLocked.get(pid);
}
}
- // Check if the instrumentation of the process has the permission. This covers the usual
- // test started from the shell (which has the permission) case. This is needed for apps
- // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
- // avoid breaking a bunch of existing tests and asking them to adopt shell permissions to do
- // this.
+
if (callerApp != null) {
+ // Check if the instrumentation of the process has the permission. This covers the usual
+ // test started from the shell (which has the permission) case. This is needed for apps
+ // targeting SDK level < S but we are also allowing for targetSdk S+ as a convenience to
+ // avoid breaking a bunch of existing tests and asking them to adopt shell permissions
+ // to do this.
ActiveInstrumentation instrumentation = callerApp.getActiveInstrumentation();
if (instrumentation != null && checkPermission(
permission.BROADCAST_CLOSE_SYSTEM_DIALOGS, -1, instrumentation.mSourceUid)
== PERMISSION_GRANTED) {
return true;
}
+ // This is the notification trampoline use-case for example, where apps use Intent.ACSD
+ // to close the shade prior to starting an activity.
+ WindowProcessController wmApp = callerApp.getWindowProcessController();
+ if (wmApp.canCloseSystemDialogsByToken()) {
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index e129561..d9c83da 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -749,9 +749,15 @@
// There are other callbacks in the queue, let's just update the originating
// token
if (mIsAllowedBgActivityStartsByStart) {
- mAppForAllowingBgActivityStartsByStart
- .addOrUpdateAllowBackgroundActivityStartsToken(
- this, getExclusiveOriginatingToken());
+ // mAppForAllowingBgActivityStartsByStart can be null here for example
+ // if get 2 calls to allowBgActivityStartsOnServiceStart() without a
+ // process attached to this ServiceRecord, so we need to perform a null
+ // check here.
+ if (mAppForAllowingBgActivityStartsByStart != null) {
+ mAppForAllowingBgActivityStartsByStart
+ .addOrUpdateAllowBackgroundActivityStartsToken(
+ this, getExclusiveOriginatingToken());
+ }
} else {
Slog.wtf(TAG,
"Service callback to revoke bg activity starts by service "
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
index b9c23b7..0881cd2 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
@@ -162,11 +162,6 @@
}
@Override
- void logWarn(String msg) {
- Slog.w(TAG, msg);
- }
-
- @Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {
ipw.println("{BinderLocationTimeZoneProvider}");
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
index ab64f97..a23b9d7 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
@@ -285,8 +285,12 @@
}
static void warnLog(String msg) {
+ warnLog(msg, null);
+ }
+
+ static void warnLog(String msg, @Nullable Throwable t) {
if (Log.isLoggable(TAG, Log.WARN)) {
- Slog.w(TAG, msg);
+ Slog.w(TAG, msg, t);
}
}
}
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
index 8b51ab4..e55d1cc 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
@@ -17,6 +17,7 @@
package com.android.server.location.timezone;
import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
@@ -345,12 +346,21 @@
}
mProviderListener = Objects.requireNonNull(providerListener);
ProviderState currentState = ProviderState.createStartingState(this);
- ProviderState newState = currentState.newState(
+ currentState = currentState.newState(
PROVIDER_STATE_STOPPED, null, null,
"initialize() called");
- setCurrentState(newState, false);
+ setCurrentState(currentState, false);
- onInitialize();
+ // Guard against uncaught exceptions due to initialization problems.
+ try {
+ onInitialize();
+ } catch (RuntimeException e) {
+ warnLog("Unable to initialize the provider", e);
+ currentState = currentState
+ .newState(PROVIDER_STATE_PERM_FAILED, null, null,
+ "Provider failed to initialize");
+ setCurrentState(currentState, true);
+ }
}
}
@@ -498,7 +508,7 @@
case PROVIDER_STATE_PERM_FAILED: {
// After entering perm failed, there is nothing to do. The remote peer is
// supposed to stop sending events after it has reported perm failure.
- logWarn("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent
+ warnLog("handleTimeZoneProviderEvent: Event=" + timeZoneProviderEvent
+ " received for provider=" + this + " when in failed state");
return;
}
@@ -509,7 +519,7 @@
+ " Failure event=" + timeZoneProviderEvent
+ " received for stopped provider=" + this
+ ", entering permanently failed state";
- logWarn(msg);
+ warnLog(msg);
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
@@ -522,7 +532,7 @@
case EVENT_TYPE_UNCERTAIN: {
// Any geolocation-related events received for a stopped provider are
// ignored: they should not happen.
- logWarn("handleTimeZoneProviderEvent:"
+ warnLog("handleTimeZoneProviderEvent:"
+ " event=" + timeZoneProviderEvent
+ " received for stopped provider=" + this
+ ", ignoring");
@@ -544,7 +554,7 @@
+ " Failure event=" + timeZoneProviderEvent
+ " received for provider=" + this
+ ", entering permanently failed state";
- logWarn(msg);
+ warnLog(msg);
ProviderState newState = currentState.newState(
PROVIDER_STATE_PERM_FAILED, null, null, msg);
setCurrentState(newState, true);
@@ -584,11 +594,6 @@
}
}
- /**
- * Implemented by subclasses.
- */
- abstract void logWarn(String msg);
-
@GuardedBy("mSharedLock")
private void assertIsStarted() {
ProviderState currentState = mCurrentState.get();
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
index 91b52f1..16f9e97 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
@@ -79,8 +79,8 @@
throw new IllegalStateException("listener already set");
}
this.mListener = listener;
+ onInitialize();
}
- onInitialize();
}
/**
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
index e11a7ce..4b321e6 100644
--- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProvider.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import java.time.Duration;
@@ -74,11 +73,6 @@
}
@Override
- void logWarn(String msg) {
- Slog.w(TAG, msg);
- }
-
- @Override
public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
synchronized (mSharedLock) {
ipw.println("{Stubbed LocationTimeZoneProvider}");
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a146c8c..c106558 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
@@ -291,7 +292,6 @@
import org.json.JSONException;
import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -308,7 +308,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -10416,12 +10416,15 @@
private final Set<String> mPackagesShown = new ArraySet<>();
@Override
- public IBinder getToken() {
- return ALLOWLIST_TOKEN;
- }
-
- @Override
- public boolean isActivityStartAllowed(int uid, String packageName) {
+ public boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid,
+ String packageName) {
+ checkArgument(!tokens.isEmpty());
+ for (IBinder token : tokens) {
+ if (token != ALLOWLIST_TOKEN) {
+ // We only block or warn if the start is exclusively due to notification
+ return true;
+ }
+ }
String toastMessage = "Indirect activity start from " + packageName;
String logcatMessage =
"Indirect notification activity start (trampoline) from " + packageName;
@@ -10439,6 +10442,15 @@
}
}
+ @Override
+ public boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid) {
+ // If the start is allowed via notification, we allow the app to close system dialogs
+ // only if their targetSdk < S, otherwise they have no valid reason to do this since
+ // trampolines are blocked.
+ return tokens.contains(ALLOWLIST_TOKEN)
+ && !CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
+ }
+
private void toast(String message) {
mUiHandler.post(() ->
Toast.makeText(getUiContext(), message + "\nSee go/s-trampolines.",
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5cd22e0..de77372 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -88,7 +88,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
@@ -106,6 +105,9 @@
// The amount of time rules instances can exist without their owning app being installed.
private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
+ // pkg|userId => uid
+ protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();
+
private final Context mContext;
private final H mHandler;
private final SettingsObserver mSettingsObserver;
@@ -145,7 +147,7 @@
mHandler = new H(looper);
addCallback(mMetrics);
mAppOps = context.getSystemService(AppOpsManager.class);
- mNotificationManager = context.getSystemService(NotificationManager.class);
+ mNotificationManager = context.getSystemService(NotificationManager.class);
mDefaultConfig = readDefaultConfig(mContext.getResources());
updateDefaultAutomaticRuleNames();
@@ -384,17 +386,25 @@
synchronized (mConfig) {
if (mConfig == null) return false;
newConfig = mConfig.copy();
- ZenRule rule = newConfig.automaticRules.get(id);
- if (rule == null) return false;
- if (canManageAutomaticZenRule(rule)) {
+ ZenRule ruleToRemove = newConfig.automaticRules.get(id);
+ if (ruleToRemove == null) return false;
+ if (canManageAutomaticZenRule(ruleToRemove)) {
newConfig.automaticRules.remove(id);
+ if (ruleToRemove.pkg != null && !"android".equals(ruleToRemove.pkg)) {
+ for (ZenRule currRule : newConfig.automaticRules.values()) {
+ if (currRule.pkg != null && currRule.pkg.equals(ruleToRemove.pkg)) {
+ break; // no need to remove from cache
+ }
+ }
+ mRulesUidCache.remove(getPackageUserKey(ruleToRemove.pkg, newConfig.user));
+ }
if (DEBUG) Log.d(TAG, "removeZenRule zenRule=" + id + " reason=" + reason);
} else {
throw new SecurityException(
"Cannot delete rules not owned by your condition provider");
}
dispatchOnAutomaticRuleStatusChanged(
- mConfig.user, rule.pkg, id, AUTOMATIC_RULE_STATUS_REMOVED);
+ mConfig.user, ruleToRemove.pkg, id, AUTOMATIC_RULE_STATUS_REMOVED);
return setConfigLocked(newConfig, reason, null, true);
}
}
@@ -1192,7 +1202,6 @@
public void pullRules(List<StatsEvent> events) {
synchronized (mConfig) {
final int numConfigs = mConfigs.size();
- int id = 0;
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
final ZenModeConfig config = mConfigs.valueAt(i);
@@ -1208,16 +1217,16 @@
.writeByteArray(config.toZenPolicy().toProto());
events.add(data.build());
if (config.manualRule != null && config.manualRule.enabler != null) {
- ruleToProto(user, config.manualRule, events);
+ ruleToProtoLocked(user, config.manualRule, events);
}
for (ZenRule rule : config.automaticRules.values()) {
- ruleToProto(user, rule, events);
+ ruleToProtoLocked(user, rule, events);
}
}
}
}
- private void ruleToProto(int user, ZenRule rule, List<StatsEvent> events) {
+ private void ruleToProtoLocked(int user, ZenRule rule, List<StatsEvent> events) {
// Make the ID safe.
String id = rule.id == null ? "" : rule.id;
if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
@@ -1231,9 +1240,6 @@
id = ZenModeConfig.MANUAL_RULE_ID;
}
- // TODO: fetch the uid from the package manager
- int uid = "android".equals(pkg) ? Process.SYSTEM_UID : 0;
-
SysUiStatsEvent.Builder data;
data = mStatsEventBuilderFactory.newBuilder()
.setAtomId(DND_MODE_RULE)
@@ -1242,7 +1248,7 @@
.writeBoolean(false) // channels_bypassing unused for rules
.writeInt(rule.zenMode)
.writeString(id)
- .writeInt(uid)
+ .writeInt(getPackageUid(pkg, user))
.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
byte[] policyProto = new byte[]{};
if (rule.zenPolicy != null) {
@@ -1252,6 +1258,24 @@
events.add(data.build());
}
+ private int getPackageUid(String pkg, int user) {
+ if ("android".equals(pkg)) {
+ return Process.SYSTEM_UID;
+ }
+ final String key = getPackageUserKey(pkg, user);
+ if (mRulesUidCache.get(key) == null) {
+ try {
+ mRulesUidCache.put(key, mPm.getPackageUidAsUser(pkg, user));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ return mRulesUidCache.getOrDefault(key, -1);
+ }
+
+ private static String getPackageUserKey(String pkg, int user) {
+ return pkg + "|" + user;
+ }
+
@VisibleForTesting
protected final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1653896..c9d8eef 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1018,9 +1018,8 @@
if (info.minAspectRatio != 0) {
pw.println(prefix + "minAspectRatio=" + info.minAspectRatio);
}
- if (info.supportsSizeChanges) {
- pw.println(prefix + "supportsSizeChanges=true");
- }
+ pw.println(prefix + "supportsSizeChanges="
+ + ActivityInfo.sizeChangesSupportModeToString(info.supportsSizeChanges()));
if (info.configChanges != 0) {
pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
}
@@ -6555,7 +6554,7 @@
* aspect ratio.
*/
boolean shouldUseSizeCompatMode() {
- if (info.supportsSizeChanges) {
+ if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) {
return false;
}
if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ea04c64..cd5ea46 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -34,7 +34,6 @@
import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1902,9 +1901,6 @@
@Override
public boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return setTaskWindowingModeSplitScreenPrimary(taskId, toTop);
- }
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "setTaskWindowingMode()");
synchronized (mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
@@ -2235,28 +2231,6 @@
}
/**
- * Moves the specified task to the primary-split-screen stack.
- *
- * @param taskId Id of task to move.
- * @param toTop If the task and stack should be moved to the top.
- * @return Whether the task was successfully put into splitscreen.
- */
- @Override
- public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, boolean toTop) {
- enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
- "setTaskWindowingModeSplitScreenPrimary()");
- synchronized (mGlobalLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- return setTaskWindowingModeSplitScreen(taskId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- toTop);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- /**
* Moves the specified task into a split-screen tile.
*/
private boolean setTaskWindowingModeSplitScreen(int taskId, int windowingMode, boolean toTop) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
index af6c255..200f207 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
@@ -18,25 +18,29 @@
import android.os.IBinder;
+import java.util.Collection;
+
/**
- * Callback to be called when a background activity start is allowed exclusively because of the
- * token provided in {@link #getToken()}.
+ * Callback to decide activity starts and related operations based on originating tokens.
*/
public interface BackgroundActivityStartCallback {
/**
- * The token for which this callback is responsible for deciding whether the app can start
- * background activities or not.
+ * Returns true if the background activity start originating from {@code tokens} should be
+ * allowed or not.
*
- * Ideally this should just return a final variable, don't do anything costly here (don't hold
- * any locks).
- */
- IBinder getToken();
-
- /**
- * Returns true if the background activity start due to originating token in {@link #getToken()}
- * should be allowed or not.
+ * Note that if the start was allowed due to a mechanism other than tokens (eg. permission),
+ * this won't be called.
*
* This will be called holding the WM lock, don't do anything costly here.
*/
- boolean isActivityStartAllowed(int uid, String packageName);
+ boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid, String packageName);
+
+ /**
+ * Returns whether {@code uid} can send {@link android.content.Intent
+ * #ACTION_CLOSE_SYSTEM_DIALOGS}, presumably to start activities, based on the originating
+ * tokens {@code tokens} currently associated with potential activity starts.
+ *
+ * This will be called holding the AM and WM lock, don't do anything costly here.
+ */
+ boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 447e66e..292d7ea 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -27,7 +27,6 @@
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
@@ -689,9 +688,6 @@
final BLASTSyncEngine mSyncEngine;
- int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
- Rect mDockedStackCreateBounds;
-
boolean mIsPc;
/**
* Flag that indicates that desktop mode is forced for public secondary screens.
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 9d64af7..90949cb 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -585,9 +585,8 @@
}
/**
- * If there are no tokens, we don't allow *by token*. If there are tokens, we need to check if
- * the callback handles all the tokens, if so we ask the callback if the activity should be
- * started, otherwise we allow.
+ * If there are no tokens, we don't allow *by token*. If there are tokens, we ask the callback
+ * if the start is allowed for these tokens, otherwise if there is no callback we allow.
*/
private boolean isBackgroundStartAllowedByToken() {
if (mBackgroundActivityStartTokens.isEmpty()) {
@@ -597,16 +596,22 @@
// We have tokens but no callback to decide => allow
return true;
}
- IBinder callbackToken = mBackgroundActivityStartCallback.getToken();
- for (IBinder token : mBackgroundActivityStartTokens.values()) {
- if (token != callbackToken) {
- // The callback doesn't handle all the tokens => allow
- return true;
- }
+ // The callback will decide
+ return mBackgroundActivityStartCallback.isActivityStartAllowed(
+ mBackgroundActivityStartTokens.values(), mInfo.uid, mInfo.packageName);
+ }
+
+ /**
+ * Returns whether this process is allowed to close system dialogs via a background activity
+ * start token that allows the close system dialogs operation (eg. notification).
+ */
+ public boolean canCloseSystemDialogsByToken() {
+ synchronized (mAtm.mGlobalLock) {
+ return !mBackgroundActivityStartTokens.isEmpty()
+ && mBackgroundActivityStartCallback != null
+ && mBackgroundActivityStartCallback.canCloseSystemDialogs(
+ mBackgroundActivityStartTokens.values(), mInfo.uid);
}
- // The callback handles all the tokens => callback decides
- return mBackgroundActivityStartCallback.isActivityStartAllowed(mInfo.uid,
- mInfo.packageName);
}
private boolean isBoundByForegroundUid() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5f6d76be..7b4c1be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7620,10 +7620,9 @@
+ " as profile owner on user " + currentForegroundUser);
// Sets profile owner on current foreground user since
// the human user will complete the DO setup workflow from there.
- mInjector.binderWithCleanCallingIdentity(() ->
- manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
- /* managedUser= */ currentForegroundUser,
- /* adminExtras= */ null));
+ manageUserUnchecked(/* deviceOwner= */ admin, /* profileOwner= */ admin,
+ /* managedUser= */ currentForegroundUser,
+ /* adminExtras= */ null);
}
return true;
}
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java
deleted file mode 100644
index 84b886e..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneEventTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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 com.android.internal.location.timezone;
-
-import static com.android.internal.location.timezone.ParcelableTestSupport.assertRoundTripParcelable;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-
-import static java.util.Collections.singletonList;
-
-import org.junit.Test;
-
-import java.util.List;
-
-public class LocationTimeZoneEventTest {
-
- private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 9999;
-
- private static final List<String> ARBITRARY_TIME_ZONE_IDS = singletonList("Europe/London");
-
- @Test(expected = RuntimeException.class)
- public void testSetInvalidEventType() {
- new LocationTimeZoneEvent.Builder().setEventType(Integer.MAX_VALUE);
- }
-
- @Test(expected = RuntimeException.class)
- public void testBuildUnsetEventType() {
- new LocationTimeZoneEvent.Builder()
- .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
- .build();
- }
-
- @Test(expected = RuntimeException.class)
- public void testInvalidTimeZoneIds() {
- new LocationTimeZoneEvent.Builder()
- .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
- .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS)
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS)
- .build();
- }
-
- @Test
- public void testEquals() {
- LocationTimeZoneEvent.Builder builder1 = new LocationTimeZoneEvent.Builder()
- .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
- {
- LocationTimeZoneEvent one = builder1.build();
- assertEquals(one, one);
- }
-
- LocationTimeZoneEvent.Builder builder2 = new LocationTimeZoneEvent.Builder()
- .setEventType(LocationTimeZoneEvent.EVENT_TYPE_UNCERTAIN)
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertEquals(one, two);
- assertEquals(two, one);
- }
-
- builder1.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertNotEquals(one, two);
- assertNotEquals(two, one);
- }
-
- builder2.setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS + 1);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertEquals(one, two);
- assertEquals(two, one);
- }
-
- builder2.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertNotEquals(one, two);
- assertNotEquals(two, one);
- }
-
- builder1.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertEquals(one, two);
- assertEquals(two, one);
- }
-
- builder2.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertNotEquals(one, two);
- assertNotEquals(two, one);
- }
-
- builder1.setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
- {
- LocationTimeZoneEvent one = builder1.build();
- LocationTimeZoneEvent two = builder2.build();
- assertEquals(one, two);
- assertEquals(two, one);
- }
- }
-
- @Test
- public void testParcelable() {
- LocationTimeZoneEvent.Builder builder = new LocationTimeZoneEvent.Builder()
- .setEventType(LocationTimeZoneEvent.EVENT_TYPE_PERMANENT_FAILURE)
- .setElapsedRealtimeMillis(ARBITRARY_ELAPSED_REALTIME_MILLIS);
- assertRoundTripParcelable(builder.build());
-
- builder.setEventType(LocationTimeZoneEvent.EVENT_TYPE_SUCCESS)
- .setTimeZoneIds(ARBITRARY_TIME_ZONE_IDS);
- assertRoundTripParcelable(builder.build());
- }
-}
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java b/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
deleted file mode 100644
index 95daa36..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/LocationTimeZoneProviderRequestTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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 com.android.internal.location.timezone;
-
-import static com.android.internal.location.timezone.ParcelableTestSupport.assertRoundTripParcelable;
-
-import org.junit.Test;
-
-import java.time.Duration;
-
-public class LocationTimeZoneProviderRequestTest {
-
- @Test
- public void testParcelable() {
- LocationTimeZoneProviderRequest.Builder builder =
- new LocationTimeZoneProviderRequest.Builder()
- .setReportLocationTimeZone(false);
- assertRoundTripParcelable(builder.build());
-
- builder.setReportLocationTimeZone(true)
- .setInitializationTimeoutMillis(Duration.ofMinutes(5).toMillis());
-
- assertRoundTripParcelable(builder.build());
- }
-}
diff --git a/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java b/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java
deleted file mode 100644
index ece5d00..0000000
--- a/services/tests/servicestests/src/com/android/internal/location/timezone/ParcelableTestSupport.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 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.internal.location.timezone;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.reflect.Field;
-
-/** Utility methods related to {@link Parcelable} objects used in several tests. */
-final class ParcelableTestSupport {
-
- private ParcelableTestSupport() {}
-
- /** Returns the result of parceling and unparceling the argument. */
- @SuppressWarnings("unchecked")
- public static <T extends Parcelable> T roundTripParcelable(T parcelable) {
- Parcel parcel = Parcel.obtain();
- parcel.writeTypedObject(parcelable, 0);
- parcel.setDataPosition(0);
-
- Parcelable.Creator<T> creator;
- try {
- Field creatorField = parcelable.getClass().getField("CREATOR");
- creator = (Parcelable.Creator<T>) creatorField.get(null);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new AssertionError(e);
- }
- T toReturn = parcel.readTypedObject(creator);
- parcel.recycle();
- return toReturn;
- }
-
- public static <T extends Parcelable> void assertRoundTripParcelable(T instance) {
- assertEquals(instance, roundTripParcelable(instance));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
index dc81237..4de4d95 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
@@ -1053,11 +1053,6 @@
}
@Override
- void logWarn(String msg) {
- System.out.println(msg);
- }
-
- @Override
public void dump(IndentingPrintWriter pw, String[] args) {
// Nothing needed for tests.
}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
new file mode 100644
index 0000000..28aff18
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 847766
+nfuller@google.com
+include /core/java/android/app/timedetector/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index cfdd246..57d5323 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -58,6 +58,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -101,7 +102,6 @@
import com.android.internal.R;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.ManagedServices.UserProfiles;
@@ -110,9 +110,7 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -134,9 +132,13 @@
private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
private static final int ZEN_MODE_FOR_TESTING = 99;
+ private static final String CUSTOM_PKG_NAME = "not.android";
+ private static final int CUSTOM_PKG_UID = 1;
+ private static final String CUSTOM_RULE_ID = "custom_rule";
ConditionProviders mConditionProviders;
@Mock NotificationManager mNotificationManager;
+ @Mock PackageManager mPackageManager;
private Resources mResources;
private TestableLooper mTestableLooper;
private ZenModeHelper mZenModeHelperSpy;
@@ -146,7 +148,7 @@
private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory;
@Before
- public void setUp() {
+ public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
@@ -169,6 +171,10 @@
mConditionProviders.addSystemProvider(new CountdownConditionProvider());
mZenModeHelperSpy = spy(new ZenModeHelper(mContext, mTestableLooper.getLooper(),
mConditionProviders, mStatsEventBuilderFactory));
+
+ when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
+ .thenReturn(CUSTOM_PKG_UID);
+ mZenModeHelperSpy.mPm = mPackageManager;
}
private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException {
@@ -238,19 +244,24 @@
private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules(int zenMode) {
ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>();
+ ZenModeConfig.ZenRule rule = createCustomAutomaticRule(zenMode, CUSTOM_RULE_ID);
+ automaticRules.put(rule.id, rule);
+ return automaticRules;
+ }
+
+ private ZenModeConfig.ZenRule createCustomAutomaticRule(int zenMode, String id) {
ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule();
final ScheduleInfo customRuleInfo = new ScheduleInfo();
customRule.enabled = true;
customRule.creationTime = 0;
- customRule.id = "customRule";
- customRule.name = "Custom Rule";
+ customRule.id = id;
+ customRule.name = "Custom Rule with id=" + id;
customRule.zenMode = zenMode;
customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo);
customRule.configurationActivity =
- new ComponentName("not.android", "ScheduleConditionProvider");
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider");
customRule.pkg = customRule.configurationActivity.getPackageName();
- automaticRules.put("customRule", customRule);
- return automaticRules;
+ return customRule;
}
@Test
@@ -893,7 +904,7 @@
if (builder.getAtomId() == DND_MODE_RULE) {
if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) {
foundCustomEvent = true;
- assertEquals(0, builder.getInt(UID_FIELD_NUMBER));
+ assertEquals(CUSTOM_PKG_UID, builder.getInt(UID_FIELD_NUMBER));
assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER));
}
} else {
@@ -904,6 +915,46 @@
}
@Test
+ public void ruleUidsCached() throws Exception {
+ setupZenConfig();
+ // one enabled automatic rule
+ mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+ List<StatsEvent> events = new LinkedList<>();
+ // first time retrieving uid:
+ mZenModeHelperSpy.pullRules(events);
+ verify(mPackageManager, atLeastOnce()).getPackageUidAsUser(anyString(), anyInt());
+
+ // second time retrieving uid:
+ reset(mPackageManager);
+ mZenModeHelperSpy.pullRules(events);
+ verify(mPackageManager, never()).getPackageUidAsUser(anyString(), anyInt());
+
+ // new rule from same package + user added
+ reset(mPackageManager);
+ ZenModeConfig.ZenRule rule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ CUSTOM_RULE_ID + "2");
+ mZenModeHelperSpy.mConfig.automaticRules.put(rule.id, rule);
+ mZenModeHelperSpy.pullRules(events);
+ verify(mPackageManager, never()).getPackageUidAsUser(anyString(), anyInt());
+ }
+
+ @Test
+ public void ruleUidAutomaticZenRuleRemovedUpdatesCache() throws Exception {
+ when(mContext.checkCallingPermission(anyString()))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ setupZenConfig();
+ // one enabled automatic rule
+ mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules();
+ List<StatsEvent> events = new LinkedList<>();
+
+ mZenModeHelperSpy.pullRules(events);
+ mZenModeHelperSpy.removeAutomaticZenRule(CUSTOM_RULE_ID, "test");
+ assertTrue(-1
+ == mZenModeHelperSpy.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1));
+ }
+
+ @Test
public void testProtoRedactsIds() throws Exception {
setupZenConfig();
// one enabled automatic rule
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 1ecf850..cf977b4 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -47,6 +47,7 @@
"testables",
"ub-uiautomator",
"hamcrest-library",
+ "platform-compat-test-rules",
],
libs: [
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 2acb647..8983b4e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
@@ -53,7 +52,6 @@
opts.setRotationAnimationHint(ROTATION_ANIMATION_ROTATE);
opts.setTaskAlwaysOnTop(true);
opts.setTaskOverlay(true, true);
- opts.setSplitScreenCreateMode(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
Bundle optsBundle = opts.toBundle();
// Try and merge the constructed options with a new set of options
@@ -71,7 +69,5 @@
assertTrue(restoredOpts.getTaskAlwaysOnTop());
assertTrue(restoredOpts.getTaskOverlay());
assertTrue(restoredOpts.canTaskOverlayResume());
- assertEquals(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
- restoredOpts.getSplitScreenCreateMode());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 4892ef3..93ef126b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1006,8 +1006,6 @@
assertNotRestoreTask(
() -> mAtm.setTaskWindowingMode(taskId, WINDOWING_MODE_FULLSCREEN,
false/* toTop */));
- assertNotRestoreTask(
- () -> mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, false /* toTop */));
}
@Test
@@ -1144,8 +1142,6 @@
assertSecurityException(expectCallable,
() -> mAtm.moveTaskToRootTask(0, INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
- () -> mAtm.setTaskWindowingModeSplitScreenPrimary(0, true));
- assertSecurityException(expectCallable,
() -> mAtm.moveTopActivityToPinnedRootTask(INVALID_STACK_ID, new Rect()));
assertSecurityException(expectCallable, () -> mAtm.getAllRootTaskInfos());
assertSecurityException(expectCallable,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index c7175a0c..e190248 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -49,6 +49,8 @@
import android.app.ActivityManagerInternal;
import android.app.TaskStackListener;
import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -58,7 +60,11 @@
import androidx.test.filters.MediumTest;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.util.ArrayList;
@@ -73,6 +79,9 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class SizeCompatTests extends WindowTestsBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private Task mTask;
private ActivityRecord mActivity;
@@ -535,6 +544,26 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+ public void testNoSizeCompatWhenPerAppOverrideSet() {
+ setUpDisplaySizeWithApp(1000, 2500);
+
+ // Make the task root resizable.
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+ assertFalse(activity.shouldUseSizeCompatMode());
+ }
+
+ @Test
public void testLaunchWithFixedRotationTransform() {
final int dw = 1000;
final int dh = 2500;
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index d81c24d..ba7770d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -107,7 +107,7 @@
configuration.endRotation)
navBarLayerIsAlwaysVisible(enabled = false)
statusBarLayerIsAlwaysVisible(enabled = false)
- visibleLayersShownMoreThanOneConsecutiveEntry(bugId = 174541970)
+ visibleLayersShownMoreThanOneConsecutiveEntry()
appLayerReplacesWallpaperLayer(testApp.`package`)
}