Merge changes from topic "nov29" into main
* changes:
`LruCache` under Ravenwood.
`FileUtils` and `AtomicFile` under Ravenwood.
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index b3e8ea8..1fb5f34 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -56,6 +56,7 @@
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.pinner-aconfig-java{.generated_srcjars}",
+ ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
@@ -598,6 +599,19 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Controls
+aconfig_declarations {
+ name: "android.service.controls.flags-aconfig",
+ package: "android.service.controls.flags",
+ srcs: ["core/java/android/service/controls/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.controls.flags-aconfig-java",
+ aconfig_declarations: "android.service.controls.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Voice
aconfig_declarations {
name: "android.service.voice.flags-aconfig",
diff --git a/Ravenwood.bp b/Ravenwood.bp
index df9ac82..03f3f0f 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -32,6 +32,8 @@
cmd: "$(location hoststubgen) " +
"@$(location ravenwood/ravenwood-standard-options.txt) " +
+ "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
+
"--out-impl-jar $(location ravenwood.jar) " +
"--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
@@ -52,6 +54,8 @@
// Following files are created just as FYI.
"hoststubgen_keep_all.txt",
"hoststubgen_dump.txt",
+
+ "hoststubgen_framework-minus-apex.log",
],
visibility: ["//visibility:private"],
}
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 1bd8da82..5a32a02 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2163,6 +2163,8 @@
mActivityManagerInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_TIMEZONE_CHANGED, "");
+ mOptsTimeBroadcast.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
}
@@ -4608,6 +4610,8 @@
mActivityManagerInternal.getBootTimeTempAllowListDuration(),
TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
PowerExemptionManager.REASON_TIME_CHANGED, "");
+ mOptsTimeBroadcast.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
null /* receiverPermission */, mOptsTimeBroadcast.toBundle());
// The world has changed on us, so we need to re-evaluate alarms
diff --git a/core/api/current.txt b/core/api/current.txt
index 5b339fa..5e7a383 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9676,6 +9676,7 @@
method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
+ method @FlaggedApi("android.companion.perm_sync_user_consent") public boolean isPermissionTransferUserConsented(int);
method public void requestNotificationAccess(android.content.ComponentName);
method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -40108,6 +40109,9 @@
method public final boolean onUnbind(@NonNull android.content.Intent);
method public abstract void performControlAction(@NonNull String, @NonNull android.service.controls.actions.ControlAction, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public static void requestAddControl(@NonNull android.content.Context, @NonNull android.content.ComponentName, @NonNull android.service.controls.Control);
+ field @FlaggedApi("android.service.controls.flags.home_panel_dream") public static final int CONTROLS_SURFACE_ACTIVITY_PANEL = 0; // 0x0
+ field @FlaggedApi("android.service.controls.flags.home_panel_dream") public static final int CONTROLS_SURFACE_DREAM = 1; // 0x1
+ field @FlaggedApi("android.service.controls.flags.home_panel_dream") public static final String EXTRA_CONTROLS_SURFACE = "android.service.controls.extra.CONTROLS_SURFACE";
field public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS = "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
field public static final String META_DATA_PANEL_ACTIVITY = "android.service.controls.META_DATA_PANEL_ACTIVITY";
field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
@@ -43132,8 +43136,8 @@
field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
field public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
- field public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
- field public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL = "additional_settings_caller_id_visibility_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.show_call_id_and_call_waiting_in_additional_settings_menu") public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL = "additional_settings_call_waiting_visibility_bool";
field public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
field public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
field public static final String KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool";
@@ -52050,6 +52054,7 @@
method public float getHandwritingBoundsOffsetLeft();
method public float getHandwritingBoundsOffsetRight();
method public float getHandwritingBoundsOffsetTop();
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public int getHandwritingDelegateFlags();
method @Nullable public Runnable getHandwritingDelegatorCallback();
method public final boolean getHasOverlappingRendering();
method public final int getHeight();
@@ -52423,6 +52428,7 @@
method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
method @FlaggedApi("android.view.flags.view_velocity_api") public void setFrameContentVelocity(float);
method public void setHandwritingBoundsOffsets(float, float, float, float);
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void setHandwritingDelegateFlags(int);
method public void setHandwritingDelegatorCallback(@Nullable Runnable);
method public void setHapticFeedbackEnabled(boolean);
method public void setHasTransientState(boolean);
@@ -55678,6 +55684,7 @@
public final class InputMethodManager {
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int);
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
@@ -55725,6 +55732,7 @@
method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
method public void updateSelection(android.view.View, int, int, int, int);
method @Deprecated public void viewClicked(android.view.View);
+ field @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 1; // 0x1
field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -59542,6 +59550,7 @@
method public void setOnClickFillInIntent(@IdRes int, android.content.Intent);
method public void setOnClickPendingIntent(@IdRes int, android.app.PendingIntent);
method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse);
+ method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public void setOnStylusHandwritingPendingIntent(@IdRes int, @Nullable android.app.PendingIntent);
method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent);
method public void setProgressBar(@IdRes int, int, int, boolean);
method public void setRadioGroupChecked(@IdRes int, @IdRes int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0497c60..847edd1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10555,6 +10555,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountType();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean);
+ method @NonNull public android.graphics.drawable.Drawable getUserBadge();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
@@ -12787,6 +12788,7 @@
field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED = 8; // 0x8
field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9; // 0x9
field public static final int ERROR_CODE_REMOTE_EXCEPTION = 7; // 0x7
+ field @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final int ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED = 10; // 0xa
field public static final int ERROR_CODE_UNKNOWN = 0; // 0x0
}
@@ -16140,8 +16142,10 @@
public interface RegistrationManager {
field public static final int SUGGESTED_ACTION_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4; // 0x4
field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK = 1; // 0x1
field public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.add_rat_related_suggested_action_to_ims_registration") public static final int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3; // 0x3
}
public static class RegistrationManager.RegistrationCallback {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index e0ce917..e4a03c5 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1358,6 +1358,36 @@
}
/**
+ * Return the current state of consent for permission transfer for the association.
+ * True if the user has allowed permission transfer for the association, false otherwise.
+ *
+ * <p>
+ * Note: The initial user consent is collected via
+ * {@link #buildPermissionTransferUserConsentIntent(int) a permission transfer user consent dialog}.
+ * After the user has made their initial selection, they can toggle the permission transfer
+ * feature in the settings.
+ * This method always returns the state of the toggle setting.
+ * </p>
+ *
+ * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association
+ * of the companion device recorded by CompanionDeviceManager
+ * @return True if the user has consented to the permission transfer, or false otherwise.
+ * @throws DeviceNotAssociatedException Exception if the companion device is not associated with
+ * the user or the calling app.
+ */
+ @UserHandleAware
+ @FlaggedApi(Flags.FLAG_PERM_SYNC_USER_CONSENT)
+ public boolean isPermissionTransferUserConsented(int associationId) {
+ try {
+ return mService.isPermissionTransferUserConsented(mContext.getOpPackageName(),
+ mContext.getUserId(), associationId);
+ } catch (RemoteException e) {
+ ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Start system data transfer which has been previously approved by the user.
*
* <p>Before calling this method, the app needs to make sure there's a communication channel
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index c5a1988..22689f3 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -97,6 +97,8 @@
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
+ boolean isPermissionTransferUserConsented(String callingPackage, int userId, int associationId);
+
void startSystemDataTransfer(String packageName, int userId, int associationId,
in ISystemDataTransferCallback callback);
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 6e33dff..9e410b8 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -27,3 +27,10 @@
description: "Enable device presence APIs"
bug: "283000075"
}
+
+flag {
+ name: "perm_sync_user_consent"
+ namespace: "companion"
+ description: "Expose perm sync user consent API"
+ bug: "309528663"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index d21b818..1b90570 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -101,3 +101,10 @@
description: "Feature flag to enable the refactored Package Installer app with updated UI."
bug: "182205982"
}
+
+flag {
+ name: "improve_install_dont_kill"
+ namespace: "package_manager_service"
+ description: "Feature flag to reduce app crashes caused by split installs with INSTALL_DONT_KILL"
+ bug: "291212866"
+}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 11bddfb..f4795f8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,7 +16,6 @@
package android.os;
-import android.annotation.FlaggedApi;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.CurrentTimeMillisLong;
@@ -3103,7 +3102,8 @@
/**
* Intent that is broadcast when Low Power Standby is enabled or disabled.
- * This broadcast is only sent to registered receivers.
+ * This broadcast is only sent to registered receivers and receivers holding
+ * {@code android.permission.MANAGE_LOW_POWER_STANDBY}.
*
* @see #isLowPowerStandbyEnabled()
*/
@@ -3113,7 +3113,8 @@
/**
* Intent that is broadcast when Low Power Standby policy is changed.
- * This broadcast is only sent to registered receivers.
+ * This broadcast is only sent to registered receivers and receivers holding
+ * {@code android.permission.MANAGE_LOW_POWER_STANDBY}.
*
* @see #isExemptFromLowPowerStandby()
* @see #isAllowedInLowPowerStandby(int)
@@ -3125,7 +3126,6 @@
/**
* Intent that is broadcast when Low Power Standby exempt ports change.
- * This broadcast is only sent to registered receivers.
*
* @see #getActiveLowPowerStandbyPorts
* @hide
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ec6d20f..c280d13 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -16,6 +16,7 @@
package android.os;
+import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL;
import static android.app.admin.DevicePolicyResources.UNDEFINED;
@@ -5652,6 +5653,38 @@
}
/**
+ * Retrieves a user badge associated with the current context user. This is only
+ * applicable to profile users since non-profile users do not have badges.
+ *
+ * @return A {@link Drawable} user badge corresponding to the context user
+ * @throws android.content.res.Resources.NotFoundException if the user is not a profile or
+ * does not have a badge defined.
+ * @hide
+ */
+ @SystemApi
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.INTERACT_ACROSS_USERS})
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public @NonNull Drawable getUserBadge() {
+ if (!isProfile(mUserId)) {
+ throw new Resources.NotFoundException("No badge found for this user.");
+ }
+ if (isManagedProfile(mUserId)) {
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ return dpm.getResources().getDrawable(
+ android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE,
+ SOLID_COLORED, () -> getDefaultUserBadge(mUserId));
+ }
+ return getDefaultUserBadge(mUserId);
+ }
+
+ private Drawable getDefaultUserBadge(@UserIdInt int userId){
+ return mContext.getResources().getDrawable(getUserBadgeResId(userId), mContext.getTheme());
+ }
+
+ /**
* If the target user is a profile of the calling user or the caller
* is itself a profile, then this returns a copy of the label with
* badging for accessibility services like talkback. E.g. passing in "Email"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f64400..1a33b768 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8846,6 +8846,24 @@
"reduce_bright_colors_persist_across_reboots";
/**
+ * Setting that specifies whether Even Dimmer - a feature that allows the brightness
+ * slider to go below what the display can conventionally do, should be enabled.
+ *
+ * @hide
+ */
+ public static final String EVEN_DIMMER_ACTIVATED =
+ "even_dimmer_activated";
+
+ /**
+ * Setting that specifies which nits level Even Dimmer should allow the screen brightness
+ * to go down to.
+ *
+ * @hide
+ */
+ public static final String EVEN_DIMMER_MIN_NITS =
+ "even_dimmer_min_nits";
+
+ /**
* List of the enabled print services.
*
* N and beyond uses {@link #DISABLED_PRINT_SERVICES}. But this might be used in an upgrade
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 0133bd8..0dc0413 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -8,7 +8,7 @@
}
flag {
- name: "fix_unlocked_device_required_keys"
+ name: "fix_unlocked_device_required_keys_v2"
namespace: "hardware_backed_security"
description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
bug: "296464083"
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 0272bb9..5cbde9f 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -16,6 +16,8 @@
package android.service.controls;
import android.Manifest;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -33,12 +35,15 @@
import android.os.RemoteException;
import android.service.controls.actions.ControlAction;
import android.service.controls.actions.ControlActionWrapper;
+import android.service.controls.flags.Flags;
import android.service.controls.templates.ControlTemplate;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.concurrent.Flow.Publisher;
import java.util.concurrent.Flow.Subscriber;
@@ -82,6 +87,40 @@
public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
"android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({CONTROLS_SURFACE_ACTIVITY_PANEL, CONTROLS_SURFACE_DREAM})
+ public @interface ControlsSurface {
+ }
+
+ /**
+ * Controls are being shown on the device controls activity panel.
+ */
+ @FlaggedApi(Flags.FLAG_HOME_PANEL_DREAM)
+ public static final int CONTROLS_SURFACE_ACTIVITY_PANEL = 0;
+
+ /**
+ * Controls are being shown as a dream, while the device is idle.
+ */
+ @FlaggedApi(Flags.FLAG_HOME_PANEL_DREAM)
+ public static final int CONTROLS_SURFACE_DREAM = 1;
+
+ /**
+ * Integer extra whose value specifies the surface which controls are being displayed on.
+ * <p>
+ * The possible values are:
+ * <ul>
+ * <li>{@link #CONTROLS_SURFACE_ACTIVITY_PANEL}
+ * <li>{@link #CONTROLS_SURFACE_DREAM}
+ * </ul>
+ *
+ * This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
+ * is launched.
+ */
+ @FlaggedApi(Flags.FLAG_HOME_PANEL_DREAM)
+ public static final String EXTRA_CONTROLS_SURFACE =
+ "android.service.controls.extra.CONTROLS_SURFACE";
+
/**
* @hide
*/
diff --git a/core/java/android/service/controls/flags/flags.aconfig b/core/java/android/service/controls/flags/flags.aconfig
new file mode 100644
index 0000000..3a28844
--- /dev/null
+++ b/core/java/android/service/controls/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.service.controls.flags"
+
+flag {
+ name: "home_panel_dream"
+ namespace: "systemui"
+ description: "Enables the home controls dream feature."
+ bug: "298025023"
+}
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 46ea158..7660ed9 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -233,7 +233,7 @@
} finally {
mapParcel.recycle();
// To prevent memory leaks, we can close the ranking map fd here.
- // Because a reference to this still exists
+ // This is safe to do because a reference to this still exists.
if (buffer != null && mRankingMapFd != null) {
SharedMemory.unmap(buffer);
mRankingMapFd.close();
diff --git a/core/java/android/service/voice/HotwordDetectionServiceFailure.java b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
index 420dac1..c8b60c4 100644
--- a/core/java/android/service/voice/HotwordDetectionServiceFailure.java
+++ b/core/java/android/service/voice/HotwordDetectionServiceFailure.java
@@ -89,6 +89,11 @@
@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
public static final int ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION = 9;
+ /** Indicates shutdown of {@link HotwordDetectionService} due to voice activation op being
+ * disabled. */
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
+ public static final int ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED = 10;
+
/**
* @hide
*/
@@ -100,7 +105,10 @@
ERROR_CODE_DETECT_TIMEOUT,
ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION,
ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE,
- ERROR_CODE_REMOTE_EXCEPTION
+ ERROR_CODE_REMOTE_EXCEPTION,
+ ERROR_CODE_ON_TRAINING_DATA_EGRESS_LIMIT_EXCEEDED,
+ ERROR_CODE_ON_TRAINING_DATA_SECURITY_EXCEPTION,
+ ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface HotwordDetectionServiceErrorCode {}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 90663c7..147c15b 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -216,6 +216,14 @@
default CompatibilityInfo.Translator getTranslator() {
return null;
}
+
+ /**
+ * Notifies when the state of running animation is changed. The state is either "running" or
+ * "idle".
+ *
+ * @param running {@code true} if there is any animation running; {@code false} otherwise.
+ */
+ default void notifyAnimationRunningStateChanged(boolean running) {}
}
private static final String TAG = "InsetsController";
@@ -749,6 +757,9 @@
final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
mFrame, state1, mToState, RESIZE_INTERPOLATOR,
ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this);
+ if (mRunningAnimations.isEmpty()) {
+ mHost.notifyAnimationRunningStateChanged(true);
+ }
mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
}
};
@@ -1382,6 +1393,9 @@
}
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
+ if (mRunningAnimations.isEmpty()) {
+ mHost.notifyAnimationRunningStateChanged(true);
+ }
mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
@@ -1588,6 +1602,9 @@
break;
}
}
+ if (mRunningAnimations.isEmpty()) {
+ mHost.notifyAnimationRunningStateChanged(false);
+ }
onAnimationStateChanged(removedTypes, false /* running */);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index d0a4d1a..ad0bf7c 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -197,7 +197,9 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
- value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
+ value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
+ FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
public @interface FrameRateCompatibility {}
// From native_window.h. Keep these in sync.
@@ -242,6 +244,13 @@
*/
public static final int FRAME_RATE_COMPATIBILITY_MIN = 102;
+ // From window.h. Keep these in sync.
+ /**
+ * The surface requests a frame rate that is greater than or equal to {@code frameRate}.
+ * @hide
+ */
+ public static final int FRAME_RATE_COMPATIBILITY_GTE = 103;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CHANGE_FRAME_RATE_"},
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d58c07d..a268bca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -34,6 +34,7 @@
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.flags.Flags.viewVelocityApi;
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
@@ -5154,9 +5155,10 @@
private Runnable mHandwritingDelegatorCallback;
private String mAllowedHandwritingDelegatePackageName;
- // These two fields are set if the view is a handwriting delegate.
+ // These three fields are set if the view is a handwriting delegate.
private boolean mIsHandwritingDelegate;
private String mAllowedHandwritingDelegatorPackageName;
+ private @InputMethodManager.HandwritingDelegateFlags int mHandwritingDelegateFlags;
/**
* Solid color to use as a background when creating the drawing cache. Enables
@@ -12747,6 +12749,30 @@
}
/**
+ * Sets flags configuring the handwriting delegation behavior for this delegate editor view.
+ *
+ * <p>This method has no effect unless {@link #setIsHandwritingDelegate} is also called to
+ * configure this view to act as a handwriting delegate.
+ *
+ * @param flags {@link InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or
+ * {@code 0}
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public void setHandwritingDelegateFlags(
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ mHandwritingDelegateFlags = flags;
+ }
+
+ /**
+ * Returns flags configuring the handwriting delegation behavior for this delegate editor view,
+ * as set by {@link #setHandwritingDelegateFlags}.
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public @InputMethodManager.HandwritingDelegateFlags int getHandwritingDelegateFlags() {
+ return mHandwritingDelegateFlags;
+ }
+
+ /**
* Gets the coordinates of this view in the coordinate space of the
* {@link Surface} that contains the view.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cac5387..9d2ab1f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -92,6 +92,7 @@
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.accessibility.Flags.forceInvertColor;
+import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
@@ -816,6 +817,8 @@
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
+ private boolean mInsetsAnimationRunning;
+
/**
* The resolved pointer icon type requested by this window.
* A null value indicates the resolved pointer icon has not yet been calculated.
@@ -2179,6 +2182,10 @@
}
}
+ void notifyInsetsAnimationRunningStateChanged(boolean running) {
+ mInsetsAnimationRunning = running;
+ }
+
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
@@ -11355,6 +11362,10 @@
}
private boolean canContinueThrottle(View source, int changeType) {
+ if (!reduceWindowContentChangedEventThrottle()) {
+ // Old behavior. Always throttle.
+ return true;
+ }
if (mSource == null) {
// We don't have a pending event.
return true;
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index a2708ee..40730e8 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -279,6 +279,13 @@
return null;
}
+ @Override
+ public void notifyAnimationRunningStateChanged(boolean running) {
+ if (mViewRoot != null) {
+ mViewRoot.notifyInsetsAnimationRunningStateChanged(running);
+ }
+ }
+
private boolean isVisibleToUser() {
return mViewRoot.getHostVisibility() == View.VISIBLE;
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c337cb4..aa4275d6 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,7 +53,21 @@
flag {
namespace: "accessibility"
+ name: "reduce_window_content_changed_event_throttle"
+ description: "Reduces the throttle of AccessibilityEvent of TYPE_WINDOW_CONTENT_CHANGED"
+ bug: "277305460"
+}
+
+flag {
+ namespace: "accessibility"
name: "update_always_on_a11y_service"
description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
bug: "298869916"
}
+
+flag {
+ name: "enable_system_pinch_zoom_gesture"
+ namespace: "accessibility"
+ description: "Feature flag for system pinch zoom gesture detector and related opt-out apis"
+ bug: "283323770"
+}
\ No newline at end of file
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 614102b..c244287 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -514,14 +514,15 @@
@NonNull IInputMethodClient client,
@UserIdInt int userId,
@NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
final IInputMethodManager service = getService();
if (service == null) {
return false;
}
try {
return service.acceptStylusHandwritingDelegation(
- client, userId, delegatePackageName, delegatorPackageName);
+ client, userId, delegatePackageName, delegatorPackageName, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 589b7a3..6d7a543 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -17,6 +17,7 @@
package android.view.inputmethod;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -425,6 +426,23 @@
private static final boolean OPTIMIZE_NONEDITABLE_VIEWS =
SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true);
+ /** @hide */
+ @IntDef(flag = true, prefix = { "HANDWRITING_DELEGATE_FLAG_" }, value = {
+ HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HandwritingDelegateFlags {}
+
+ /**
+ * Flag indicating that views from the default home screen ({@link Intent#CATEGORY_HOME}) may
+ * act as a handwriting delegator for the delegate editor view. If set, views from the home
+ * screen package will be trusted for handwriting delegation, in addition to views in the {@code
+ * delegatorPackageName} passed to {@link #acceptStylusHandwritingDelegation(View, String,
+ * int)}.
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 0x0001;
+
/**
* @deprecated Use {@link IInputMethodManagerGlobalInvoker} instead.
*/
@@ -2345,17 +2363,20 @@
* @see #isStylusHandwritingAvailable()
*/
public void startStylusHandwriting(@NonNull View view) {
- startStylusHandwritingInternal(view, null /* delegatorPackageName */);
+ startStylusHandwritingInternal(
+ view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
}
private boolean startStylusHandwritingInternal(
- @NonNull View view, @Nullable String delegatorPackageName) {
+ @NonNull View view, @Nullable String delegatorPackageName,
+ @HandwritingDelegateFlags int handwritingDelegateFlags) {
Objects.requireNonNull(view);
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
- fallbackImm.startStylusHandwritingInternal(view, delegatorPackageName);
+ fallbackImm.startStylusHandwritingInternal(
+ view, delegatorPackageName, handwritingDelegateFlags);
}
boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
@@ -2375,7 +2396,7 @@
if (useDelegation) {
return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
- delegatorPackageName);
+ delegatorPackageName, handwritingDelegateFlags);
} else {
IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
}
@@ -2470,16 +2491,17 @@
*/
public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
return startStylusHandwritingInternal(
- delegateView, delegateView.getContext().getOpPackageName());
+ delegateView, delegateView.getContext().getOpPackageName(),
+ delegateView.getHandwritingDelegateFlags());
}
/**
* Accepts and starts a stylus handwriting session on the delegate view, if handwriting
* initiation delegation was previously requested using
- * {@link #prepareStylusHandwritingDelegation(View, String)} from te delegator and the view
+ * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
* belongs to a specified delegate package.
*
- * <p>Note: If delegator and delegate are in same application package use
+ * <p>Note: If delegator and delegate are in the same application package, use
* {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
*
* @param delegateView delegate view capable of receiving input via {@link InputConnection}
@@ -2493,8 +2515,35 @@
public boolean acceptStylusHandwritingDelegation(
@NonNull View delegateView, @NonNull String delegatorPackageName) {
Objects.requireNonNull(delegatorPackageName);
+ return startStylusHandwritingInternal(
+ delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags());
+ }
- return startStylusHandwritingInternal(delegateView, delegatorPackageName);
+ /**
+ * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+ * initiation delegation was previously requested using {@link
+ * #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
+ * a specified delegate package.
+ *
+ * <p>Note: If delegator and delegate are in the same application package, use {@link
+ * #acceptStylusHandwritingDelegation(View)} instead.
+ *
+ * @param delegateView delegate view capable of receiving input via {@link InputConnection} on
+ * which {@link #startStylusHandwriting(View)} will be called.
+ * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
+ * @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
+ * @return {@code true} if view belongs to allowed delegate package declared in {@link
+ * #prepareStylusHandwritingDelegation(View, String)} and handwriting session can start.
+ * @see #prepareStylusHandwritingDelegation(View, String)
+ * @see #acceptStylusHandwritingDelegation(View)
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull View delegateView, @NonNull String delegatorPackageName,
+ @HandwritingDelegateFlags int flags) {
+ Objects.requireNonNull(delegatorPackageName);
+
+ return startStylusHandwritingInternal(delegateView, delegatorPackageName, flags);
}
/**
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 7486362..dc6aa6c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -31,3 +31,10 @@
bug: "284527000"
is_fixed_read_only: true
}
+
+flag {
+ name: "home_screen_handwriting_delegator"
+ namespace: "input_method"
+ description: "Feature flag for supporting stylus handwriting delegation from RemoteViews on the home screen"
+ bug: "279959705"
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1acebf4..da31348 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,11 +16,14 @@
package android.widget;
+import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
+
import android.annotation.AttrRes;
import android.annotation.ColorInt;
import android.annotation.ColorRes;
import android.annotation.DimenRes;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IdRes;
import android.annotation.IntDef;
import android.annotation.LayoutRes;
@@ -92,6 +95,7 @@
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
+import android.view.MotionEvent;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewGroup;
@@ -239,6 +243,7 @@
private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
private static final int SET_REMOTE_ADAPTER_TAG = 33;
+ private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -1510,6 +1515,53 @@
}
}
+ /** Helper action to configure handwriting delegation via {@link PendingIntent}. */
+ private class SetOnStylusHandwritingResponse extends Action {
+ final PendingIntent mPendingIntent;
+
+ SetOnStylusHandwritingResponse(@IdRes int id, @Nullable PendingIntent pendingIntent) {
+ this.mViewId = id;
+ this.mPendingIntent = pendingIntent;
+ }
+
+ SetOnStylusHandwritingResponse(@NonNull Parcel parcel) {
+ mViewId = parcel.readInt();
+ mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
+ }
+
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mViewId);
+ PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
+ final View target = root.findViewById(mViewId);
+ if (target == null) return;
+
+ if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
+ Log.w(LOG_TAG, "Cannot use setOnStylusHandwritingPendingIntent for collection item "
+ + "(id: " + mViewId + ")");
+ return;
+ }
+
+ if (mPendingIntent != null) {
+ RemoteResponse response = RemoteResponse.fromPendingIntent(mPendingIntent);
+ target.setHandwritingDelegatorCallback(
+ () -> response.handleViewInteraction(target, params.handler));
+ target.setAllowedHandwritingDelegatePackage(mPendingIntent.getCreatorPackage());
+ } else {
+ target.setHandwritingDelegatorCallback(null);
+ target.setAllowedHandwritingDelegatePackage(null);
+ }
+ }
+
+ @Override
+ public int getActionTag() {
+ return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG;
+ }
+ }
+
/**
* Equivalent to calling
* {@link android.widget.CompoundButton#setOnCheckedChangeListener(
@@ -4189,6 +4241,8 @@
return new SetRemoteCollectionItemListAdapterAction(parcel);
case ATTRIBUTE_REFLECTION_ACTION_TAG:
return new AttributeReflectionAction(parcel);
+ case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
+ return new SetOnStylusHandwritingResponse(parcel);
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -4742,6 +4796,33 @@
}
/**
+ * Equivalent to calling {@link View#setHandwritingDelegatorCallback(Runnable)} to send the
+ * provided {@link PendingIntent}.
+ *
+ * <p>A common use case is a remote view which looks like a text editor but does not actually
+ * support text editing itself, and clicking on the remote view launches an activity containing
+ * an EditText. To support handwriting initiation in this case, this method can be called on the
+ * remote view to configure it as a handwriting delegator, meaning that stylus movement on the
+ * remote view triggers a {@link PendingIntent} and starts handwriting mode for the delegate
+ * EditText. The {@link PendingIntent} is typically the same as the one passed to {@link
+ * #setOnClickPendingIntent} which launches the activity containing the EditText. The EditText
+ * should call {@link View#setIsHandwritingDelegate} to set it as a delegate, and also use
+ * {@link View#setAllowedHandwritingDelegatorPackage} or {@link
+ * android.view.inputmethod.InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED}
+ * if necessary to support delegators from the package displaying the remote view.
+ *
+ * @param viewId identifier of the view that will trigger the {@link PendingIntent} when a
+ * stylus {@link MotionEvent} occurs within the view's bounds
+ * @param pendingIntent the {@link PendingIntent} to send, or {@code null} to clear the
+ * handwriting delegation
+ */
+ @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
+ public void setOnStylusHandwritingPendingIntent(
+ @IdRes int viewId, @Nullable PendingIntent pendingIntent) {
+ addAction(new SetOnStylusHandwritingResponse(viewId, pendingIntent));
+ }
+
+ /**
* @hide
* Equivalent to calling
* {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index e10f7c8..478aeec 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -70,20 +70,4 @@
/** Updates a state of camera compat control for stretched issues in the viewfinder. */
void updateCameraCompatControlState(in WindowContainerToken task, int state);
-
- /**
- * Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
- * requested orientations to others.
- *
- * @param isDisabled when {@code true}, the system always ignores the value of {@link
- * com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
- * requested orientation is respected.
- * @param fromOrientations The orientations we want to map to the correspondent orientations
- * in toOrientation.
- * @param toOrientations The orientations we map to the ones in fromOrientations at the same
- * index
- */
- void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
- in int[] fromOrientations, in int[] toOrientations);
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index cd1275c..6d36b57 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -266,31 +266,6 @@
}
/**
- * Controls whether ignore orientation request logic in {@link
- * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
- * requested orientation to others.
- *
- * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
- * value of {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}
- * and app requested orientation is respected.
- * @param fromOrientations The orientations we want to map to the correspondent orientations
- * in toOrientation.
- * @param toOrientations The orientations we map to the ones in fromOrientations at the same
- * index
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
- public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
- @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
- try {
- mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
- fromOrientations, toOrientations);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Gets the executor to run callbacks on.
* @hide
*/
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 7be27be..1bd0982 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -87,10 +87,6 @@
public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
"persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
- /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */
- public static final Flag VISIT_RISKY_URIS = devFlag(
- "persist.sysui.notification.visit_risky_uris");
-
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
"persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 1c3fd93..595bf3b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -152,8 +152,8 @@
in String delegatorPackageName);
/** Accepts and starts a stylus handwriting session for the delegate view **/
- boolean acceptStylusHandwritingDelegation(in IInputMethodClient client,
- in int userId, in String delegatePackageName, in String delegatorPackageName);
+ boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId,
+ in String delegatePackageName, in String delegatorPackageName, int flags);
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 4d6ed80..3887dd7 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -268,6 +268,13 @@
optional SettingProto enhanced_voice_privacy_enabled = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ message EvenDimmer {
+ optional SettingProto even_dimmer_activated = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto even_dimmer_min_nits = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional EvenDimmer even_dimmer = 98;
+
optional SettingProto font_weight_adjustment = 85 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Gesture {
@@ -712,5 +719,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 98;
+ // Next tag = 99;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1d4e01a..ba1f392 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2246,6 +2246,9 @@
<!-- The default volume for the ring stream -->
<integer name="config_audio_ring_vol_default">5</integer>
+ <!-- The default min volume for the alarm stream -->
+ <integer name="config_audio_alarm_min_vol">1</integer>
+
<!-- The default value for whether head tracking for
spatial audio is enabled for a newly connected audio device -->
<bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -6830,6 +6833,9 @@
window that does not wrap content). -->
<bool name="config_allowFloatingWindowsFillScreen">false</bool>
+ <!-- Whether to enable left-right split in portrait on this device -->
+ <bool name="config_leftRightSplitInPortrait">false</bool>
+
<!-- Whether scroll haptic feedback is enabled for rotary encoder scrolls on
{@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
devices. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b0fa4b..7787c5d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -287,6 +287,7 @@
<java-symbol type="integer" name="config_audio_notif_vol_steps" />
<java-symbol type="integer" name="config_audio_ring_vol_default" />
<java-symbol type="integer" name="config_audio_ring_vol_steps" />
+ <java-symbol type="integer" name="config_audio_alarm_min_vol" />
<java-symbol type="bool" name="config_spatial_audio_head_tracking_enabled_default" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
@@ -406,6 +407,7 @@
<java-symbol type="bool" name="config_supportsMultiWindow" />
<java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" />
<java-symbol type="bool" name="config_supportsMultiDisplay" />
+ <java-symbol type="bool" name="config_leftRightSplitInPortrait" />
<java-symbol type="integer" name="config_supportsNonResizableMultiWindow" />
<java-symbol type="integer" name="config_respectsActivityMinWidthHeightMultiWindow" />
<java-symbol type="dimen" name="config_minPercentageMultiWindowSupportHeight" />
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 1048742..e7e1740 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -500,7 +500,7 @@
key ESCAPE {
base: none
- alt, meta: fallback HOME
+ alt: fallback HOME
ctrl: fallback MENU
}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 4d2d960..4511f3b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -50,3 +50,10 @@
bug: "290220798"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_left_right_split_in_portrait"
+ namespace: "multitasking"
+ description: "Enables left/right split in portrait"
+ bug: "291018646"
+}
diff --git a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml b/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
deleted file mode 100644
index d732b01..0000000
--- a/libs/WindowManager/Shell/res/layout/docked_stack_divider.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<com.android.wm.shell.legacysplitscreen.DividerView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
-
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"
- android:background="@color/split_divider_background"/>
-
- <com.android.wm.shell.legacysplitscreen.MinimizedDockShadow
- style="@style/DockedDividerMinimizedShadow"
- android:id="@+id/minimized_dock_shadow"
- android:alpha="0"/>
-
- <com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
- android:id="@+id/docked_divider_handle"
- android:contentDescription="@string/accessibility_divider"
- android:background="@null"/>
-
-</com.android.wm.shell.legacysplitscreen.DividerView>
diff --git a/libs/WindowManager/Shell/res/layout/split_divider.xml b/libs/WindowManager/Shell/res/layout/split_divider.xml
index e3be700..db35c8c 100644
--- a/libs/WindowManager/Shell/res/layout/split_divider.xml
+++ b/libs/WindowManager/Shell/res/layout/split_divider.xml
@@ -24,17 +24,16 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <View
- style="@style/DockedDividerBackground"
- android:id="@+id/docked_divider_background"/>
-
<com.android.wm.shell.common.split.DividerHandleView
- style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
<com.android.wm.shell.common.split.DividerRoundedCorner
+ android:id="@+id/docked_divider_rounded_corner"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index a95323f..1b96fa2 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -16,13 +16,6 @@
*/
-->
<resources>
- <!-- Divider handle size for legacy split screen -->
- <dimen name="docked_divider_handle_width">2dp</dimen>
- <dimen name="docked_divider_handle_height">16dp</dimen>
- <!-- Divider handle size for split screen -->
- <dimen name="split_divider_handle_width">3dp</dimen>
- <dimen name="split_divider_handle_height">72dp</dimen>
-
<!-- Padding between status bar and bubbles when displayed in expanded state, smaller
value in landscape since we have limited vertical space-->
<dimen name="bubble_padding_top">4dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values-land/styles.xml b/libs/WindowManager/Shell/res/values-land/styles.xml
deleted file mode 100644
index e89f65b..0000000
--- a/libs/WindowManager/Shell/res/values-land/styles.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="DockedDividerBackground">
- <item name="android:layout_width">@dimen/split_divider_bar_width</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:layout_gravity">center_horizontal</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerHandle">
- <item name="android:layout_gravity">center</item>
- <item name="android:layout_width">48dp</item>
- <item name="android:layout_height">96dp</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">8dp</item>
- <item name="android:layout_height">match_parent</item>
- </style>
-</resources>
-
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f20d44d..8f9de61 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -96,6 +96,9 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
<!-- Divider handle size for split screen -->
+ <dimen name="split_divider_handle_region_width">96dp</dimen>
+ <dimen name="split_divider_handle_region_height">48dp</dimen>
+
<dimen name="split_divider_handle_width">72dp</dimen>
<dimen name="split_divider_handle_height">3dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 468cfd5..08c2a02 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -60,20 +60,9 @@
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">@dimen/split_divider_bar_width</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:background">@color/split_divider_background</item>
- </style>
-
- <style name="DockedDividerMinimizedShadow">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">8dp</item>
- </style>
-
- <style name="DockedDividerHandle">
+ <item name="android:layout_height">match_parent</item>
<item name="android:layout_gravity">center</item>
- <item name="android:layout_width">96dp</item>
- <item name="android:layout_height">48dp</item>
+ <item name="android:background">@color/split_divider_background</item>
</style>
<style name="TvPipEduText">
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index ec26800..999da24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -68,24 +68,33 @@
};
private final Paint mPaint = new Paint();
- private final int mWidth;
- private final int mHeight;
- private final int mTouchingWidth;
- private final int mTouchingHeight;
+ private int mWidth;
+ private int mHeight;
+ private int mTouchingWidth;
+ private int mTouchingHeight;
private int mCurrentWidth;
private int mCurrentHeight;
private AnimatorSet mAnimator;
private boolean mTouching;
private boolean mHovering;
- private final int mHoveringWidth;
- private final int mHoveringHeight;
+ private int mHoveringWidth;
+ private int mHoveringHeight;
+ private boolean mIsLeftRightSplit;
public DividerHandleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint.setColor(getResources().getColor(R.color.docked_divider_handle, null));
mPaint.setAntiAlias(true);
- mWidth = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_width);
- mHeight = getResources().getDimensionPixelSize(R.dimen.split_divider_handle_height);
+ updateDimens();
+ }
+
+ private void updateDimens() {
+ mWidth = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_height
+ : R.dimen.split_divider_handle_width);
+ mHeight = getResources().getDimensionPixelSize(mIsLeftRightSplit
+ ? R.dimen.split_divider_handle_width
+ : R.dimen.split_divider_handle_height);
mCurrentWidth = mWidth;
mCurrentHeight = mHeight;
mTouchingWidth = mWidth > mHeight ? mWidth / 2 : mWidth;
@@ -94,6 +103,11 @@
mHoveringHeight = mHeight > mWidth ? ((int) (mHeight * 1.5f)) : mHeight;
}
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ updateDimens();
+ }
+
/** Sets touching state for this handle view. */
public void setTouching(boolean touching, boolean animate) {
if (touching == mTouching) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index 364bb65..834c15d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -47,6 +46,7 @@
private InvertedRoundedCornerDrawInfo mTopRightCorner;
private InvertedRoundedCornerDrawInfo mBottomLeftCorner;
private InvertedRoundedCornerDrawInfo mBottomRightCorner;
+ private boolean mIsLeftRightSplit;
public DividerRoundedCorner(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -98,8 +98,8 @@
return false;
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ void setIsLeftRightSplit(boolean isLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
}
/**
@@ -134,7 +134,7 @@
}
private void calculateStartPos(Point outPos) {
- if (isLandscape()) {
+ if (mIsLeftRightSplit) {
// Place left corner at the right side of the divider bar.
outPos.x = isLeftCorner()
? getWidth() / 2 + mDividerWidth / 2
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0b0c693..0f0fbd9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.common.split;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
@@ -27,6 +26,8 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.DeviceConfig;
@@ -65,12 +66,15 @@
public static final long TOUCH_ANIMATION_DURATION = 150;
public static final long TOUCH_RELEASE_ANIMATION_DURATION = 200;
+ private final Paint mPaint = new Paint();
+ private final Rect mBackgroundRect = new Rect();
private final int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private SplitLayout mSplitLayout;
private SplitWindowManager mSplitWindowManager;
private SurfaceControlViewHost mViewHost;
private DividerHandleView mHandle;
+ private DividerRoundedCorner mCorners;
private View mBackground;
private int mTouchElevation;
@@ -81,6 +85,8 @@
private boolean mInteractive;
private boolean mSetTouchRegion = true;
private int mLastDraggingPosition;
+ private int mHandleRegionWidth;
+ private int mHandleRegionHeight;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -123,7 +129,7 @@
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
- if (isLandscape()) {
+ if (mSplitLayout.isLeftRightSplit()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_left_full)));
if (snapAlgorithm.isFirstSplitTargetAvailable()) {
@@ -215,6 +221,17 @@
mViewHost = viewHost;
layout.getDividerBounds(mDividerBounds);
onInsetsChanged(insetsState, false /* animate */);
+
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ mHandle.setIsLeftRightSplit(isLeftRightSplit);
+ mCorners.setIsLeftRightSplit(isLeftRightSplit);
+
+ mHandleRegionWidth = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_height
+ : R.dimen.split_divider_handle_region_width);
+ mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
+ ? R.dimen.split_divider_handle_region_width
+ : R.dimen.split_divider_handle_region_height);
}
void onInsetsChanged(InsetsState insetsState, boolean animate) {
@@ -255,30 +272,47 @@
super.onFinishInflate();
mDividerBar = findViewById(R.id.divider_bar);
mHandle = findViewById(R.id.docked_divider_handle);
- mBackground = findViewById(R.id.docked_divider_background);
+ mCorners = findViewById(R.id.docked_divider_rounded_corner);
mTouchElevation = getResources().getDimensionPixelSize(
R.dimen.docked_stack_divider_lift_elevation);
mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
mInteractive = true;
setOnTouchListener(this);
mHandle.setAccessibilityDelegate(mHandleDelegate);
+ setWillNotDraw(false);
+ mPaint.setColor(getResources().getColor(R.color.split_divider_background, null));
+ mPaint.setAntiAlias(true);
+ mPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mSetTouchRegion) {
- mTempRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
- mHandle.getBottom());
+ int startX = (mDividerBounds.width() - mHandleRegionWidth) / 2;
+ int startY = (mDividerBounds.height() - mHandleRegionHeight) / 2;
+ mTempRect.set(startX, startY, startX + mHandleRegionWidth,
+ startY + mHandleRegionHeight);
mSplitWindowManager.setTouchRegion(mTempRect);
mSetTouchRegion = false;
}
+
+ if (changed) {
+ boolean isHorizontalSplit = mSplitLayout.isLeftRightSplit();
+ int dividerSize = getResources().getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ left = isHorizontalSplit ? (getWidth() - dividerSize) / 2 : 0;
+ top = isHorizontalSplit ? 0 : (getHeight() - dividerSize) / 2;
+ right = isHorizontalSplit ? left + dividerSize : getWidth();
+ bottom = isHorizontalSplit ? getHeight() : top + dividerSize;
+ mBackgroundRect.set(left, top, right, bottom);
+ }
}
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
return PointerIcon.getSystemIcon(getContext(),
- isLandscape() ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW);
+ mSplitLayout.isLeftRightSplit() ? TYPE_HORIZONTAL_DOUBLE_ARROW
+ : TYPE_VERTICAL_DOUBLE_ARROW);
}
@Override
@@ -295,8 +329,8 @@
// moving divider bar and calculating dragging velocity.
event.setLocation(event.getRawX(), event.getRawY());
final int action = event.getAction() & MotionEvent.ACTION_MASK;
- final boolean isLandscape = isLandscape();
- final int touchPos = (int) (isLandscape ? event.getX() : event.getY());
+ final boolean isLeftRightSplit = mSplitLayout.isLeftRightSplit();
+ final int touchPos = (int) (isLeftRightSplit ? event.getX() : event.getY());
switch (action) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker = VelocityTracker.obtain();
@@ -328,7 +362,7 @@
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
- final float velocity = isLandscape
+ final float velocity = isLeftRightSplit
? mVelocityTracker.getXVelocity()
: mVelocityTracker.getYVelocity();
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
@@ -410,6 +444,11 @@
.start();
}
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ canvas.drawRect(mBackgroundRect, mPaint);
+ }
+
@VisibleForTesting
void releaseHovering() {
mHandle.setHovering(false, true);
@@ -446,10 +485,6 @@
mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
}
- private boolean isLandscape() {
- return getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
- }
-
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 63cdb4f..b699533 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -79,7 +79,7 @@
* divide position changes.
*/
public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
-
+ private static final String TAG = "SplitLayout";
public static final int PARALLAX_NONE = 0;
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
@@ -121,12 +121,15 @@
private int mDividerPosition;
private boolean mInitialized = false;
private boolean mFreezeDividerWindow = false;
+ private boolean mIsLargeScreen = false;
private int mOrientation;
private int mRotation;
private int mDensity;
private int mUiMode;
private final boolean mDimNonImeSide;
+ private final boolean mAllowLeftRightSplitInPortrait;
+ private boolean mIsLeftRightSplit;
private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
@@ -138,6 +141,7 @@
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayController = displayController;
mDisplayImeController = displayImeController;
@@ -147,14 +151,17 @@
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ final Resources res = mContext.getResources();
+ mDimNonImeSide = res.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(res);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
+
updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
-
- mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
-
updateInvisibleRect();
}
@@ -284,17 +291,17 @@
* Returns the divider position as a fraction from 0 to 1.
*/
public float getDividerPositionAsFraction() {
- return Math.min(1f, Math.max(0f, isLandscape()
+ return Math.min(1f, Math.max(0f, mIsLeftRightSplit
? (float) ((mBounds1.right + mBounds2.left) / 2f) / mBounds2.right
: (float) ((mBounds1.bottom + mBounds2.top) / 2f) / mBounds2.bottom));
}
private void updateInvisibleRect() {
mInvisibleBounds.set(mRootBounds.left, mRootBounds.top,
- isLandscape() ? mRootBounds.right / 2 : mRootBounds.right,
- isLandscape() ? mRootBounds.bottom : mRootBounds.bottom / 2);
- mInvisibleBounds.offset(isLandscape() ? mRootBounds.right : 0,
- isLandscape() ? 0 : mRootBounds.bottom);
+ mIsLeftRightSplit ? mRootBounds.right / 2 : mRootBounds.right,
+ mIsLeftRightSplit ? mRootBounds.bottom : mRootBounds.bottom / 2);
+ mInvisibleBounds.offset(mIsLeftRightSplit ? mRootBounds.right : 0,
+ mIsLeftRightSplit ? 0 : mRootBounds.bottom);
}
/** Applies new configuration, returns {@code false} if there's no effect to the layout. */
@@ -309,6 +316,7 @@
final int orientation = configuration.orientation;
final int density = configuration.densityDpi;
final int uiMode = configuration.uiMode;
+ final boolean wasLeftRightSplit = mIsLeftRightSplit;
if (mOrientation == orientation
&& mRotation == rotation
@@ -326,9 +334,12 @@
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
+ mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ configuration);
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
updateInvisibleRect();
return true;
@@ -347,18 +358,27 @@
}
// We only need new bounds here, other configuration should be update later.
+ final boolean wasLeftRightSplit = SplitScreenUtils.isLeftRightSplit(
+ mAllowLeftRightSplitInPortrait, mIsLargeScreen,
+ mRootBounds.width() >= mRootBounds.height());
mTempRect.set(mRootBounds);
mRootBounds.set(tmpRect);
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
- initDividerPosition(mTempRect);
+ initDividerPosition(mTempRect, wasLeftRightSplit);
}
- private void initDividerPosition(Rect oldBounds) {
+ /**
+ * Updates the divider position to the position in the current orientation and bounds using the
+ * snap fraction calculated based on the previous orientation and bounds.
+ */
+ private void initDividerPosition(Rect oldBounds, boolean wasLeftRightSplit) {
final float snapRatio = (float) mDividerPosition
- / (float) (isLandscape(oldBounds) ? oldBounds.width() : oldBounds.height());
+ / (float) (wasLeftRightSplit ? oldBounds.width() : oldBounds.height());
// Estimate position by previous ratio.
final float length =
- (float) (isLandscape() ? mRootBounds.width() : mRootBounds.height());
+ (float) (mIsLeftRightSplit ? mRootBounds.width() : mRootBounds.height());
final int estimatePosition = (int) (length * snapRatio);
// Init divider position by estimated position using current bounds snap algorithm.
mDividerPosition = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
@@ -376,8 +396,7 @@
dividerBounds.set(mRootBounds);
bounds1.set(mRootBounds);
bounds2.set(mRootBounds);
- final boolean isLandscape = isLandscape(mRootBounds);
- if (isLandscape) {
+ if (mIsLeftRightSplit) {
position += mRootBounds.left;
dividerBounds.left = position - mDividerInsets;
dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
@@ -393,7 +412,7 @@
DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
if (setEffectBounds) {
- mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
+ mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit);
}
}
@@ -563,13 +582,12 @@
}
private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
- final boolean isLandscape = isLandscape(rootBounds);
final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
// landscape while they are letterboxed.
- if (!isLandscape) {
+ if (!mIsLeftRightSplit) {
final int largerInsets = Math.max(insets.top, insets.bottom);
insets.set(insets.left, largerInsets, insets.right, largerInsets);
}
@@ -579,9 +597,9 @@
rootBounds.width(),
rootBounds.height(),
mDividerSize,
- !isLandscape,
+ !mIsLeftRightSplit,
insets,
- isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
+ mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
/** Fling divider from current position to end or start position then exit */
@@ -643,13 +661,12 @@
/** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
- final boolean isLandscape = isLandscape();
final Rect insets = getDisplayStableInsets(mContext);
- insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
- isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
+ insets.set(mIsLeftRightSplit ? insets.left : 0, mIsLeftRightSplit ? 0 : insets.top,
+ mIsLeftRightSplit ? insets.right : 0, mIsLeftRightSplit ? 0 : insets.bottom);
final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
- isLandscape ? mBounds2.width() : mBounds2.height()).position;
+ mIsLeftRightSplit ? mBounds2.width() : mBounds2.height()).position;
final Rect distBounds1 = new Rect();
final Rect distBounds2 = new Rect();
final Rect distDividerBounds = new Rect();
@@ -740,15 +757,12 @@
.toRect();
}
- private static boolean isLandscape(Rect bounds) {
- return bounds.width() > bounds.height();
- }
-
/**
- * Return if this layout is landscape.
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
*/
- public boolean isLandscape() {
- return isLandscape(mRootBounds);
+ public boolean isLeftRightSplit() {
+ return mIsLeftRightSplit;
}
/** Apply recorded surface layout to the {@link SurfaceControl.Transaction}. */
@@ -850,9 +864,13 @@
/** Dumps the current split bounds recorded in this layout. */
public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + "bounds1=" + mBounds1.toShortString());
- pw.println(prefix + "dividerBounds=" + mDividerBounds.toShortString());
- pw.println(prefix + "bounds2=" + mBounds2.toShortString());
+ final String innerPrefix = prefix + "\t";
+ pw.println(prefix + TAG + ":");
+ pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
+ pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
+ pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
}
/** Handles layout change event. */
@@ -937,32 +955,32 @@
* Applies a parallax to the task to hint dismissing progress.
*
* @param position the split position to apply dismissing parallax effect
- * @param isLandscape indicates whether it's splitting horizontally or vertically
+ * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically
*/
- void applyDividerPosition(int position, boolean isLandscape) {
+ void applyDividerPosition(int position, boolean isLeftRightSplit) {
mDismissingSide = DOCKED_INVALID;
mParallaxOffset.set(0, 0);
mDismissingDimValue = 0;
int totalDismissingDistance = 0;
if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
- mDividerSnapAlgorithm.getFirstSplitTarget().position;
} else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
- mDismissingSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
- mDividerSnapAlgorithm.getDismissEndTarget().position;
}
- final boolean topLeftShrink = isLandscape
+ final boolean topLeftShrink = isLeftRightSplit
? position < mWinBounds1.right : position < mWinBounds1.bottom;
if (topLeftShrink) {
- mShrinkSide = isLandscape ? DOCKED_LEFT : DOCKED_TOP;
+ mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
mContentBounds.set(mWinBounds1);
mSurfaceBounds.set(mBounds1);
} else {
- mShrinkSide = isLandscape ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
mContentBounds.set(mWinBounds2);
mSurfaceBounds.set(mBounds2);
}
@@ -973,7 +991,7 @@
mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
if (mParallaxType == PARALLAX_DISMISSING) {
fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
} else {
mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
@@ -982,7 +1000,7 @@
}
if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- if (isLandscape) {
+ if (isLeftRightSplit) {
mParallaxOffset.x =
(mSurfaceBounds.width() - mContentBounds.width()) / 2;
} else {
@@ -1129,7 +1147,7 @@
// Calculate target bounds offset for IME
mLastYOffset = mYOffsetForIme;
final boolean needOffset = imeTargetPosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- && !isFloating && !isLandscape(mRootBounds) && mImeShown;
+ && !isFloating && !mIsLeftRightSplit && mImeShown;
mTargetYOffset = needOffset ? getTargetYOffset() : 0;
if (mTargetYOffset != mLastYOffset) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index d7ea1c0..0693543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common.split;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -25,9 +27,14 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
/** Helper utility class for split screen components to use. */
@@ -94,4 +101,38 @@
public static String splitFailureMessage(String caller, String reason) {
return "(" + caller + ") Splitscreen aborted: " + reason;
}
+
+ /**
+ * Returns whether left/right split is allowed in portrait.
+ */
+ public static boolean allowLeftRightSplitInPortrait(Resources res) {
+ return Flags.enableLeftRightSplitInPortrait() && res.getBoolean(
+ com.android.internal.R.bool.config_leftRightSplitInPortrait);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ Configuration config) {
+ // Compare the max bounds sizes as on near-square devices, the insets may result in a
+ // configuration in the other orientation
+ final boolean isLargeScreen = config.smallestScreenWidthDp >= 600;
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ final boolean isLandscape = maxBounds.width() >= maxBounds.height();
+ return isLeftRightSplit(allowLeftRightSplitInPortrait, isLargeScreen, isLandscape);
+ }
+
+ /**
+ * Returns whether left/right split is supported in the given configuration state. This method
+ * is useful for cases where we need to calculate this given last saved state.
+ */
+ public static boolean isLeftRightSplit(boolean allowLeftRightSplitInPortrait,
+ boolean isLargeScreen, boolean isLandscape) {
+ if (allowLeftRightSplitInPortrait && isLargeScreen) {
+ return !isLandscape;
+ } else {
+ return isLandscape;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0bf8ec3..fdfb6f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -94,6 +94,7 @@
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
+ // Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
/**
@@ -362,7 +363,7 @@
*/
private boolean isReadyToHandleDrag() {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
- if (mDisplayDropTargets.valueAt(i).mHasDrawn) {
+ if (mDisplayDropTargets.valueAt(i).hasDrawn) {
return true;
}
}
@@ -398,8 +399,13 @@
* Dumps information about this controller.
*/
public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(prefix + " listeners=" + mListeners.size());
+ pw.println(innerPrefix + "listeners=" + mListeners.size());
+ pw.println(innerPrefix + "Per display:");
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ mDisplayDropTargets.valueAt(i).dump(pw, innerPrefix);
+ }
}
/**
@@ -440,7 +446,7 @@
final FrameLayout rootView;
final DragLayout dragLayout;
// Tracks whether the window has fully drawn since it was last made visible
- boolean mHasDrawn;
+ boolean hasDrawn;
boolean isHandlingDrag;
// A count of the number of active drags in progress to ensure that we only hide the window
@@ -464,17 +470,29 @@
rootView.setVisibility(visibility);
if (visibility == View.VISIBLE) {
rootView.requestApplyInsets();
- if (!mHasDrawn && rootView.getViewRootImpl() != null) {
+ if (!hasDrawn && rootView.getViewRootImpl() != null) {
rootView.getViewRootImpl().registerRtFrameCallback(this);
}
} else {
- mHasDrawn = false;
+ hasDrawn = false;
}
}
@Override
public void onFrameDraw(long frame) {
- mHasDrawn = true;
+ hasDrawn = true;
+ }
+
+ /**
+ * Dumps information about this display's shell drop target.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(innerPrefix + "displayId=" + displayId);
+ pw.println(innerPrefix + "hasDrawn=" + hasDrawn);
+ pw.println(innerPrefix + "isHandlingDrag=" + isHandlingDrag);
+ pw.println(innerPrefix + "activeDragCount=" + activeDragCount);
+ dragLayout.dump(pw, innerPrefix);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index e70768b..162ce19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -138,7 +138,7 @@
final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
final Rect fullscreenDrawRegion = new Rect(displayRegion);
final Rect fullscreenHitRegion = new Rect(displayRegion);
- final boolean inLandscape = mSession.displayLayout.isLandscape();
+ final boolean isLeftRightSplit = mSplitScreen != null && mSplitScreen.isLeftRightSplit();
final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
final float dividerWidth = mContext.getResources().getDimensionPixelSize(
R.dimen.split_divider_bar_width);
@@ -155,7 +155,7 @@
topOrLeftBounds.intersect(displayRegion);
bottomOrRightBounds.intersect(displayRegion);
- if (inLandscape) {
+ if (isLeftRightSplit) {
final Rect leftHitRegion = new Rect();
final Rect rightHitRegion = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 205a455..445ba89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -47,14 +48,18 @@
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import java.io.PrintWriter;
import java.util.ArrayList;
/**
@@ -74,6 +79,11 @@
private final StatusBarManager mStatusBarManager;
private final Configuration mLastConfiguration = new Configuration();
+ // Whether this device supports left/right split in portrait
+ private final boolean mAllowLeftRightSplitInPortrait;
+ // Whether the device is currently in left/right split mode
+ private boolean mIsLeftRightSplit;
+
private DragAndDropPolicy.Target mCurrentTarget = null;
private DropZoneView mDropZoneView1;
private DropZoneView mDropZoneView2;
@@ -106,17 +116,18 @@
setLayoutDirection(LAYOUT_DIRECTION_LTR);
mDropZoneView1 = new DropZoneView(context);
mDropZoneView2 = new DropZoneView(context);
- addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
- addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT));
+ addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
- int orientation = getResources().getConfiguration().orientation;
- setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
- ? LinearLayout.HORIZONTAL
- : LinearLayout.VERTICAL);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ // We don't use the configuration orientation here to determine landscape because
+ // near-square devices may report the same orietation with insets taken into account
+ mAllowLeftRightSplitInPortrait = SplitScreenUtils.allowLeftRightSplitInPortrait(
+ context.getResources());
+ mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ getResources().getConfiguration());
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
@Override
@@ -124,11 +135,12 @@
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
- final int orientation = getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ boolean isLeftRightSplit = mSplitScreenController != null
+ && mSplitScreenController.isLeftRightSplit();
+ if (isLeftRightSplit) {
mDropZoneView1.setBottomInset(mInsets.bottom);
mDropZoneView2.setBottomInset(mInsets.bottom);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setBottomInset(0);
mDropZoneView2.setBottomInset(mInsets.bottom);
}
@@ -136,14 +148,12 @@
}
public void onConfigChanged(Configuration newConfig) {
- if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
- && getOrientation() != HORIZONTAL) {
- setOrientation(LinearLayout.HORIZONTAL);
- updateContainerMargins(newConfig.orientation);
- } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
- && getOrientation() != VERTICAL) {
- setOrientation(LinearLayout.VERTICAL);
- updateContainerMargins(newConfig.orientation);
+ boolean isLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
+ newConfig);
+ if (isLeftRightSplit != mIsLeftRightSplit) {
+ mIsLeftRightSplit = isLeftRightSplit;
+ setOrientation(mIsLeftRightSplit ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+ updateContainerMargins(mIsLeftRightSplit);
}
final int diff = newConfig.diff(mLastConfiguration);
@@ -162,14 +172,14 @@
mDropZoneView2.setContainerMargin(0, 0, 0, 0);
}
- private void updateContainerMargins(int orientation) {
+ private void updateContainerMargins(boolean isLeftRightSplit) {
final float halfMargin = mDisplayMargin / 2f;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ if (isLeftRightSplit) {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin);
mDropZoneView2.setContainerMargin(
halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
- } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ } else {
mDropZoneView1.setContainerMargin(
mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin);
mDropZoneView2.setContainerMargin(
@@ -257,23 +267,21 @@
* @param bounds2 bounds to apply to the second dropzone view, null if split in half.
*/
private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
- final int orientation = getResources().getConfiguration().orientation;
- final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
final int halfDivider = mDividerSize / 2;
final LinearLayout.LayoutParams dropZoneView1 =
(LayoutParams) mDropZoneView1.getLayoutParams();
final LinearLayout.LayoutParams dropZoneView2 =
(LayoutParams) mDropZoneView2.getLayoutParams();
- if (isPortrait) {
- dropZoneView1.width = MATCH_PARENT;
- dropZoneView2.width = MATCH_PARENT;
- dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
- dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
- } else {
+ if (mIsLeftRightSplit) {
dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
dropZoneView1.height = MATCH_PARENT;
dropZoneView2.height = MATCH_PARENT;
+ } else {
+ dropZoneView1.width = MATCH_PARENT;
+ dropZoneView2.width = MATCH_PARENT;
+ dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+ dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
}
dropZoneView1.weight = bounds1 != null ? 0 : 1;
dropZoneView2.weight = bounds2 != null ? 0 : 1;
@@ -371,7 +379,7 @@
// Reset the state if we previously force-ignore the bottom margin
mDropZoneView1.setForceIgnoreBottomMargin(false);
mDropZoneView2.setForceIgnoreBottomMargin(false);
- updateContainerMargins(getResources().getConfiguration().orientation);
+ updateContainerMargins(mIsLeftRightSplit);
mCurrentTarget = null;
}
@@ -481,4 +489,19 @@
final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
}
+
+ /**
+ * Dumps information about this drag layout.
+ */
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + "DragLayout:");
+ pw.println(innerPrefix + "mIsLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
+ pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+ pw.println(innerPrefix + "mDisplayMargin=" + mDisplayMargin);
+ pw.println(innerPrefix + "mDividerSize=" + mDividerSize);
+ pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
+ pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
+ pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 478b6a9..353d702 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -18,31 +18,17 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
-import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.Intent.EXTRA_USER;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
import com.android.wm.shell.common.DisplayLayout;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b4067d0..fdd3044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -123,7 +123,7 @@
private static final int EXTRA_CONTENT_OVERLAY_FADE_OUT_DELAY_MS =
SystemProperties.getInt(
- "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 0);
+ "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 450);
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 63afd3e..79c2076 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -590,7 +590,7 @@
cancel("transit_sleep");
return;
}
- if (mKeyguardLocked) {
+ if (mKeyguardLocked || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"[%d] RecentsController.merge: keyguard is locked", mInstanceId);
// We will not accept new changes if we are swiping over the keyguard.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 664d4491..37b24e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -366,6 +366,14 @@
return mStageCoordinator.getStageOfTask(taskId);
}
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ public boolean isLeftRightSplit() {
+ return mStageCoordinator.isLeftRightSplit();
+ }
+
/** Check split is foreground and task is under split or not by taskId. */
public boolean isTaskInSplitScreenForeground(int taskId) {
return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 7a4834c..36e0eb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1301,7 +1301,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1659,7 +1659,7 @@
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
@@ -1749,10 +1749,10 @@
}
if (stage == STAGE_TYPE_MAIN) {
mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
} else {
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
if (present) {
updateRecentTasksSplitPair();
@@ -2113,7 +2113,7 @@
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
}
}
@@ -2205,8 +2205,12 @@
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
- private boolean isLandscape() {
- return mSplitLayout.isLandscape();
+ /**
+ * @return {@code true} if we should create a left-right split, {@code false} if we should
+ * create a top-bottom split.
+ */
+ boolean isLeftRightSplit() {
+ return mSplitLayout.isLeftRightSplit();
}
/**
@@ -3177,6 +3181,7 @@
pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
pw.println(innerPrefix + "isSplitActive=" + isSplitActive());
pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible());
+ pw.println(innerPrefix + "isLeftRightSplit=" + mSplitLayout.isLeftRightSplit());
pw.println(innerPrefix + "MainStage");
pw.println(childPrefix + "stagePosition=" + splitPositionToString(getMainStagePosition()));
pw.println(childPrefix + "isActive=" + mMainStage.isActive());
@@ -3188,10 +3193,7 @@
mSideStage.dump(pw, childPrefix);
pw.println(innerPrefix + "SideStageListener");
mSideStageListener.dump(pw, childPrefix);
- if (mMainStage.isActive()) {
- pw.println(innerPrefix + "SplitLayout");
- mSplitLayout.dump(pw, childPrefix);
- }
+ mSplitLayout.dump(pw, childPrefix);
if (!mPausingTasks.isEmpty()) {
pw.println(childPrefix + "mPausingTasks=" + mPausingTasks);
}
@@ -3243,7 +3245,7 @@
mLogger.logExit(exitReason,
SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
/**
@@ -3256,7 +3258,7 @@
toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
!toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
!toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
+ mSplitLayout.isLeftRightSplit());
}
class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 366f7b1..4abaf5b 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -52,7 +52,7 @@
}
java_defaults {
- name: "WMShellFlickerTestsDefaultWithoutTemplate",
+ name: "WMShellFlickerTestsDefault",
platform_apis: true,
certificate: "platform",
optimize: {
@@ -75,16 +75,9 @@
],
data: [
":FlickerTestApp",
- "trace_config/*",
],
}
-java_defaults {
- name: "WMShellFlickerTestsDefault",
- defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
- test_config_template: "AndroidTestTemplate.xml",
-}
-
java_library {
name: "WMShellFlickerTestsBase",
defaults: ["WMShellFlickerTestsDefault"],
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
deleted file mode 100644
index b00d88e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests {MODULE}">
- <option name="test-tag" value="FlickerTests"/>
- <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
- <option name="isolated-storage" value="false"/>
-
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <!-- keeps the screen on during tests -->
- <option name="screen-always-on" value="on"/>
- <!-- prevents the phone from restarting -->
- <option name="force-skip-system-props" value="true"/>
- <!-- set WM tracing verbose level to all -->
- <option name="run-command" value="cmd window tracing level all"/>
- <!-- set WM tracing to frame (avoid incomplete states) -->
- <option name="run-command" value="cmd window tracing frame"/>
- <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests -->
- <option name="run-command" value="pm disable com.google.android.internal.betterbug"/>
- <!-- ensure lock screen mode is swipe -->
- <option name="run-command" value="locksettings set-disabled false"/>
- <!-- restart launcher to activate TAPL -->
- <option name="run-command"
- value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher"/>
- <!-- Increase trace size: 20mb for WM and 80mb for SF -->
- <option name="run-command" value="cmd window tracing size 20480"/>
- <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/>
- <!-- b/307664397 - Ensure camera has the correct permissions and doesn't show a dialog -->
- <option name="run-command"
- value="pm grant com.google.android.GoogleCamera android.permission.ACCESS_FINE_LOCATION"/>
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="test-user-token" value="%TEST_USER%"/>
- <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/>
- <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/>
- <option name="run-command" value="settings put system show_touches 1"/>
- <option name="run-command" value="settings put system pointer_location 1"/>
- <option name="teardown-command"
- value="settings delete secure show_ime_with_hard_keyboard"/>
- <option name="teardown-command" value="settings delete system show_touches"/>
- <option name="teardown-command" value="settings delete system pointer_location"/>
- <option name="teardown-command"
- value="cmd overlay enable com.android.internal.systemui.navbar.gestural"/>
- </target_preparer>
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true"/>
- <option name="test-file-name" value="{MODULE}.apk"/>
- <option name="test-file-name" value="FlickerTestApp.apk"/>
- </target_preparer>
- <!-- Enable mocking GPS location by the test app -->
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command"
- value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
- <option name="teardown-command"
- value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
- </target_preparer>
-
- <!-- Needed for pushing the trace config file -->
- <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="push-file"
- key="trace_config.textproto"
- value="/data/misc/perfetto-traces/trace_config.textproto"
- />
- <!--Install the content provider automatically when we push some file in sdcard folder.-->
- <!--Needed to avoid the installation during the test suite.-->
- <option name="push-file" key="trace_config.textproto" value="/sdcard/sample.textproto"/>
- </target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="{PACKAGE}"/>
- <option name="shell-timeout" value="6600s"/>
- <option name="test-timeout" value="6000s"/>
- <option name="hidden-api-checks" value="false"/>
- <option name="device-listeners" value="android.device.collectors.PerfettoListener"/>
- <!-- PerfettoListener related arguments -->
- <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>
- <option name="instrumentation-arg"
- key="perfetto_config_file"
- value="trace_config.textproto"
- />
- <option name="instrumentation-arg" key="per_run" value="true"/>
- </test>
- <!-- Needed for pulling the collected trace config on to the host -->
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="pull-pattern-keys" value="perfetto_file_path"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.bubbles/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.pip/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.splitscreen/files"/>
- <option name="directory-keys"
- value="/data/user/0/com.android.wm.shell.flicker.service/files"/>
- <option name="collect-on-run-ended-only" value="true"/>
- <option name="clean-up" value="true"/>
- </metrics_collector>
-</configuration>
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index bae701f..e151ab2 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -36,6 +36,8 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker",
instrumentation_target_package: "com.android.wm.shell.flicker",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [":WMShellFlickerTestsAppCompat-src"],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
index c4e9a84..f0b4f1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -29,6 +29,8 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.bubbles",
instrumentation_target_package: "com.android.wm.shell.flicker.bubbles",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*.kt"],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index b9b56c2..e61f762 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -62,11 +62,13 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsPip1-src",
":WMShellFlickerTestsPipCommon-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
@@ -75,11 +77,13 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsPip2-src",
":WMShellFlickerTestsPipCommon-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
@@ -88,6 +92,7 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsPip3-src",
":WMShellFlickerTestsPipCommon-src",
@@ -98,6 +103,7 @@
":WMShellFlickerTestsPipApps-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
@@ -106,19 +112,22 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.pip.apps",
instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsPipApps-src",
":WMShellFlickerTestsPipCommon-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
name: "WMShellFlickerTestsPipAppsCSuite",
- defaults: ["WMShellFlickerTestsDefaultWithoutTemplate"],
+ defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["AndroidManifest.xml"],
package_name: "com.android.wm.shell.flicker.pip.apps",
instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsPipApps-src",
":WMShellFlickerTestsPipCommon-src",
@@ -128,6 +137,7 @@
"device-tests",
"csuite",
],
+ data: ["trace_config/*"],
}
csuite_test {
diff --git a/libs/WindowManager/Shell/tests/flicker/service/Android.bp b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
index 9b8cd94..4f1a68a 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/service/Android.bp
@@ -52,8 +52,10 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.service",
instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: ["src/**/*.kt"],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
@@ -62,6 +64,8 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.service",
instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [":WMShellFlickerServicePlatinumTests-src"],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 80ab24d..824e454 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -64,4 +66,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index cc982d1..c52ada3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -75,4 +77,8 @@
secondaryApp.exit(wmHelper)
sendNotificationApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index fa12bb8..8134fdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -82,4 +84,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index 2592fd4..3417744 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -28,6 +29,7 @@
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -70,4 +72,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index 983653b..f1a011c 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -70,4 +72,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 068171d..c9b1c91 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -20,6 +20,7 @@
import android.graphics.Point
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.helpers.WindowUtils
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,6 +30,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -149,4 +151,8 @@
val LARGE_SCREEN_DP_THRESHOLD = 600
return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 64b75c5..72f2db3 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -67,4 +69,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index 1795010..511de4f 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -66,4 +68,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 7065846..558d2bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -68,4 +70,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 251cb50..ecd68295 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -69,4 +71,8 @@
thirdApp.exit(wmHelper)
fourthApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index a9933bbe..f50d5c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.AndroidLoggerSetupRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,6 +28,7 @@
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -66,4 +68,8 @@
primaryApp.exit(wmHelper)
secondaryApp.exit(wmHelper)
}
+
+ companion object {
+ @ClassRule @JvmField val setupLoggerRule = AndroidLoggerSetupRule()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
index 4629c53..f813b0d 100644
--- a/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/Android.bp
@@ -54,11 +54,13 @@
manifest: "AndroidManifest.xml",
package_name: "com.android.wm.shell.flicker.splitscreen",
instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+ test_config_template: "AndroidTestTemplate.xml",
srcs: [
":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerTestsSplitScreenGroup1-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
android_test {
@@ -74,4 +76,5 @@
":WMShellFlickerTestsSplitScreenGroup1-src",
],
static_libs: ["WMShellFlickerTestsBase"],
+ data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto b/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
deleted file mode 100644
index 406ada9..0000000
--- a/libs/WindowManager/Shell/tests/flicker/trace_config/trace_config.textproto
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (C) 2023 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# proto-message: TraceConfig
-
-# Enable periodic flushing of the trace buffer into the output file.
-write_into_file: true
-
-# Writes the userspace buffer into the file every 1s.
-file_write_period_ms: 2500
-
-# See b/126487238 - we need to guarantee ordering of events.
-flush_period_ms: 30000
-
-# The trace buffers needs to be big enough to hold |file_write_period_ms| of
-# trace data. The trace buffer sizing depends on the number of trace categories
-# enabled and the device activity.
-
-# RSS events
-buffers: {
- size_kb: 63488
- fill_policy: RING_BUFFER
-}
-
-data_sources {
- config {
- name: "linux.process_stats"
- target_buffer: 0
- # polled per-process memory counters and process/thread names.
- # If you don't want the polled counters, remove the "process_stats_config"
- # section, but keep the data source itself as it still provides on-demand
- # thread/process naming for ftrace data below.
- process_stats_config {
- scan_all_processes_on_start: true
- }
- }
-}
-
-data_sources: {
- config {
- name: "linux.ftrace"
- ftrace_config {
- ftrace_events: "ftrace/print"
- ftrace_events: "task/task_newtask"
- ftrace_events: "task/task_rename"
- atrace_categories: "ss"
- atrace_categories: "wm"
- atrace_categories: "am"
- atrace_categories: "aidl"
- atrace_categories: "input"
- atrace_categories: "binder_driver"
- atrace_categories: "sched_process_exit"
- atrace_apps: "com.android.server.wm.flicker.testapp"
- atrace_apps: "com.android.systemui"
- atrace_apps: "com.android.wm.shell.flicker"
- atrace_apps: "com.android.wm.shell.flicker.other"
- atrace_apps: "com.android.wm.shell.flicker.bubbles"
- atrace_apps: "com.android.wm.shell.flicker.pip"
- atrace_apps: "com.android.wm.shell.flicker.splitscreen"
- atrace_apps: "com.google.android.apps.nexuslauncher"
- }
- }
-}
-
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 527dc01..1b347e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -204,6 +204,7 @@
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -219,6 +220,7 @@
@Test
public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
@@ -239,6 +241,7 @@
@Test
public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
mPortraitDisplayLayout, mActivityClipData);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
new file mode 100644
index 0000000..30847d3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenUtilsTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.split.SplitScreenUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/** Tests for {@link com.android.wm.shell.common.split.SplitScreenUtils} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitScreenUtilsTests extends ShellTestCase {
+
+ @Test
+ public void testIsLeftRightSplit() {
+ Configuration portraitTablet = new Configuration();
+ portraitTablet.smallestScreenWidthDp = 720;
+ portraitTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapeTablet = new Configuration();
+ landscapeTablet.smallestScreenWidthDp = 720;
+ landscapeTablet.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+ Configuration portraitPhone = new Configuration();
+ portraitPhone.smallestScreenWidthDp = 420;
+ portraitPhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 500, 1000));
+ Configuration landscapePhone = new Configuration();
+ landscapePhone.smallestScreenWidthDp = 420;
+ landscapePhone.windowConfiguration.setMaxBounds(new Rect(0, 0, 1000, 500));
+
+ // Allow L/R split in portrait = false
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(false /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+
+ // Allow L/R split in portrait = true, only affects large screens
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapeTablet));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ landscapePhone));
+ assertTrue(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitTablet));
+ assertFalse(SplitScreenUtils.isLeftRightSplit(true /* allowLeftRightSplitInPortrait */,
+ portraitPhone));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index fff65f3..d819261 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -140,7 +140,7 @@
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
- when(mSplitLayout.isLandscape()).thenReturn(false);
+ when(mSplitLayout.isLeftRightSplit()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 12cb69d..d747489 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -105,7 +105,7 @@
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkAutoCanvasRestore saver(profileCanvas, true);
- profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ profileCanvas->concat(preTransform);
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index ef218fd..c80891c 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -210,6 +210,18 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+
+ <activity android:name=".UnarchiveErrorActivity"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
+ android:excludeFromRecents="true"
+ android:noHistory="true"
+ android:exported="true">
+ <intent-filter android:priority="1">
+ <action android:name="com.android.intent.action.UNARCHIVE_ERROR_DIALOG" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 0a2e880..e5036b0 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -267,4 +267,86 @@
<!-- The action to restore (i.e. re-install, re-download) an app after parts of the app have been previously moved
into the cloud for temporary storage. [CHAR LIMIT=15] -->
<string name="restore">Restore</string>
+
+ <!-- Dialog title shown when the user is trying to restore an app but the associated download
+ cannot happen immediately because the device is offline (has no internet connection.
+ [CHAR LIMIT=50] -->
+ <string name="unarchive_error_offline_title">You\'re offline</string>
+
+ <!-- Dialog body test shown when the user is trying to restore an app but the associated download
+ cannot happen immediately because the device is offline (has no internet connection.
+ [CHAR LIMIT=none] -->
+ <string name="unarchive_error_offline_body">
+ This app will automatically restore when you\'re connected to the internet
+ </string>
+
+ <!-- Dialog title shown when the user is trying to restore an app but a generic error happened.
+ [CHAR LIMIT=50] -->
+ <string name="unarchive_error_generic_title">Something went wrong</string>
+
+ <!-- Dialog body shown when the user is trying to restore an app but a generic error happened.
+ [CHAR LIMIT=none] -->
+ <string name="unarchive_error_generic_body">
+ There was a problem trying to restore this app
+ </string>
+
+ <!-- Dialog title shown when the user is trying to restore an app but the device is out of
+ storage. [CHAR LIMIT=50] -->
+ <string name="unarchive_error_storage_title">Not enough storage</string>
+
+ <!-- Dialog body shown when the user is trying to restore an app but the device is out of
+ storage. [CHAR LIMIT=none] -->
+ <string name="unarchive_error_storage_body">
+ To restore this app, you can free up space on this device. Storage
+ required: <xliff:g id="bytes" example="100 MB">%1$s</xliff:g>
+ </string>
+
+ <!-- Dialog title shown when the user is trying to restore an app but the installer needs to
+ perform an additional action. [CHAR LIMIT=50] -->
+ <string name="unarchive_action_required_title">Action required</string>
+
+ <!-- Dialog body shown when the user is trying to restore an app but the installer needs to
+ perform an additional action. [CHAR LIMIT=none] -->
+ <string name="unarchive_action_required_body">
+ Follow the next steps to restore this app
+ </string>
+ <!-- Dialog title shown when the user is trying to restore an app but the installer responsible
+ for the action is in a disabled state. [CHAR LIMIT=50] -->
+ <string name="unarchive_error_installer_disabled_title">
+ <xliff:g id="installername" example="App Store">%1$s</xliff:g> is disabled
+ </string>
+
+ <!-- Dialog body shown when the user is trying to restore an app but the installer responsible
+ for the action is in a disabled state. [CHAR LIMIT=none] -->
+ <string name="unarchive_error_installer_disabled_body">
+ To restore this app, enable the
+ <xliff:g id="installername" example="App Store">%1$s</xliff:g> in Settings
+ </string>
+
+ <!-- Dialog title shown when the user is trying to restore an app but the installer responsible
+ for the action is uninstalled. [CHAR LIMIT=50] -->
+ <string name="unarchive_error_installer_uninstalled_title">
+ <xliff:g id="installername" example="App Store">%1$s</xliff:g> is uninstalled
+ </string>
+
+ <!-- Dialog body shown when the user is trying to restore an app but the installer responsible
+ for the action is uninstalled. [CHAR LIMIT=none] -->
+ <string name="unarchive_error_installer_uninstalled_body">
+ To restore this app, you\'ll need to install
+ <xliff:g id="installername" example="App Store">%1$s</xliff:g>
+ </string>
+
+ <!-- Dialog action to continue with the next action. [CHAR LIMIT=30] -->
+ <string name="unarchive_action_required_continue">Continue</string>
+
+ <!-- Dialog action to clear device storage, as in deleting some apps or photos to free up bytes
+ [CHAR LIMIT=30] -->
+ <string name="unarchive_clear_storage_button">Clear storage</string>
+
+ <!-- Dialog action to open the Settings app. [CHAR LIMIT=30] -->
+ <string name="unarchive_settings_button">Settings</string>
+
+ <!-- Dialog action to close a dialog. [CHAR LIMIT=30] -->
+ <string name="close">Close</string>
+
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index daedb1a..8d8254a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -298,7 +298,14 @@
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
- session.commit(pendingIntent.getIntentSender());
+ try {
+ session.commit(pendingIntent.getIntentSender());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Cannot install package: ", e);
+ launchFailure(PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ return;
+ }
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorActivity.java
new file mode 100644
index 0000000..78d2f88
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.packageinstaller;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+public class UnarchiveErrorActivity extends Activity {
+
+ static final String EXTRA_REQUIRED_BYTES =
+ "com.android.content.pm.extra.UNARCHIVE_EXTRA_REQUIRED_BYTES";
+ static final String EXTRA_INSTALLER_PACKAGE_NAME =
+ "com.android.content.pm.extra.UNARCHIVE_INSTALLER_PACKAGE_NAME";
+ static final String EXTRA_INSTALLER_TITLE =
+ "com.android.content.pm.extra.UNARCHIVE_INSTALLER_TITLE";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(null);
+
+ Bundle extras = getIntent().getExtras();
+ int unarchivalStatus = extras.getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS);
+ long requiredBytes = extras.getLong(EXTRA_REQUIRED_BYTES);
+ PendingIntent intent = extras.getParcelable(Intent.EXTRA_INTENT, PendingIntent.class);
+ String installerPackageName = extras.getString(EXTRA_INSTALLER_PACKAGE_NAME);
+ // We cannot derive this from the package name because the installer might not be installed
+ // anymore.
+ String installerAppTitle = extras.getString(EXTRA_INSTALLER_TITLE);
+
+ if (unarchivalStatus == PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+ Objects.requireNonNull(intent);
+ }
+
+ FragmentTransaction ft = getFragmentManager().beginTransaction();
+ Fragment prev = getFragmentManager().findFragmentByTag("dialog");
+ if (prev != null) {
+ ft.remove(prev);
+ }
+
+ Bundle args = new Bundle();
+ args.putInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS, unarchivalStatus);
+ args.putLong(EXTRA_REQUIRED_BYTES, requiredBytes);
+ if (intent != null) {
+ args.putParcelable(Intent.EXTRA_INTENT, intent);
+ }
+ if (installerPackageName != null) {
+ args.putString(EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
+ }
+ if (installerAppTitle != null) {
+ args.putString(EXTRA_INSTALLER_TITLE, installerAppTitle);
+ }
+
+ DialogFragment fragment = new UnarchiveErrorFragment();
+ fragment.setArguments(args);
+ fragment.show(ft, "dialog");
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
new file mode 100644
index 0000000..d33433f
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.packageinstaller;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.PendingIntent;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.format.Formatter;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+public class UnarchiveErrorFragment extends DialogFragment implements
+ DialogInterface.OnClickListener {
+
+ private static final String TAG = "UnarchiveErrorFragment";
+
+ private int mStatus;
+
+ @Nullable
+ private PendingIntent mExtraIntent;
+
+ @Nullable
+ private String mInstallerPackageName;
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ mStatus = args.getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS, -1);
+ mExtraIntent = args.getParcelable(Intent.EXTRA_INTENT, PendingIntent.class);
+ long requiredBytes = args.getLong(UnarchiveErrorActivity.EXTRA_REQUIRED_BYTES);
+ mInstallerPackageName = args.getString(
+ UnarchiveErrorActivity.EXTRA_INSTALLER_PACKAGE_NAME);
+ String installerAppTitle = args.getString(UnarchiveErrorActivity.EXTRA_INSTALLER_TITLE);
+
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
+
+ dialogBuilder.setTitle(getDialogTitle(mStatus, installerAppTitle));
+ dialogBuilder.setMessage(getBodyText(mStatus, installerAppTitle, requiredBytes));
+
+ addButtons(dialogBuilder, mStatus);
+
+ return dialogBuilder.create();
+ }
+
+ private void addButtons(AlertDialog.Builder dialogBuilder, int status) {
+ switch (status) {
+ case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
+ dialogBuilder.setPositiveButton(R.string.unarchive_action_required_continue, this);
+ dialogBuilder.setNegativeButton(R.string.close, this);
+ break;
+ case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
+ dialogBuilder.setPositiveButton(R.string.unarchive_clear_storage_button, this);
+ dialogBuilder.setNegativeButton(R.string.close, this);
+ break;
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
+ dialogBuilder.setPositiveButton(R.string.external_sources_settings, this);
+ dialogBuilder.setNegativeButton(R.string.close, this);
+ break;
+ case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
+ case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
+ dialogBuilder.setPositiveButton(android.R.string.ok, this);
+ break;
+ default:
+ // This should never happen through normal API usage.
+ throw new IllegalArgumentException("Invalid unarchive status " + status);
+ }
+ }
+
+ private String getBodyText(int status, String installerAppTitle, long requiredBytes) {
+ switch (status) {
+ case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
+ return getString(R.string.unarchive_action_required_body);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
+ return String.format(getString(R.string.unarchive_error_storage_body),
+ Formatter.formatShortFileSize(getActivity(), requiredBytes));
+ case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
+ return getString(R.string.unarchive_error_offline_body);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
+ return String.format(getString(R.string.unarchive_error_installer_disabled_body),
+ installerAppTitle);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
+ return String.format(
+ getString(R.string.unarchive_error_installer_uninstalled_body),
+ installerAppTitle);
+ case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
+ return getString(R.string.unarchive_error_generic_body);
+ default:
+ // This should never happen through normal API usage.
+ throw new IllegalArgumentException("Invalid unarchive status " + status);
+ }
+ }
+
+ private String getDialogTitle(int status, String installerAppTitle) {
+ switch (status) {
+ case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
+ return getString(R.string.unarchive_action_required_title);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
+ return getString(R.string.unarchive_error_storage_title);
+ case PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY:
+ return getString(R.string.unarchive_error_offline_title);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
+ return String.format(getString(R.string.unarchive_error_installer_disabled_title),
+ installerAppTitle);
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED:
+ return String.format(
+ getString(R.string.unarchive_error_installer_uninstalled_title),
+ installerAppTitle);
+ case PackageInstaller.UNARCHIVAL_GENERIC_ERROR:
+ return getString(R.string.unarchive_error_generic_title);
+ default:
+ // This should never happen through normal API usage.
+ throw new IllegalArgumentException("Invalid unarchive status " + status);
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which != Dialog.BUTTON_POSITIVE) {
+ return;
+ }
+
+ try {
+ onClickInternal();
+ } catch (IntentSender.SendIntentException e) {
+ Log.e(TAG, "Failed to start intent after onClick.", e);
+ }
+ }
+
+ private void onClickInternal() throws IntentSender.SendIntentException {
+ Activity activity = getActivity();
+ if (activity == null) {
+ // This probably shouldn't happen in practice.
+ Log.i(TAG, "Lost reference to activity, cannot act onClick.");
+ return;
+ }
+
+ switch (mStatus) {
+ case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
+ activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
+ null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+ break;
+ case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
+ if (mExtraIntent != null) {
+ activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
+ null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+ } else {
+ Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
+ startActivity(intent);
+ }
+ break;
+ case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", mInstallerPackageName, null);
+ intent.setData(uri);
+ startActivity(intent);
+ break;
+ default:
+ // Do nothing. The rest of the dialogs are purely informational.
+ }
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ super.onDismiss(dialog);
+ if (isAdded()) {
+ getActivity().finish();
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 8386bc1..f216abb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -17,24 +17,32 @@
package com.android.settingslib.spa.gallery.card
import android.os.Bundle
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Error
import androidx.compose.material.icons.outlined.PowerOff
import androidx.compose.material.icons.outlined.Shield
import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.widget.card.CardButton
import com.android.settingslib.spa.widget.card.CardModel
import com.android.settingslib.spa.widget.card.SettingsCard
import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
+import com.android.settingslib.spa.widget.card.SettingsCardContent
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -50,6 +58,7 @@
SettingsCardWithIcon()
SettingsCardWithoutIcon()
SampleSettingsCollapsibleCard()
+ SampleSettingsCardContent()
}
}
@@ -108,6 +117,32 @@
)
}
+ @Composable
+ fun SampleSettingsCardContent() {
+ SettingsCard {
+ SettingsCardContent {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .clickable { }
+ .padding(SettingsDimension.dialogItemPadding),
+ ) {
+ Text(text = "Abc")
+ }
+ }
+ SettingsCardContent {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .clickable { }
+ .padding(SettingsDimension.dialogItemPadding),
+ ) {
+ Text(text = "123")
+ }
+ }
+ }
+ }
+
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = createSettingsPage())
.setUiLayoutFn {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 8c862d4..f7c5414 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -20,6 +20,8 @@
import androidx.compose.ui.unit.dp
object SettingsShape {
+ val CornerExtraSmall = RoundedCornerShape(4.dp)
+
val CornerMedium = RoundedCornerShape(12.dp)
val CornerExtraLarge = RoundedCornerShape(28.dp)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 10e2686..4379278 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -36,10 +36,13 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.debug.UiModePreviews
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarge
+import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -49,7 +52,7 @@
Card(
shape = CornerExtraLarge,
colors = CardDefaults.cardColors(
- containerColor = SettingsTheme.colorScheme.surface,
+ containerColor = Color.Transparent,
),
modifier = Modifier
.fillMaxWidth()
@@ -62,6 +65,20 @@
}
@Composable
+fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+ Card(
+ shape = CornerExtraSmall,
+ colors = CardDefaults.cardColors(
+ containerColor = SettingsTheme.colorScheme.surface,
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 1.dp),
+ content = content,
+ )
+}
+
+@Composable
fun SettingsCard(model: CardModel) {
SettingsCard {
SettingsCardImpl(model)
@@ -70,14 +87,16 @@
@Composable
internal fun SettingsCardImpl(model: CardModel) {
- Column(
- modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
- verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
- ) {
- CardIcon(model.imageVector)
- SettingsTitle(model.title)
- SettingsBody(model.text)
- Buttons(model.buttons)
+ SettingsCardContent {
+ Column(
+ modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+ verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
+ ) {
+ CardIcon(model.imageVector)
+ SettingsTitle(model.title)
+ SettingsBody(model.text)
+ Buttons(model.buttons)
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 7d10645..bf192a1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -30,7 +30,6 @@
import androidx.compose.material.icons.outlined.Error
import androidx.compose.material.icons.outlined.PowerOff
import androidx.compose.material.icons.outlined.Shield
-import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -54,18 +53,16 @@
fun SettingsCollapsibleCard(
title: String,
imageVector: ImageVector,
- models: List<CardModel>
+ models: List<CardModel>,
) {
var expanded by rememberSaveable { mutableStateOf(false) }
SettingsCard {
- Header(title, imageVector, models.size, expanded) { expanded = it }
+ SettingsCardContent {
+ Header(title, imageVector, models.size, expanded) { expanded = it }
+ }
AnimatedVisibility(expanded) {
Column {
for (model in models) {
- HorizontalDivider(
- thickness = SettingsDimension.paddingSmall,
- color = MaterialTheme.colorScheme.surface,
- )
SettingsCardImpl(model)
}
}
diff --git a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
index 31e9696..bee9e20 100644
--- a/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
+++ b/packages/SettingsLib/res/layout-v33/restricted_switch_preference.xml
@@ -53,7 +53,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxLines="2"
+ android:maxLines="10"
android:hyphenationFrequency="normalFast"
android:lineBreakWordStyle="phrase"
android:textAppearance="?android:attr/textAppearanceListItem"
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8412cba..5c09b16 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -251,6 +251,8 @@
Settings.Secure.STYLUS_HANDWRITING_ENABLED,
Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
Settings.Secure.CREDENTIAL_SERVICE,
- Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 9197554..b0169a1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -110,6 +110,9 @@
VALIDATORS.put(Secure.FONT_WEIGHT_ADJUSTMENT, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_LEVEL, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.REDUCE_BRIGHT_COLORS_PERSIST_ACROSS_REBOOTS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EVEN_DIMMER_ACTIVATED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EVEN_DIMMER_MIN_NITS,
+ new InclusiveFloatRangeValidator(0.0f, Float.MAX_VALUE));
VALIDATORS.put(Secure.TTS_DEFAULT_RATE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_PITCH, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.TTS_DEFAULT_SYNTH, PACKAGE_NAME_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a509ba3..a978889 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2135,6 +2135,15 @@
Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED,
SecureSettingsProto.ENHANCED_VOICE_PRIVACY_ENABLED);
+ final long evenDimmerToken = p.start(SecureSettingsProto.EVEN_DIMMER);
+ dumpSetting(s, p,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED,
+ SecureSettingsProto.EvenDimmer.EVEN_DIMMER_ACTIVATED);
+ dumpSetting(s, p,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS,
+ SecureSettingsProto.EvenDimmer.EVEN_DIMMER_MIN_NITS);
+ p.end(evenDimmerToken);
+
final long gestureToken = p.start(SecureSettingsProto.GESTURE);
dumpSetting(s, p,
Settings.Secure.AWARE_ENABLED,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0a71cda..f1029a3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -512,15 +512,6 @@
</intent-filter>
</activity-alias>
- <activity
- android:name="com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity"
- android:theme="@style/ForcedResizableTheme"
- android:excludeFromRecents="true"
- android:stateNotNeeded="true"
- android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
- android:exported="false">
- </activity>
-
<!-- Springboard for launching the share and edit activity. This needs to be in the main
system ui process since we need to notify the status bar to dismiss the keyguard -->
<receiver android:name=".screenshot.ActionProxyReceiver"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3e84597..77c4aa6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -68,6 +68,16 @@
}
flag {
+ name: "notification_background_tint_optimization"
+ namespace: "systemui"
+ description: "Re-enable the codepath that removed tinting of notifications when the"
+ " standard background color is desired. This was the behavior before we discovered"
+ " a resources threading issue, which we worked around by tinting the notification"
+ " backgrounds and footer buttons."
+ bug: "294347738"
+}
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
@@ -165,8 +175,16 @@
}
flag {
+ name: "rest_to_unlock"
+ namespace: "systemui"
+ description: "Require prolonged touch for fingerprint authentication"
+ bug: "303672286"
+}
+
+flag {
name: "record_issue_qs_tile"
namespace: "systemui"
description: "Replace Record Trace QS Tile with expanded Record Issue QS Tile"
bug: "305049544"
}
+
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 02db0d7..a613ad8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -29,17 +29,16 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -48,7 +47,6 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@@ -56,26 +54,32 @@
@Mock private lateinit var walletController: QuickAccessWalletController
@Mock private lateinit var activityStarter: ActivityStarter
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+
private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
context,
+ testDispatcher,
walletController,
activityStarter,
)
}
@Test
- fun affordance_keyguardShowing_hasWalletCard_visibleModel() = runBlockingTest {
+ fun affordance_keyguardShowing_hasWalletCard_visibleModel() = testScope.runTest {
setUpState()
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleModel.icon)
@@ -88,77 +92,61 @@
),
)
)
- job.cancel()
}
@Test
- fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
- runTest(UnconfinedTestDispatcher()) {
+ fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() = testScope.runTest {
setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- job.cancel()
}
@Test
- fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
- runTest(UnconfinedTestDispatcher()) {
- setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ fun affordance_keyguardShowing_hasPaymentCard_visibleModel() = testScope.runTest {
+ setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
- assertThat(visibleModel.icon)
- .isEqualTo(
- Icon.Loaded(
- drawable = ICON,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
- )
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+ assertThat(visibleModel.icon)
+ .isEqualTo(
+ Icon.Loaded(
+ drawable = ICON,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
)
- job.cancel()
- }
+ )
+ }
@Test
- fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+ fun affordance_walletFeatureNotEnabled_modelIsNone() = testScope.runTest {
setUpState(isWalletFeatureAvailable = false)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
- job.cancel()
}
@Test
- fun affordance_queryNotSuccessful_modelIsNone() = runBlockingTest {
+ fun affordance_queryNotSuccessful_modelIsNone() = testScope.runTest {
setUpState(isWalletQuerySuccessful = false)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
- job.cancel()
}
@Test
- fun affordance_noSelectedCard_modelIsNone() = runBlockingTest {
+ fun affordance_noSelectedCard_modelIsNone() = testScope.runTest {
setUpState(hasSelectedCard = false)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val latest by collectLastValue(underTest.lockScreenState)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
- job.cancel()
}
@Test
@@ -179,7 +167,7 @@
}
@Test
- fun getPickerScreenState_default() = runTest {
+ fun getPickerScreenState_default() = testScope.runTest {
setUpState()
assertThat(underTest.getPickerScreenState())
@@ -187,7 +175,7 @@
}
@Test
- fun getPickerScreenState_unavailable() = runTest {
+ fun getPickerScreenState_unavailable() = testScope.runTest {
setUpState(
isWalletServiceAvailable = false,
)
@@ -197,7 +185,7 @@
}
@Test
- fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
+ fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = testScope.runTest {
setUpState(
isWalletFeatureAvailable = false,
)
@@ -207,7 +195,7 @@
}
@Test
- fun getPickerScreenState_disabledWhenThereIsNoCard() = runTest {
+ fun getPickerScreenState_disabledWhenThereIsNoCard() = testScope.runTest {
setUpState(
hasSelectedCard = false,
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 8d1d905..b343add 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -37,17 +37,23 @@
@MainThread
fun enable(onShadeInteraction: Runnable) {
- if (shadeExpansionCollectorJob == null) {
- shadeExpansionCollectorJob =
- scope.launch {
- // wait for it to emit true once
- shadeInteractorLazy.get().isUserInteracting.first { it }
- onShadeInteraction.run()
- }
- shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
- } else {
+ if (shadeExpansionCollectorJob != null) {
Log.e(TAG, "Already enabled")
+ return
}
+ if (shadeInteractorLazy.get().isUserInteracting.value) {
+ Log.e(TAG, "isUserInteracting already true, skipping enable")
+ return
+ }
+ shadeExpansionCollectorJob =
+ scope.launch {
+ Log.i(TAG, "Enable detector")
+ // wait for it to emit true once
+ shadeInteractorLazy.get().isUserInteracting.first { it }
+ Log.i(TAG, "Detector detected shade interaction")
+ onShadeInteraction.run()
+ }
+ shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
}
@MainThread
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0c24752..ff65b31 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -240,11 +240,6 @@
@JvmField
val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
- /** Flag to enable rest to unlock feature. */
- // TODO(b/303672286): Tracking bug
- @JvmField
- val REST_TO_UNLOCK: UnreleasedFlag = unreleasedFlag("rest_to_unlock")
-
/**
* TODO(b/278086361): Tracking bug
* Complete rewrite of the interactions between System UI and Window Manager involving keyguard
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 7337292..a988a5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -32,13 +32,19 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import com.android.systemui.wallet.util.getPaymentCards
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
/** Quick access wallet quick affordance data source. */
@SysUISingleton
@@ -46,6 +52,7 @@
@Inject
constructor(
@Application private val context: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
@@ -56,6 +63,7 @@
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+ @OptIn(ExperimentalCoroutinesApi::class)
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
conflatedCallbackFlow {
val callback =
@@ -63,11 +71,7 @@
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true
trySendWithFailureLogging(
- state(
- isFeatureEnabled = isWalletAvailable(),
- hasCard = hasCards,
- tileIcon = walletController.walletClient.tileIcon,
- ),
+ hasCards,
TAG,
)
}
@@ -75,7 +79,7 @@
override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
trySendWithFailureLogging(
- KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
+ null,
TAG,
)
}
@@ -86,8 +90,12 @@
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
)
- walletController.updateWalletPreference()
- walletController.queryWalletCards(callback)
+
+ withContext(backgroundDispatcher) {
+ // Both must be called on background thread
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+ }
awaitClose {
walletController.unregisterWalletChangeObservers(
@@ -95,6 +103,19 @@
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
)
}
+ }.flatMapLatest { hasCards ->
+ // If hasCards is null, this indicates an error occurred upon card retrieval
+ val state =
+ if (hasCards == null) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ } else {
+ state(
+ isWalletAvailable(),
+ hasCards,
+ walletController.walletClient.tileIcon,
+ )
+ }
+ flowOf(state)
}
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
@@ -131,25 +152,33 @@
}
private suspend fun queryCards(): List<WalletCard> {
- return suspendCancellableCoroutine { continuation ->
- val callback =
- object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
- continuation.resumeWith(
- Result.success(getPaymentCards(response.walletCards) ?: emptyList())
- )
- }
+ return withContext(backgroundDispatcher) {
+ suspendCancellableCoroutine { continuation ->
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
+ continuation.resumeWith(
+ Result.success(getPaymentCards(response.walletCards))
+ )
+ }
- override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
- continuation.resumeWith(Result.success(emptyList()))
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
+ continuation.resumeWith(Result.success(emptyList()))
+ }
}
- }
- walletController.queryWalletCards(callback)
+ // Must be called on background thread
+ walletController.queryWalletCards(callback)
+ }
}
}
- private fun isWalletAvailable() =
- with(walletController.walletClient) { isWalletServiceAvailable && isWalletFeatureAvailable }
+ private suspend fun isWalletAvailable() =
+ withContext(backgroundDispatcher) {
+ with(walletController.walletClient) {
+ // Must be called on background thread
+ isWalletServiceAvailable && isWalletFeatureAvailable
+ }
+ }
private fun state(
isFeatureEnabled: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
index 0bee48a..560e4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -19,11 +19,10 @@
import android.animation.ValueAnimator
import android.graphics.Point
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags
import com.android.systemui.biometrics.SideFpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
@@ -50,11 +49,10 @@
private val sfpsController: dagger.Lazy<SideFpsController>,
private val logger: SideFpsLogger,
private val commandRegistry: CommandRegistry,
- private val featureFlagsClassic: FeatureFlagsClassic,
) : CoreStartable {
override fun start() {
- if (!featureFlagsClassic.isEnabled(Flags.REST_TO_UNLOCK)) {
+ if (!Flags.restToUnlock()) {
return
}
// When the rest to unlock feature is disabled by the user, stop any coroutines that are
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index a0f5baf..2d0712c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,14 +20,13 @@
import android.content.Context
import android.graphics.Point
import androidx.core.animation.doOnEnd
+import com.android.systemui.Flags
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
@@ -58,7 +57,6 @@
private val sfpsSensorInteractor: SideFpsSensorInteractor,
displayStateInteractor: DisplayStateInteractor,
@Application private val applicationScope: CoroutineScope,
- private val featureFlagsClassic: FeatureFlagsClassic,
) {
private val _progress = MutableStateFlow(0.0f)
private val _visible = MutableStateFlow(false)
@@ -155,7 +153,7 @@
sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
init {
- if (featureFlagsClassic.isEnabled(Flags.REST_TO_UNLOCK)) {
+ if (Flags.restToUnlock()) {
launchAnimator()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
index 5bfc7dc..039d0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationHandle.java
@@ -105,8 +105,8 @@
float height = mRadius * 2 + additionalHeight;
float additionalWidth = mAdditionalWidthForAnimation * mPulseAnimationProgress;
float width = getWidth() + additionalWidth;
- float x = -(additionalWidth / 2);
- float y = navHeight - mBottom - height - (additionalHeight / 2);
+ float x = -additionalWidth;
+ float y = navHeight - mBottom - height + (additionalHeight / 2);
float adjustedRadius = height / 2;
canvas.drawRoundRect(x, y, width, y + height, adjustedRadius, adjustedRadius, mPaint);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 6a9757f..31a4de4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -39,7 +39,7 @@
* input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
* but a transition they initiated is still animating.
*/
- val isUserInteracting: Flow<Boolean>
+ val isUserInteracting: StateFlow<Boolean>
/** Are touches allowed on the notification panel? */
val isShadeTouchable: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index d41c5a6..6defbcf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -39,7 +39,7 @@
override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean
- override val isUserInteracting: Flow<Boolean> = inactiveFlowBoolean
+ override val isUserInteracting: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 68600e9..7a340d2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -65,9 +65,10 @@
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
- override val isUserInteracting: Flow<Boolean> =
+ override val isUserInteracting: StateFlow<Boolean> =
combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
.distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
override val isShadeTouchable: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index c1a630f..909cff37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -493,11 +493,11 @@
Arrays.asList(
Pair.create(KeyEvent.KEYCODE_SLASH, KeyEvent.META_META_ON))),
/* Back: go back to previous state (back button) */
- /* Meta + Grave, Meta + backspace, Meta + left arrow */
+ /* Meta + Escape, Meta + Grave, Meta + backspace, Meta + left arrow */
new ShortcutKeyGroupMultiMappingInfo(
context.getString(R.string.group_system_go_back),
Arrays.asList(
- Pair.create(KeyEvent.KEYCODE_GRAVE, KeyEvent.META_META_ON),
+ Pair.create(KeyEvent.KEYCODE_ESCAPE, KeyEvent.META_META_ON),
Pair.create(KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON),
Pair.create(KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_META_ON))),
/* Access home screen: Meta + H, Meta + Enter */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 31893b4..e90ddf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -15,37 +15,48 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
class ActiveNotificationsInteractor
@Inject
constructor(
private val repository: ActiveNotificationListRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
/** Notifications actively presented to the user in the notification stack, in order. */
val topLevelRepresentativeNotifications: Flow<List<ActiveNotificationModel>> =
- repository.activeNotifications.map { store ->
- store.renderList.map { key ->
- val entry =
- store[key]
- ?: error("Could not find notification with key $key in active notif store.")
- when (entry) {
- is ActiveNotificationGroupModel -> entry.summary
- is ActiveNotificationModel -> entry
+ repository.activeNotifications
+ .map { store ->
+ store.renderList.map { key ->
+ val entry =
+ store[key]
+ ?: error(
+ "Could not find notification with key $key in active notif store."
+ )
+ when (entry) {
+ is ActiveNotificationGroupModel -> entry.summary
+ is ActiveNotificationModel -> entry
+ }
}
}
- }
+ .flowOn(backgroundDispatcher)
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
- repository.activeNotifications.map { it.renderList.isNotEmpty() }.distinctUntilChanged()
+ repository.activeNotifications
+ .map { it.renderList.isNotEmpty() }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
/**
* The same as [areAnyNotificationsPresent], but without flows, for easy access in synchronous
@@ -59,6 +70,7 @@
repository.notifStats
.map { it.hasClearableAlertingNotifs || it.hasClearableSilentNotifs }
.distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
fun setNotifStats(notifStats: NotifStats) {
repository.notifStats.value = notifStats
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
index 87b8e55..73341db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsKeyguardInteractor.kt
@@ -15,19 +15,24 @@
*/
package com.android.systemui.statusbar.notification.domain.interactor
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
/** Domain logic pertaining to notifications on the keyguard. */
class NotificationsKeyguardInteractor
@Inject
constructor(
repository: NotificationsKeyguardViewStateRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
) {
/** Is a pulse expansion occurring? */
- val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding
+ val isPulseExpanding: Flow<Boolean> = repository.isPulseExpanding.flowOn(backgroundDispatcher)
/** Are notifications fully hidden from view? */
- val areNotificationsFullyHidden: Flow<Boolean> = repository.areNotificationsFullyHidden
+ val areNotificationsFullyHidden: Flow<Boolean> =
+ repository.areNotificationsFullyHidden.flowOn(backgroundDispatcher)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 3184d5e..3616fd6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -18,6 +18,8 @@
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
+import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.StringRes;
@@ -313,15 +315,16 @@
Resources.Theme theme = mContext.getTheme();
final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorOnSurface);
- final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh);
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
- // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
- final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
- if (scHigh != 0) {
- clearAllBg.setColorFilter(bgColorFilter);
- manageBg.setColorFilter(bgColorFilter);
+ if (!notificationBackgroundTintOptimization()) {
+ final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext,
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh);
+ if (scHigh != 0) {
+ final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
+ clearAllBg.setColorFilter(bgColorFilter);
+ manageBg.setColorFilter(bgColorFilter);
+ }
}
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7c8d762..4fe05ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.Flags.notificationBackgroundTintOptimization;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -309,8 +311,7 @@
protected void setBackgroundTintColor(int color) {
if (color != mCurrentBackgroundTint) {
mCurrentBackgroundTint = color;
- // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe
- if (false && color == mNormalColor) {
+ if (notificationBackgroundTintOptimization() && color == mNormalColor) {
// We don't need to tint a normal notification
color = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
index 0e6df6b..e031be2 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java
@@ -19,6 +19,7 @@
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
+import android.annotation.WorkerThread;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -146,7 +147,9 @@
/**
* Update the "show wallet" preference.
+ * This should not be called on the main thread.
*/
+ @WorkerThread
public void updateWalletPreference() {
mWalletEnabled = mQuickAccessWalletClient.isWalletServiceAvailable()
&& mQuickAccessWalletClient.isWalletFeatureAvailable()
@@ -155,10 +158,12 @@
/**
* Query the wallet cards from {@link QuickAccessWalletClient}.
+ * This should not be called on the main thread.
*
* @param cardsRetriever a callback to retrieve wallet cards.
* @param maxCards the maximum number of cards requested from the QuickAccessWallet
*/
+ @WorkerThread
public void queryWalletCards(
QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever, int maxCards) {
if (mClock.elapsedRealtime() - mQawClientCreatedTimeMillis
@@ -182,9 +187,11 @@
/**
* Query the wallet cards from {@link QuickAccessWalletClient}.
+ * This should not be called on the main thread.
*
* @param cardsRetriever a callback to retrieve wallet cards.
*/
+ @WorkerThread
public void queryWalletCards(
QuickAccessWalletClient.OnWalletCardsRetrievedCallback cardsRetriever) {
queryWalletCards(cardsRetriever, /* maxCards= */ 1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 993dbac..54d6b53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -95,6 +95,18 @@
}
@Test
+ fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() =
+ testComponent.runTest {
+ // GIVEN isInteracting starts true
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+ detector.enable(action)
+
+ // THEN action was not run
+ verifyZeroInteractions(action)
+ }
+
+ @Test
fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
testComponent.runTest {
// GIVEN shade is closed and detector is enabled
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 1dbb297..62c0ebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -22,6 +22,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
@@ -293,8 +295,10 @@
)
);
- mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(new ActiveNotificationListRepository());
+ mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
+ new ActiveNotificationListRepository(),
+ StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)
+ );
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index b86f841..6374d5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -25,14 +25,19 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@SmallTest
class RenderNotificationsListInteractorTest : SysuiTestCase() {
+ private val backgroundDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(backgroundDispatcher)
private val notifsRepository = ActiveNotificationListRepository()
- private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository)
+ private val notifsInteractor =
+ ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher)
private val underTest =
RenderNotificationListInteractor(
notifsRepository,
@@ -40,21 +45,26 @@
)
@Test
- fun setRenderedList_preservesOrdering() = runTest {
- val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
- val keys = (1..50).shuffled().map { "$it" }
- val entries =
- keys.map {
- mock<ListEntry> {
- val mockRep = mock<NotificationEntry> {
- whenever(key).thenReturn(it)
- whenever(sbn).thenReturn(mock())
- whenever(icons).thenReturn(mock())
+ fun setRenderedList_preservesOrdering() =
+ testScope.runTest {
+ val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+ val keys = (1..50).shuffled().map { "$it" }
+ val entries =
+ keys.map {
+ mock<ListEntry> {
+ val mockRep =
+ mock<NotificationEntry> {
+ whenever(key).thenReturn(it)
+ whenever(sbn).thenReturn(mock())
+ whenever(icons).thenReturn(mock())
+ }
+ whenever(representativeEntry).thenReturn(mockRep)
}
- whenever(representativeEntry).thenReturn(mockRep)
}
- }
- underTest.setRenderedList(entries)
- assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
- }
+ underTest.setRenderedList(entries)
+ assertThat(notifs)
+ .comparingElementsUsing(byKey)
+ .containsExactlyElementsIn(keys)
+ .inOrder()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ff5c026..7558974 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import android.metrics.LogMaker;
import android.testing.AndroidTestingRunner;
@@ -169,7 +170,8 @@
new ActiveNotificationListRepository();
private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(mActiveNotificationsRepository);
+ new ActiveNotificationsInteractor(mActiveNotificationsRepository,
+ StandardTestDispatcher(/* scheduler = */ null, /* name = */ null));
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
new SeenNotificationsInteractor(mActiveNotificationsRepository);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
index 3d7fb6d..01f4535 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorKosmos.kt
@@ -17,7 +17,10 @@
package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
val Kosmos.activeNotificationsInteractor by
- Kosmos.Fixture { ActiveNotificationsInteractor(activeNotificationListRepository) }
+ Kosmos.Fixture {
+ ActiveNotificationsInteractor(activeNotificationListRepository, testDispatcher)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
index 61a38b8..432464e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationsKeyguardInteractorKosmos.kt
@@ -18,11 +18,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
val Kosmos.notificationsKeyguardInteractor by Fixture {
NotificationsKeyguardInteractor(
repository = notificationsKeyguardViewStateRepository,
+ backgroundDispatcher = testDispatcher,
)
}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
new file mode 100644
index 0000000..be1f081
--- /dev/null
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+ <!-- If true, attach the navigation bar to the app during app transition -->
+ <bool name="config_attachNavBarToAppDuringTransition">false</bool>
+</resources>
diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt
index 8ad21fa..f64f26d 100644
--- a/ravenwood/ravenwood-standard-options.txt
+++ b/ravenwood/ravenwood-standard-options.txt
@@ -1,6 +1,6 @@
# File containing standard options to HostStubGen for Ravenwood
---debug
+# --debug # To enable debug log on consone
# Keep all classes / methods / fields, but make the methods throw.
--default-throw
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 71a1f01..cce596b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -833,6 +833,13 @@
}
@Override
+ public boolean isPermissionTransferUserConsented(String packageName, int userId,
+ int associationId) {
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
+ userId, associationId);
+ }
+
+ @Override
public void startSystemDataTransfer(String packageName, int userId, int associationId,
ISystemDataTransferCallback callback) {
mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index e5c847a..bd646fa 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -120,7 +120,8 @@
* Resolve the requested association, throwing if the caller doesn't have
* adequate permissions.
*/
- private @NonNull AssociationInfo resolveAssociation(String packageName, int userId,
+ @NonNull
+ private AssociationInfo resolveAssociation(String packageName, int userId,
int associationId) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
@@ -133,6 +134,20 @@
}
/**
+ * Return whether the user has consented to the permission transfer for the association.
+ */
+ public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId,
+ int associationId) {
+ resolveAssociation(packageName, userId, associationId);
+
+ PermissionSyncRequest request = getPermissionSyncRequest(associationId);
+ if (request == null) {
+ return false;
+ }
+ return request.isUserConsented();
+ }
+
+ /**
* Build a PendingIntent of permission sync user consent dialog
*/
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 6794f75..3280afdf 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -1797,8 +1797,13 @@
mFingerprints);
try {
- dump.write("user_keys", AdbDebuggingManagerProto.USER_KEYS,
- FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
+ File userKeys = new File("/data/misc/adb/adb_keys");
+ if (userKeys.exists()) {
+ dump.write("user_keys", AdbDebuggingManagerProto.USER_KEYS,
+ FileUtils.readTextFile(userKeys, 0, null));
+ } else {
+ Slog.i(TAG, "No user keys on this device");
+ }
} catch (IOException e) {
Slog.i(TAG, "Cannot read user keys", e);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 1928780..f2d9759 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -480,6 +480,7 @@
}
public void systemServicesReady() {
+ mStats.setPowerStatsCollectorEnabled(Flags.streamlinedBatteryStats());
mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(Flags.streamlinedBatteryStats());
mWorker.systemServicesReady();
mStats.systemServicesReady(mContext);
@@ -2648,7 +2649,9 @@
pw.println(" --proto: output as a binary protobuffer");
pw.println(" --model power-profile: use the power profile model"
+ " even if measured energy is available");
- pw.println(" --sample: collect and dump a sample of stats for debugging purpose");
+ if (Flags.streamlinedBatteryStats()) {
+ pw.println(" --sample: collect and dump a sample of stats for debugging purpose");
+ }
pw.println(" <package.name>: optional name of package to filter output by.");
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
@@ -2938,10 +2941,10 @@
mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "),
SystemClock.elapsedRealtime());
return;
- } else if ("--sample".equals(arg)) {
+ } else if (Flags.streamlinedBatteryStats() && "--sample".equals(arg)) {
dumpStatsSample(pw);
return;
- } else if ("--aggregated".equals(arg)) {
+ } else if (Flags.streamlinedBatteryStats() && "--aggregated".equals(arg)) {
dumpAggregatedStats(pw);
return;
} else if ("--store".equals(arg)) {
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 3ce92bc..0b56146 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -361,7 +361,8 @@
&& (record.userId == testRecord.userId)
&& record.intent.filterEquals(testRecord.intent)
&& isReceiverEquals(receiver, testReceiver)
- && testRecord.allReceiversPending()) {
+ && testRecord.allReceiversPending()
+ && record.isMatchingRecord(testRecord)) {
// Exact match found; perform in-place swap
args.arg1 = record;
args.argi1 = recordIndex;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index e07631c..ad49991 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -230,6 +230,14 @@
new AtomicReference<>();
/**
+ * Container for holding the set of broadcast records that matches an enqueueing record.
+ * @see BroadcastRecord#isMatchingRecord(BroadcastRecord)
+ */
+ @GuardedBy("mService")
+ private final AtomicReference<ArrayMap<BroadcastRecord, Boolean>> mMatchingRecordsCache =
+ new AtomicReference<>();
+
+ /**
* Map from UID to its last known "foreground" state. A UID is considered to be in
* "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}.
* <p>
@@ -747,6 +755,12 @@
if (replacedBroadcasts == null) {
replacedBroadcasts = new ArraySet<>();
}
+ ArrayMap<BroadcastRecord, Boolean> matchingBroadcasts =
+ mMatchingRecordsCache.getAndSet(null);
+ if (matchingBroadcasts == null) {
+ matchingBroadcasts = new ArrayMap<>();
+ }
+ r.setMatchingRecordsCache(matchingBroadcasts);
boolean enqueuedBroadcast = false;
for (int i = 0; i < r.receivers.size(); i++) {
@@ -780,6 +794,9 @@
skipAndCancelReplacedBroadcasts(replacedBroadcasts);
replacedBroadcasts.clear();
mReplacedBroadcastsCache.compareAndSet(null, replacedBroadcasts);
+ matchingBroadcasts.clear();
+ r.clearMatchingRecordsCache();
+ mMatchingRecordsCache.compareAndSet(null, matchingBroadcasts);
// If nothing to dispatch, send any pending result immediately
if (r.receivers.isEmpty() || !enqueuedBroadcast) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 198adcb..99b91ff 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -61,6 +61,7 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.PrintWriterPrinter;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -164,6 +165,11 @@
@Nullable
final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+ // Cache of records that are "matching" this. Only used at the time of enqueuing this record
+ // into the queue.
+ @Nullable
+ private ArrayMap<BroadcastRecord, Boolean> mMatchingRecordsCache;
+
private @Nullable String mCachedToString;
private @Nullable String mCachedToShortString;
@@ -1250,6 +1256,33 @@
return (terminalCount == 0 && dispatchTime <= 0);
}
+ boolean isMatchingRecord(@NonNull BroadcastRecord record) {
+ final int idx = mMatchingRecordsCache.indexOfKey(record);
+ if (idx > 0) {
+ return mMatchingRecordsCache.valueAt(idx);
+ }
+ // Consider a record to be matching if has the same receivers in the same order.
+ boolean matches = (receivers.size() == record.receivers.size());
+ if (matches) {
+ for (int i = receivers.size() - 1; i >= 0; --i) {
+ if (!isReceiverEquals(receivers.get(i), record.receivers.get(i))) {
+ matches = false;
+ break;
+ }
+ }
+ }
+ mMatchingRecordsCache.put(record, matches);
+ return matches;
+ }
+
+ void setMatchingRecordsCache(@NonNull ArrayMap<BroadcastRecord, Boolean> matchingRecordsCache) {
+ mMatchingRecordsCache = matchingRecordsCache;
+ }
+
+ void clearMatchingRecordsCache() {
+ mMatchingRecordsCache = null;
+ }
+
@Override
public String toString() {
if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5d47e35..4f6c6d6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -38,6 +38,7 @@
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
+import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
@@ -94,6 +95,7 @@
import android.hidl.manager.V1_0.IServiceManager;
import android.media.AudioAttributes;
import android.media.AudioAttributes.AttributeSystemUsage;
+import android.media.AudioDescriptor;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioDeviceVolumeManager;
@@ -106,6 +108,7 @@
import android.media.AudioManagerInternal;
import android.media.AudioMixerAttributes;
import android.media.AudioPlaybackConfiguration;
+import android.media.AudioProfile;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
@@ -233,6 +236,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -1191,6 +1195,19 @@
MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM] = maxAlarmVolume;
}
+ if (alarmMinVolumeZero()) {
+ try {
+ int minAlarmVolume = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_audio_alarm_min_vol);
+ if (minAlarmVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]) {
+ MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM] = minAlarmVolume;
+ } else {
+ Log.e(TAG, "Error min alarm volume greater than max alarm volume");
+ }
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Error querying for alarm min volume ", e);
+ }
+ }
int defaultAlarmVolume = SystemProperties.getInt("ro.config.alarm_vol_default", -1);
if (defaultAlarmVolume != -1 &&
defaultAlarmVolume <= MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]) {
@@ -7673,6 +7690,13 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ConnectionState {}
+ /**
+ * Default SAD for a TV using ARC, used when the Amplifier didn't report any SADs.
+ * Represents 2-channel LPCM including all defined sample rates and bit depths.
+ * For the format definition, see Table 34 in the CEA standard CEA-861-D.
+ */
+ private static final byte[] DEFAULT_ARC_AUDIO_DESCRIPTOR = new byte[]{0x09, 0x7f, 0x07};
+
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
/**
* see AudioManager.setWiredDeviceConnectionState()
@@ -7684,6 +7708,27 @@
attributes = retrieveBluetoothAddress(attributes);
+ // When using ARC, a TV should use default 2 channel LPCM if the Amplifier didn't
+ // report any SADs. See section 13.15.3 of the HDMI-CEC spec version 1.4b.
+ if (attributes.getType() == AudioDeviceInfo.TYPE_HDMI_ARC
+ && attributes.getRole() == AudioDeviceAttributes.ROLE_OUTPUT
+ && attributes.getAudioDescriptors().isEmpty()) {
+ attributes = new AudioDeviceAttributes(
+ attributes.getRole(),
+ attributes.getType(),
+ attributes.getAddress(),
+ attributes.getName(),
+ attributes.getAudioProfiles(),
+ new ArrayList<AudioDescriptor>(Collections.singletonList(
+ new AudioDescriptor(
+ AudioDescriptor.STANDARD_EDID,
+ AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
+ DEFAULT_ARC_AUDIO_DESCRIPTOR
+ )
+ ))
+ );
+ }
+
if (state != CONNECTION_STATE_CONNECTED
&& state != CONNECTION_STATE_DISCONNECTED) {
throw new IllegalArgumentException("Invalid state " + state);
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 8a884b6..42ebc40 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -25,7 +25,7 @@
import java.io.PrintWriter;
/**
- * Provides max allowed brightness
+ * Provides brightness range constraints
*/
abstract class BrightnessClamper<T> {
@@ -74,6 +74,7 @@
protected enum Type {
THERMAL,
POWER,
- BEDTIME_MODE
+ BEDTIME_MODE,
+ LUX,
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 765608e..01694dd 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -68,14 +68,14 @@
private boolean mClamperApplied = false;
public BrightnessClamperController(Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags) {
this(new Injector(), handler, clamperChangeListener, data, context, flags);
}
@VisibleForTesting
BrightnessClamperController(Injector injector, Handler handler,
- ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
+ ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context,
DisplayManagerFlags flags) {
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
mHandler = handler;
@@ -147,7 +147,8 @@
* Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState
* TODO: b/263362199
*/
- @BrightnessInfo.BrightnessMaxReason public int getBrightnessMaxReason() {
+ @BrightnessInfo.BrightnessMaxReason
+ public int getBrightnessMaxReason() {
if (mClamperType == null) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
} else if (mClamperType == Type.THERMAL) {
@@ -241,12 +242,15 @@
new BrightnessThermalClamper(handler, clamperChangeListener, data));
if (flags.isPowerThrottlingClamperEnabled()) {
clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener,
- data));
+ data));
}
if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
clamperChangeListener, data));
}
+ if (flags.isEvenDimmerEnabled()) {
+ clampers.add(new BrightnessMinClamper(handler, clamperChangeListener, context));
+ }
return clampers;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java
new file mode 100644
index 0000000..71efca1
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessMinClamper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.DebugUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Class used to prevent the screen brightness dipping below a certain value, based on current
+ * lux conditions.
+ */
+public class BrightnessMinClamper extends BrightnessClamper {
+
+ // To enable these logs, run:
+ // 'adb shell setprop persist.log.tag.BrightnessMinClamper DEBUG && adb reboot'
+ private static final String TAG = "BrightnessMinClamper";
+ private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+ private final SettingsObserver mSettingsObserver;
+
+ ContentResolver mContentResolver;
+ private float mNitsLowerBound;
+
+ @VisibleForTesting
+ BrightnessMinClamper(Handler handler,
+ BrightnessClamperController.ClamperChangeListener listener, Context context) {
+ super(handler, listener);
+
+ mContentResolver = context.getContentResolver();
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mHandler.post(() -> {
+ start();
+ });
+ }
+
+ private void recalculateLowerBound() {
+ final int userId = UserHandle.USER_CURRENT;
+ float settingNitsLowerBound = Settings.Secure.getFloatForUser(
+ mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
+ /* def= */ PowerManager.BRIGHTNESS_MIN, userId);
+
+ boolean isActive = Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED,
+ /* def= */ 0, userId) == 1;
+
+ // TODO: luxBasedNitsLowerBound = mMinNitsToLuxSpline(currentLux);
+ float luxBasedNitsLowerBound = PowerManager.BRIGHTNESS_MIN;
+ final float nitsLowerBound = Math.max(settingNitsLowerBound, luxBasedNitsLowerBound);
+
+ if (mNitsLowerBound != nitsLowerBound || mIsActive != isActive) {
+ mIsActive = isActive;
+ mNitsLowerBound = nitsLowerBound;
+ if (DEBUG) {
+ Slog.i(TAG, "mIsActive: " + mIsActive);
+ }
+ // TODO: mBrightnessCap = nitsToBrightnessSpline(mNitsLowerBound);
+ mChangeListener.onChanged();
+ }
+ }
+
+ void start() {
+ recalculateLowerBound();
+ }
+
+
+ @Override
+ Type getType() {
+ return Type.LUX;
+ }
+
+ @Override
+ void onDeviceConfigChanged() {
+ // TODO
+ }
+
+ @Override
+ void onDisplayChanged(Object displayData) {
+
+ }
+
+ @Override
+ void stop() {
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+
+ @Override
+ void dump(PrintWriter pw) {
+ pw.println("BrightnessMinClamper:");
+ pw.println(" mBrightnessCap=" + mBrightnessCap);
+ pw.println(" mIsActive=" + mIsActive);
+ pw.println(" mNitsLowerBound=" + mNitsLowerBound);
+ super.dump(pw);
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_MIN_NITS),
+ false, this);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.EVEN_DIMMER_ACTIVATED),
+ false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ recalculateLowerBound();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index c71f0cf2..2d5da71 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -78,6 +78,9 @@
Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
Flags::enablePowerThrottlingClamper);
+ private final FlagState mEvenDimmerFlagState = new FlagState(
+ Flags.FLAG_EVEN_DIMMER,
+ Flags::evenDimmer);
private final FlagState mSmallAreaDetectionFlagState = new FlagState(
com.android.graphics.surfaceflinger.flags.Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
com.android.graphics.surfaceflinger.flags.Flags::enableSmallAreaDetection);
@@ -174,6 +177,11 @@
return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
}
+ /** Returns whether brightness range is allowed to extend below traditional range. */
+ public boolean isEvenDimmerEnabled() {
+ return mEvenDimmerFlagState.isEnabled();
+ }
+
public boolean isSmallAreaDetectionEnabled() {
return mSmallAreaDetectionFlagState.isEnabled();
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9dfa1ee..1b4d74c 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -106,6 +106,14 @@
}
flag {
+ name: "even_dimmer"
+ namespace: "display_manager"
+ description: "Feature flag for extending the brightness below traditional range"
+ bug: "179428400"
+ is_fixed_read_only: true
+}
+
+flag {
name: "brightness_int_range_user_perception"
namespace: "display_manager"
description: "Feature flag for converting the brightness integer range to the user perception scale"
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 2dd2a16..ebc784d 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -369,15 +369,15 @@
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return;
}
- int vendorId = inputDevice.getVendorId();
- int productId = inputDevice.getProductId();
if (keyboardSystemEvent == null) {
Slog.w(TAG, "Invalid keyboard event logging, keycode = " + Arrays.toString(keyCodes)
+ ", modifier state = " + modifierState);
return;
}
FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
- vendorId, productId, keyboardSystemEvent.getIntValue(), keyCodes, modifierState);
+ inputDevice.getVendorId(), inputDevice.getProductId(),
+ keyboardSystemEvent.getIntValue(), keyCodes, modifierState,
+ inputDevice.getDeviceBus());
if (DEBUG) {
Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemEvent.mName);
@@ -402,7 +402,7 @@
// Push the atom to Statsd
FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_CONFIGURED,
event.isFirstConfiguration(), event.getVendorId(), event.getProductId(),
- proto.getBytes());
+ proto.getBytes(), event.getDeviceBus());
if (DEBUG) {
Slog.d(TAG, "Logging Keyboard configuration event: " + event);
@@ -467,6 +467,10 @@
return mInputDevice.getProductId();
}
+ public int getDeviceBus() {
+ return mInputDevice.getDeviceBus();
+ }
+
public boolean isFirstConfiguration() {
return mIsFirstConfiguration;
}
@@ -479,6 +483,7 @@
public String toString() {
return "InputDevice = {VendorId = " + Integer.toHexString(getVendorId())
+ ", ProductId = " + Integer.toHexString(getProductId())
+ + ", Device Bus = " + Integer.toHexString(getDeviceBus())
+ "}, isFirstConfiguration = " + mIsFirstConfiguration
+ ", LayoutConfigurations = " + mLayoutConfigurations;
}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 749d6b0..dcb86a7 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -22,6 +22,8 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UiThread;
+import android.content.ComponentName;
+import android.content.pm.PackageManagerInternal;
import android.hardware.input.InputManagerGlobal;
import android.os.Handler;
import android.os.IBinder;
@@ -66,6 +68,7 @@
private final Looper mLooper;
private final InputManagerInternal mInputManagerInternal;
private final WindowManagerInternal mWindowManagerInternal;
+ private final PackageManagerInternal mPackageManagerInternal;
private ArrayList<MotionEvent> mHandwritingBuffer;
private InputEventReceiver mHandwritingEventReceiver;
@@ -75,6 +78,7 @@
// when set, package names are used for handwriting delegation.
private @Nullable String mDelegatePackageName;
private @Nullable String mDelegatorPackageName;
+ private boolean mDelegatorFromDefaultHomePackage;
private Runnable mDelegationIdleTimeoutRunnable;
private Handler mDelegationIdleTimeoutHandler;
@@ -88,6 +92,7 @@
mCurrentDisplayId = Display.INVALID_DISPLAY;
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mCurrentRequestId = 0;
mInkWindowInitRunnable = inkWindowInitRunnable;
}
@@ -151,9 +156,20 @@
* @see InputMethodManager#prepareStylusHandwritingDelegation(View, String)
*/
void prepareStylusHandwritingDelegation(
- @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
+ int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
mDelegatePackageName = delegatePackageName;
mDelegatorPackageName = delegatorPackageName;
+ mDelegatorFromDefaultHomePackage = false;
+ // mDelegatorFromDefaultHomeActivity is only used in the cross-package delegation case.
+ // For same-package delegation, it doesn't need to be checked.
+ if (!delegatorPackageName.equals(delegatePackageName)) {
+ ComponentName defaultHomeActivity =
+ mPackageManagerInternal.getDefaultHomeActivity(userId);
+ if (defaultHomeActivity != null) {
+ mDelegatorFromDefaultHomePackage =
+ delegatorPackageName.equals(defaultHomeActivity.getPackageName());
+ }
+ }
if (mHandwritingBuffer == null) {
mHandwritingBuffer = new ArrayList<>(getHandwritingBufferSize());
} else {
@@ -170,6 +186,10 @@
return mDelegatorPackageName;
}
+ boolean isDelegatorFromDefaultHomePackage() {
+ return mDelegatorFromDefaultHomePackage;
+ }
+
private void scheduleHandwritingDelegationTimeout() {
if (mDelegationIdleTimeoutHandler == null) {
mDelegationIdleTimeoutHandler = new Handler(mLooper);
@@ -210,6 +230,7 @@
mDelegationIdleTimeoutRunnable = null;
mDelegatorPackageName = null;
mDelegatePackageName = null;
+ mDelegatorFromDefaultHomePackage = false;
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ddb32fe..a0bc7c2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2085,7 +2085,7 @@
new ArrayMap<>();
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
- methodList, directBootAwareness, mSettings.getEnabledInputMethodNames());
+ methodList, directBootAwareness);
settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */);
}
// filter caller's access to input methods
@@ -2713,10 +2713,10 @@
}
@AnyThread
- void schedulePrepareStylusHandwritingDelegation(
+ void schedulePrepareStylusHandwritingDelegation(@UserIdInt int userId,
@NonNull String delegatePackageName, @NonNull String delegatorPackageName) {
mHandler.obtainMessage(
- MSG_PREPARE_HANDWRITING_DELEGATION,
+ MSG_PREPARE_HANDWRITING_DELEGATION, userId, 0 /* unused */,
new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget();
}
@@ -3433,7 +3433,8 @@
Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
throw new IllegalArgumentException("Delegator doesn't match Uid");
}
- schedulePrepareStylusHandwritingDelegation(delegatePackageName, delegatorPackageName);
+ schedulePrepareStylusHandwritingDelegation(
+ userId, delegatePackageName, delegatorPackageName);
}
@Override
@@ -3441,13 +3442,14 @@
@NonNull IInputMethodClient client,
@UserIdInt int userId,
@NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
if (!isStylusHandwritingEnabled(mContext, userId)) {
Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting"
+ " pref is disabled for user: " + userId);
return false;
}
- if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) {
+ if (!verifyDelegator(client, delegatePackageName, delegatorPackageName, flags)) {
return false;
}
@@ -3471,14 +3473,20 @@
private boolean verifyDelegator(
@NonNull IInputMethodClient client,
@NonNull String delegatePackageName,
- @NonNull String delegatorPackageName) {
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+ " startStylusHandwriting");
return false;
}
synchronized (ImfLock.class) {
- if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())) {
+ boolean homeDelegatorAllowed =
+ (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
+ != 0;
+ if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName())
+ && !(homeDelegatorAllowed
+ && mHwController.isDelegatorFromDefaultHomePackage())) {
Slog.w(TAG,
"Delegator package does not match. Ignoring startStylusHandwriting");
return false;
@@ -4200,7 +4208,7 @@
new ArrayMap<>();
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
- methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames());
+ methodList, DirectBootAwareness.AUTO);
final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap,
userId, false);
settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap,
@@ -4924,9 +4932,10 @@
}
case MSG_PREPARE_HANDWRITING_DELEGATION:
synchronized (ImfLock.class) {
+ int userId = msg.arg1;
String delegate = (String) ((Pair) msg.obj).first;
String delegator = (String) ((Pair) msg.obj).second;
- mHwController.prepareStylusHandwritingDelegation(delegate, delegator);
+ mHwController.prepareStylusHandwritingDelegation(userId, delegate, delegator);
}
return true;
case MSG_START_HANDWRITING:
@@ -5025,7 +5034,7 @@
static void queryInputMethodServicesInternal(Context context,
@UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
- @DirectBootAwareness int directBootAwareness, List<String> enabledInputMethodList) {
+ @DirectBootAwareness int directBootAwareness) {
final Context userAwareContext = context.getUserId() == userId
? context
: context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
@@ -5058,6 +5067,11 @@
methodList.ensureCapacity(services.size());
methodMap.ensureCapacity(services.size());
+ // Note: This is a temporary solution for Bug 261723412. If there is any better solution,
+ // we should remove this data dependency.
+ final List<String> enabledInputMethodList =
+ InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId);
+
filterInputMethodServices(additionalSubtypeMap, methodMap, methodList,
enabledInputMethodList, userAwareContext, services);
}
@@ -5124,8 +5138,7 @@
mMyPackageMonitor.clearKnownImePackageNamesLocked();
queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(),
- mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
+ mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO);
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
@@ -5438,47 +5451,21 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final String imeId = mSettings.getSelectedInputMethodForUser(userId);
- if (TextUtils.isEmpty(imeId)) {
- Slog.e(TAG, "No default input method found for userId " + userId);
- return null;
+ if (userId == mSettings.getCurrentUserId()) {
+ return mMethodMap.get(mSettings.getSelectedInputMethod());
}
- InputMethodInfo curInputMethodInfo;
- if (userId == mSettings.getCurrentUserId()
- && (curInputMethodInfo = mMethodMap.get(imeId)) != null) {
- // clone the InputMethodInfo before returning.
- return new InputMethodInfo(curInputMethodInfo);
- }
-
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- Context userAwareContext =
- mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
-
- final int flags = PackageManager.GET_META_DATA
- | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_AUTO;
- final List<ResolveInfo> services =
- userAwareContext.getPackageManager().queryIntentServicesAsUser(
- new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.ResolveInfoFlags.of(flags),
- userId);
- for (ResolveInfo ri : services) {
- final String imeIdResolved = InputMethodInfo.computeId(ri);
- if (imeId.equals(imeIdResolved)) {
- try {
- return new InputMethodInfo(
- userAwareContext, ri, additionalSubtypeMap.get(imeId));
- } catch (Exception e) {
- Slog.wtf(TAG, "Unable to load input method " + imeId, e);
- }
- }
- }
- // we didn't find the InputMethodInfo for imeId. This shouldn't happen.
- Slog.e(TAG, "Error while locating input method info for imeId: " + imeId);
- return null;
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
+ methodList, DirectBootAwareness.AUTO);
+ InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId,
+ true /* copyOnWrite */);
+ return methodMap.get(settings.getSelectedInputMethod());
}
+
private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
@@ -5486,8 +5473,7 @@
new ArrayMap<>();
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
+ methodMap, methodList, DirectBootAwareness.AUTO);
return methodMap;
}
@@ -6511,8 +6497,7 @@
new ArrayMap<>();
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList, DirectBootAwareness.AUTO,
- mSettings.getEnabledInputMethodNames());
+ methodMap, methodList, DirectBootAwareness.AUTO);
final InputMethodSettings settings = new InputMethodSettings(mContext,
methodMap, userId, false);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index c97d8e2..984ae1f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -330,17 +330,11 @@
@Nullable
private String getString(@NonNull String key, @Nullable String defaultValue) {
- return getStringForUser(key, defaultValue, mCurrentUserId);
- }
-
- @Nullable
- private String getStringForUser(
- @NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) {
final String result;
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
result = mCopyOnWriteDataStore.get(key);
} else {
- result = Settings.Secure.getStringForUser(mResolver, key, userId);
+ result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
}
return result != null ? result : defaultValue;
}
@@ -756,16 +750,6 @@
return imi;
}
- @Nullable
- String getSelectedInputMethodForUser(@UserIdInt int userId) {
- final String imi =
- getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
- if (DEBUG) {
- Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi);
- }
- return imi;
- }
-
void putDefaultVoiceInputMethod(String imeId) {
if (DEBUG) {
Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
@@ -1029,6 +1013,44 @@
}
/**
+ * Returns a list of enabled IME IDs to address Bug 261723412.
+ *
+ * <p>This is a temporary workaround until we come up with a better solution. Do not use this
+ * for anything other than Bug 261723412.</p>
+ *
+ * @param context {@link Context} object to query secure settings.
+ * @param userId User ID to query about.
+ * @return A list of enabled IME IDs.
+ */
+ @NonNull
+ static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context,
+ @UserIdInt int userId) {
+ final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
+ Settings.Secure.getStringForUser(
+ context.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS,
+ userId));
+ if (enabledInputMethodsStr == null) {
+ return List.of();
+ }
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ final ArrayList<String> result = new ArrayList<>();
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ // The first element is ime id.
+ result.add(subtypeSplitter.next());
+ }
+ }
+ return result;
+ }
+
+ /**
* Convert the input method ID to a component name
*
* @param id A unique ID for this input method.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 57e424d..49095ce 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -246,7 +246,7 @@
private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
private static final boolean FIX_UNLOCKED_DEVICE_REQUIRED_KEYS =
- android.security.Flags.fixUnlockedDeviceRequiredKeys();
+ android.security.Flags.fixUnlockedDeviceRequiredKeysV2();
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 27df00f..246d68d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -115,7 +115,9 @@
mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
mBluetoothRouteController =
new AudioPoliciesBluetoothRouteController(
- mContext, btAdapter, this::rebuildAvailableRoutes);
+ mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
+ // Just build routes but don't notify. The caller may not expect the listener to be invoked
+ // before this constructor has finished executing.
rebuildAvailableRoutes();
}
@@ -203,7 +205,7 @@
public synchronized boolean updateVolume(int volume) {
// TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We
// don't need to rebuild all available routes.
- rebuildAvailableRoutes();
+ rebuildAvailableRoutesAndNotify();
return true;
}
@@ -216,10 +218,15 @@
AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) {
if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) {
// We only care about the media usage. Ignore everything else.
- rebuildAvailableRoutes();
+ rebuildAvailableRoutesAndNotify();
}
}
+ private synchronized void rebuildAvailableRoutesAndNotify() {
+ rebuildAvailableRoutes();
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+ }
+
@RequiresPermission(
anyOf = {
Manifest.permission.MODIFY_AUDIO_ROUTING,
@@ -298,7 +305,6 @@
.map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute)
.forEach(
it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it));
- mOnDeviceRouteChangedListener.onDeviceRouteChanged();
}
private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() {
@@ -469,7 +475,7 @@
// of this, when the user connects a bluetooth device or a wired headset, the
// new device becomes the active route, which is the traditional behavior.
mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
- rebuildAvailableRoutes();
+ rebuildAvailableRoutesAndNotify();
break;
}
}
@@ -484,7 +490,7 @@
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
for (AudioDeviceInfo deviceInfo : removedDevices) {
if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
- rebuildAvailableRoutes();
+ rebuildAvailableRoutesAndNotify();
break;
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 448f215..ba2cf1d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2861,10 +2861,11 @@
? request.getRemovedInfo().mArgs : null;
if (args != null) {
if (!killApp) {
- // If we didn't kill the app, defer the deletion of code/resource files, since
- // they may still be in use by the running application. This mitigates problems
- // in cases where resources or code is loaded by a new Activity before
- // ApplicationInfo changes have propagated to all application threads.
+ // If we didn't kill the app, defer the deletion of code/resource files,
+ // since the old code/resource files may still be in use by the running
+ // application. This mitigates problems and cases where resources or
+ // code is loaded by a new Activity before ApplicationInfo changes have
+ // propagated to all application threads.
mPm.scheduleDeferredNoKillPostDelete(args);
} else {
mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 233bf4f..adb6906 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -523,6 +523,9 @@
private static final String PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE =
"is_update_ownership_enforcement_available";
+ private static final String PROPERTY_DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED =
+ "deferred_no_kill_post_delete_delay_ms_extended";
+
/**
* The default response for package verification timeout.
*
@@ -924,7 +927,11 @@
static final int WRITE_USER_PACKAGE_RESTRICTIONS = 30;
- static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
+ private static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
+
+ private static final long DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED =
+ TimeUnit.DAYS.toMillis(1);
+
private static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
private static final int DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS = 1000;
@@ -1288,7 +1295,19 @@
void scheduleDeferredNoKillPostDelete(InstallArgs args) {
Message message = mHandler.obtainMessage(DEFERRED_NO_KILL_POST_DELETE, args);
- mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_POST_DELETE_DELAY_MS);
+ // If the feature flag is on, retain the old files for a day. Otherwise, delete the old
+ // files after a few seconds.
+ long deleteDelayMillis = DEFERRED_NO_KILL_POST_DELETE_DELAY_MS;
+ if (Flags.improveInstallDontKill()) {
+ deleteDelayMillis = Binder.withCleanCallingIdentity(() -> {
+ return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ /* name= */ PROPERTY_DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED,
+ /* defaultValue= */ DEFERRED_NO_KILL_POST_DELETE_DELAY_MS_EXTENDED);
+ });
+ Slog.w(TAG, "Delaying the deletion of <" + args.getCodePath() + "> by "
+ + deleteDelayMillis + "ms or till the next reboot");
+ }
+ mHandler.sendMessageDelayed(message, deleteDelayMillis);
}
void schedulePruneUnusedStaticSharedLibraries(boolean delay) {
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 6d58d34..8adb566 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -23,10 +23,10 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.RecoverySystem;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -35,6 +35,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
import java.io.File;
import java.io.IOException;
@@ -43,7 +44,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Helper class for preparing and destroying user storage
@@ -65,31 +65,37 @@
/**
* Prepare storage areas for given user on all mounted devices.
*/
- void prepareUserData(int userId, int userSerial, int flags) {
+ void prepareUserData(UserInfo userInfo, int flags) {
synchronized (mInstallLock) {
final StorageManager storage = mContext.getSystemService(StorageManager.class);
/*
* Internal storage must be prepared before adoptable storage, since the user's volume
* keys are stored in their internal storage.
*/
- prepareUserDataLI(null /* internal storage */, userId, userSerial, flags, true);
+ prepareUserDataLI(null /* internal storage */, userInfo, flags, true);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
if (volumeUuid != null) {
- prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
+ prepareUserDataLI(volumeUuid, userInfo, flags, true);
}
}
}
}
- private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
+ private void prepareUserDataLI(String volumeUuid, UserInfo userInfo, int flags,
boolean allowRecover) {
- // Prepare storage and verify that serial numbers are consistent; if
- // there's a mismatch we need to destroy to avoid leaking data
+ final int userId = userInfo.id;
+ final int userSerial = userInfo.serialNumber;
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ final boolean isNewUser = userInfo.lastLoggedInTime == 0;
+ Slogf.d(TAG, "Preparing user data; volumeUuid=%s, userId=%d, flags=0x%x, isNewUser=%s",
+ volumeUuid, userId, flags, isNewUser);
try {
+ // Prepare CE and/or DE storage.
storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
+ // Ensure that the data directories of a removed user with the same ID are not being
+ // reused. New users must get fresh data directories, to avoid leaking data.
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
@@ -103,9 +109,10 @@
}
}
+ // Prepare the app data directories.
mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
- // CE storage is available after they are prepared.
+ // If applicable, record that the system user's CE storage has been prepared.
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 &&
(userId == UserHandle.USER_SYSTEM)) {
String propertyName = "sys.user." + userId + ".ce_available";
@@ -113,20 +120,31 @@
SystemProperties.set(propertyName, "true");
}
} catch (Exception e) {
- logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
- + " because we failed to prepare: " + e);
- destroyUserDataLI(volumeUuid, userId, flags);
-
+ // Failed to prepare user data. For new users, specifically users that haven't ever
+ // been unlocked, destroy the user data, and try again (if not already retried). This
+ // might be effective at resolving some errors, such as stale directories from a reused
+ // user ID. Don't auto-destroy data for existing users, since issues with existing
+ // users might be fixable via an OTA without having to wipe the user's data.
+ if (isNewUser) {
+ logCriticalInfo(Log.ERROR, "Destroying user " + userId + " on volume " + volumeUuid
+ + " because we failed to prepare: " + e);
+ destroyUserDataLI(volumeUuid, userId, flags);
+ } else {
+ logCriticalInfo(Log.ERROR, "Failed to prepare user " + userId + " on volume "
+ + volumeUuid + ": " + e);
+ }
if (allowRecover) {
// Try one last time; if we fail again we're really in trouble
- prepareUserDataLI(volumeUuid, userId, userSerial,
- flags | StorageManager.FLAG_STORAGE_DE, false);
+ prepareUserDataLI(volumeUuid, userInfo, flags | StorageManager.FLAG_STORAGE_DE,
+ false);
} else {
+ // If internal storage of the system user fails to prepare on first boot, then
+ // things are *really* broken, so we might as well reboot to recovery right away.
try {
Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
- if (userId == UserHandle.USER_SYSTEM) {
+ if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
RecoverySystem.rebootPromptAndWipeUserData(mContext,
- "prepareUserData failed for system user");
+ "failed to prepare internal storage for system user");
}
} catch (IOException e2) {
throw new RuntimeException("error rebooting into recovery", e2);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b89b4a2..1393121 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -5124,8 +5124,7 @@
// unlocked. We do this to ensure that CE storage isn't prepared before the CE key is
// saved to disk. This also matches what is done for user 0.
t.traceBegin("prepareUserData");
- mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
- StorageManager.FLAG_STORAGE_DE);
+ mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_DE);
t.traceEnd();
t.traceBegin("LSS.createNewUser");
@@ -6387,12 +6386,11 @@
}
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("onBeforeStartUser-" + userId);
- final int userSerial = userInfo.serialNumber;
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
userInfo.lastLoggedInFingerprint);
t.traceBegin("prepareUserData");
- mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE);
+ mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_DE);
t.traceEnd();
t.traceBegin("reconcileAppsData");
getPackageManagerInternal().reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE,
@@ -6418,14 +6416,13 @@
if (userInfo == null) {
return;
}
- final int userSerial = userInfo.serialNumber;
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !PackagePartitions.FINGERPRINT.equals(
userInfo.lastLoggedInFingerprint);
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("prepareUserData-" + userId);
- mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+ mUserDataPreparer.prepareUserData(userInfo, StorageManager.FLAG_STORAGE_CE);
t.traceEnd();
StorageManagerInternal smInternal = LocalServices.getService(StorageManagerInternal.class);
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index d0c346a..57f4a5d 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -337,7 +337,8 @@
0, // deprecated, used to be durationIncludingSleepMs
0, // optimizedPackagesCount
0, // packagesDependingOnBootClasspathCount
- 0); // totalPackagesCount
+ 0, // totalPackagesCount
+ ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__PASS__PASS_UNKNOWN);
}
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e817df8..30bce2f4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -22,6 +22,9 @@
import static android.app.AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.AppOpsManager.OP_TOAST_WINDOW;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
@@ -563,6 +566,7 @@
int mShortPressOnWindowBehavior;
int mPowerVolUpBehavior;
boolean mStylusButtonsEnabled = true;
+ boolean mKidsModeEnabled;
boolean mHasSoftInput = false;
boolean mUseTvRouting;
boolean mAllowStartActivityForLongPressOnPowerDuringSetup;
@@ -887,6 +891,9 @@
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.STYLUS_BUTTONS_ENABLED), false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.NAV_BAR_KIDS_MODE), false, this,
+ UserHandle.USER_ALL);
updateSettings();
}
@@ -2964,12 +2971,44 @@
mStylusButtonsEnabled = Settings.Secure.getIntForUser(resolver,
Secure.STYLUS_BUTTONS_ENABLED, 1, UserHandle.USER_CURRENT) == 1;
mInputManagerInternal.setStylusButtonMotionEventsEnabled(mStylusButtonsEnabled);
+
+ final boolean kidsModeEnabled = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
+ if (mKidsModeEnabled != kidsModeEnabled) {
+ mKidsModeEnabled = kidsModeEnabled;
+ updateKidsModeSettings();
+ }
}
if (updateRotation) {
updateRotation(true);
}
}
+ private void updateKidsModeSettings() {
+ if (mKidsModeEnabled) {
+ // Needed since many Kids apps aren't optimised to support both orientations and it
+ // will be hard for kids to understand the app compat mode.
+ // TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once
+ // possible.
+ if (mContext.getResources().getBoolean(R.bool.config_reverseDefaultRotation)) {
+ mWindowManagerInternal.setOrientationRequestPolicy(
+ true /* isIgnoreOrientationRequestDisabled */,
+ new int[]{SCREEN_ORIENTATION_LANDSCAPE,
+ SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
+ new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE});
+ } else {
+ mWindowManagerInternal.setOrientationRequestPolicy(
+ true /* isIgnoreOrientationRequestDisabled */,
+ null /* fromOrientations */, null /* toOrientations */);
+ }
+ } else {
+ mWindowManagerInternal.setOrientationRequestPolicy(
+ false /* isIgnoreOrientationRequestDisabled */,
+ null /* fromOrientations */, null /* toOrientations */);
+ }
+ }
+
private DreamManagerInternal getDreamManagerInternal() {
if (mDreamManagerInternal == null) {
// If mDreamManagerInternal is null, attempt to re-fetch it.
@@ -3433,7 +3472,7 @@
}
break;
case KeyEvent.KEYCODE_DEL:
- case KeyEvent.KEYCODE_GRAVE:
+ case KeyEvent.KEYCODE_ESCAPE:
if (firstDown && event.isMetaPressed()) {
logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
injectBackGesture(event.getDownTime());
@@ -3932,7 +3971,9 @@
}
return true;
case KeyEvent.KEYCODE_ESCAPE:
- if (down && repeatCount == 0) {
+ if (down
+ && KeyEvent.metaStateHasNoModifiers(metaState)
+ && repeatCount == 0) {
mContext.closeSystemDialogs();
}
return true;
@@ -6421,6 +6462,7 @@
pw.print(!mAllowLockscreenWhenOnDisplays.isEmpty());
pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
+ pw.print(prefix); pw.print("mKidsModeEnabled="); pw.println(mKidsModeEnabled);
mHapticFeedbackVibrationProvider.dump(prefix, pw);
mGlobalKeyManager.dump(prefix, pw);
diff --git a/services/core/java/com/android/server/power/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java
index fbad762..fa94b43 100644
--- a/services/core/java/com/android/server/power/LowPowerStandbyController.java
+++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java
@@ -33,6 +33,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -146,6 +147,7 @@
this::onStandbyTimeoutExpired;
private final LowPowerStandbyControllerInternal mLocalService = new LocalService();
private final SparseIntArray mUidAllowedReasons = new SparseIntArray();
+ private final List<String> mLowPowerStandbyManagingPackages = new ArrayList<>();
private final List<StandbyPortsLock> mStandbyPortLocks = new ArrayList<>();
@GuardedBy("mLock")
@@ -370,6 +372,14 @@
return;
}
+ List<PackageInfo> manageLowPowerStandbyPackages = mContext.getPackageManager()
+ .getPackagesHoldingPermissions(new String[]{
+ Manifest.permission.MANAGE_LOW_POWER_STANDBY
+ }, PackageManager.MATCH_SYSTEM_ONLY);
+ for (PackageInfo packageInfo : manageLowPowerStandbyPackages) {
+ mLowPowerStandbyManagingPackages.add(packageInfo.packageName);
+ }
+
mAlarmManager = mContext.getSystemService(AlarmManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -756,9 +766,7 @@
Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled);
}
- final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
}
@GuardedBy("mLock")
@@ -772,10 +780,7 @@
Slog.d(TAG, "notifyPolicyChanged, policy=" + policy);
}
- final Intent intent = new Intent(
- PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ sendExplicitBroadcast(PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED);
}
private void onStandbyTimeoutExpired() {
@@ -787,6 +792,22 @@
}
}
+ private void sendExplicitBroadcast(String intentType) {
+ final Intent intent = new Intent(intentType);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ // Send explicit broadcast to holders of MANAGE_LOW_POWER_STANDBY
+ final Intent privilegedIntent = new Intent(intentType);
+ privilegedIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ for (String packageName : mLowPowerStandbyManagingPackages) {
+ final Intent explicitIntent = new Intent(privilegedIntent);
+ explicitIntent.setPackage(packageName);
+ mContext.sendBroadcastAsUser(explicitIntent, UserHandle.ALL,
+ Manifest.permission.MANAGE_LOW_POWER_STANDBY);
+ }
+ }
+
@GuardedBy("mLock")
private void enqueueNotifyActiveChangedLocked() {
final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive);
@@ -1360,7 +1381,7 @@
}
final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
Manifest.permission.MANAGE_LOW_POWER_STANDBY);
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0491c14..b8d26d9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -286,6 +286,7 @@
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private boolean mPowerStatsCollectorEnabled;
public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
return mKernelMemoryStats;
@@ -601,6 +602,10 @@
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
@VisibleForTesting
public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
+ if (mPowerStatsCollectorEnabled) {
+ return;
+ }
+
ensureKernelSingleUidTimeReaderLocked();
final Uid u = getUidStatsLocked(uid);
@@ -653,8 +658,9 @@
*/
@SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
public void updateCpuTimesForAllUids() {
- if (mCpuPowerStatsCollector != null) {
+ if (mPowerStatsCollectorEnabled && mCpuPowerStatsCollector != null) {
mCpuPowerStatsCollector.schedule();
+ return;
}
synchronized (BatteryStatsImpl.this) {
@@ -712,7 +718,7 @@
@GuardedBy("this")
private void ensureKernelSingleUidTimeReaderLocked() {
- if (mKernelSingleUidTimeReader != null) {
+ if (mPowerStatsCollectorEnabled || mKernelSingleUidTimeReader != null) {
return;
}
@@ -8213,6 +8219,10 @@
@GuardedBy("mBsi")
private void ensureMultiStateCounters(long timestampMs) {
+ if (mBsi.mPowerStatsCollectorEnabled) {
+ throw new IllegalStateException("Multi-state counters used in streamlined mode");
+ }
+
if (mProcStateTimeMs == null) {
mProcStateTimeMs =
new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
@@ -10511,7 +10521,7 @@
mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
- if (mBsi.trackPerProcStateCpuTimes()) {
+ if (!mBsi.mPowerStatsCollectorEnabled && mBsi.trackPerProcStateCpuTimes()) {
mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs, uptimeMs);
LongArrayMultiStateCounter onBatteryCounter =
@@ -14211,7 +14221,7 @@
mCpuUidFreqTimeReader.onSystemReady();
}
if (mCpuPowerStatsCollector != null) {
- mCpuPowerStatsCollector.onSystemReady();
+ mCpuPowerStatsCollector.setEnabled(mPowerStatsCollectorEnabled);
}
mSystemReady = true;
}
@@ -15232,6 +15242,15 @@
return mCpuUidFreqTimeReader.isFastCpuTimesReader();
}
+ /**
+ * Enables or disables the PowerStatsCollector mode.
+ */
+ public void setPowerStatsCollectorEnabled(boolean enabled) {
+ synchronized (this) {
+ mPowerStatsCollectorEnabled = enabled;
+ }
+ }
+
@GuardedBy("this")
public void systemServicesReady(Context context) {
mConstants.startObserving(context.getContentResolver());
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index c05407c..4442845 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -35,7 +35,6 @@
import com.android.internal.os.PowerProfile;
import com.android.internal.os.PowerStats;
import com.android.server.LocalServices;
-import com.android.server.power.optimization.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -273,13 +272,6 @@
mDefaultCpuPowerBracketsPerEnergyConsumer = defaultCpuPowerBracketsPerEnergyConsumer;
}
- /**
- * Initializes the collector during the boot sequence.
- */
- public void onSystemReady() {
- setEnabled(Flags.streamlinedBatteryStats());
- }
-
private boolean ensureInitialized() {
if (mIsInitialized) {
return true;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index f82f08b..2bf7075 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -827,7 +827,7 @@
}
final boolean trusted;
- if (android.security.Flags.fixUnlockedDeviceRequiredKeys()) {
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
} else {
trusted = aggregateIsTrusted(id);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 75e6faf..6e9219a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5262,7 +5262,7 @@
boolean canAffectSystemUiFlags() {
return task != null && task.canAffectSystemUiFlags() && isVisible()
- && !inPinnedWindowingMode();
+ && !mWaitForEnteringPinnedMode && !inPinnedWindowingMode();
}
@Override
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index e6bbd52..c089d10 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -740,6 +740,8 @@
private final Handler mHandler;
private final String mName;
+ private boolean mInsetsAnimationRunning;
+
Host(Handler handler, String name) {
mHandler = handler;
mName = name;
@@ -841,5 +843,10 @@
public IBinder getWindowToken() {
return null;
}
+
+ @Override
+ public void notifyAnimationRunningStateChanged(boolean running) {
+ mInsetsAnimationRunning = running;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3a711b2..27cc2d6 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -1182,22 +1182,6 @@
}
}
- @Override
- public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
- @Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
- enforceTaskPermission("setOrientationRequestPolicy()");
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized (mGlobalLock) {
- mService.mWindowManager
- .setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
- fromOrientations, toOrientations);
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
public boolean handleInterceptBackPressedOnTaskRoot(Task task) {
if (!shouldInterceptBackPressedOnRootTask(task)) {
return false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index ae171a0..808a11d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -993,4 +993,18 @@
* @param displayId the id of display to check if there is a software navigation bar.
*/
public abstract boolean hasNavigationBar(int displayId);
+
+ /**
+ * Controls whether the app-requested screen orientation is always respected.
+ *
+ * @param respected If {@code true}, the app requested orientation is always respected.
+ * Otherwise, the system might ignore the request due to
+ * {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}.
+ * @param fromOrientations The orientations we want to map to the correspondent orientations
+ * in toOrientation.
+ * @param toOrientations The orientations we map to the ones in fromOrientations at the same
+ * index
+ */
+ public abstract void setOrientationRequestPolicy(boolean respected,
+ int[] fromOrientations, int[] toOrientations);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dd2b48b..0d2c94d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8523,6 +8523,15 @@
mImeTargetChangeListener = listener;
}
}
+
+ @Override
+ public void setOrientationRequestPolicy(boolean respected,
+ int[] fromOrientations, int[] toOrientations) {
+ synchronized (mGlobalLock) {
+ WindowManagerService.this.setOrientationRequestPolicy(respected,
+ fromOrientations, toOrientations);
+ }
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index afbe352..e5be4d9 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -21,6 +21,8 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -56,6 +58,7 @@
private static final int TEST_USER_SERIAL = 1000;
private static final int TEST_USER_ID = 10;
+ private static final UserInfo TEST_USER = new UserInfo();
private TestUserDataPreparer mUserDataPreparer;
@@ -72,6 +75,8 @@
@Before
public void setup() {
+ TEST_USER.id = TEST_USER_ID;
+ TEST_USER.serialNumber = TEST_USER_SERIAL;
Context ctx = InstrumentationRegistry.getContext();
FileUtils.deleteContents(ctx.getCacheDir());
mInstallLock = new Object();
@@ -92,8 +97,7 @@
userDeDir.mkdirs();
File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
systemDeDir.mkdirs();
- mUserDataPreparer
- .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_DE);
+ mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
@@ -110,8 +114,7 @@
userCeDir.mkdirs();
File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
systemCeDir.mkdirs();
- mUserDataPreparer
- .prepareUserData(TEST_USER_ID, TEST_USER_SERIAL, StorageManager.FLAG_STORAGE_CE);
+ mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
@@ -123,6 +126,28 @@
}
@Test
+ public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
+ TEST_USER.lastLoggedInTime = 0;
+ doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
+ .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+ eq(StorageManager.FLAG_STORAGE_CE));
+ mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
+ verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ eq(StorageManager.FLAG_STORAGE_CE));
+ }
+
+ @Test
+ public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
+ TEST_USER.lastLoggedInTime = System.currentTimeMillis();
+ doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
+ .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID), eq(TEST_USER_SERIAL),
+ eq(StorageManager.FLAG_STORAGE_CE));
+ mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
+ verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
+ eq(TEST_USER_ID), eq(StorageManager.FLAG_STORAGE_CE));
+ }
+
+ @Test
public void testDestroyUserData_De_DoesNotDestroyCe() throws Exception {
// Add file in CE storage
File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 9aa6136..6ba7368 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -262,8 +262,7 @@
Handler handler,
BrightnessClamperController.ClamperChangeListener clamperChangeListener,
BrightnessClamperController.DisplayDeviceData data,
- DisplayManagerFlags flags,
- Context context) {
+ DisplayManagerFlags flags, Context context) {
mCapturedChangeListener = clamperChangeListener;
return mClampers;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 21e3b34..b39cd04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3397,6 +3397,8 @@
bOptions.getTemporaryAppAllowlistType());
assertEquals(PowerExemptionManager.REASON_TIMEZONE_CHANGED,
bOptions.getTemporaryAppAllowlistReasonCode());
+ assertEquals(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT,
+ bOptions.getDeliveryGroupPolicy());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3364545..918bc5d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1665,7 +1665,8 @@
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
resultToSecond, null));
waitForIdle();
@@ -1681,6 +1682,11 @@
anyInt(), anyInt(), any());
// We deliver second broadcast to app
+ timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_BLUE);
+ inOrder.verify(blueThread).scheduleReceiver(
+ argThat(filterAndExtrasEquals(timezoneSecond)), any(), any(),
+ anyInt(), any(), any(), eq(true), eq(false), anyInt(),
+ anyInt(), anyInt(), any());
timezoneSecond.setClassName(PACKAGE_BLUE, CLASS_GREEN);
inOrder.verify(blueThread).scheduleReceiver(
argThat(filterAndExtrasEquals(timezoneSecond)), any(), any(),
@@ -1797,9 +1803,15 @@
waitForIdle();
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ if (mImpl == Impl.MODERN) {
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
+ } else {
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ }
}
@Test
@@ -1830,6 +1842,39 @@
}
@Test
+ public void testReplacePending_existingDiffReceivers() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 5))));
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverBlue, 10),
+ withPriority(receiverGreen, 5))));
+
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
+ if (mImpl == Impl.MODERN) {
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+ } else {
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ }
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
index 8e328ca..0e815d0 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/LowPowerStandbyControllerTest.java
@@ -49,8 +49,11 @@
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IForegroundServiceObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
@@ -68,7 +71,7 @@
import android.test.mock.MockContentResolver;
import android.util.ArraySet;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
@@ -85,9 +88,11 @@
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -145,7 +150,8 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext()));
+ mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry
+ .getInstrumentation().getTargetContext()));
when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock);
when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
@@ -395,26 +401,65 @@
setLowPowerStandbySupportedConfig(true);
mController.systemReady();
+ TestReceiver receiver = new TestReceiver();
+ mContextSpy.registerReceiver(receiver,
+ new IntentFilter(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED));
+
BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent(
PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
mController.setEnabled(false);
futureIntent.assertNotReceived();
+ assertThat(receiver.receivedCount).isEqualTo(0);
+ receiver.reset();
futureIntent = mContextSpy.nextBroadcastIntent(
PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
mController.setEnabled(true);
assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+ assertThat(receiver.receivedCount).isEqualTo(1);
+ receiver.reset();
futureIntent = mContextSpy.nextBroadcastIntent(
PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
mController.setEnabled(true);
futureIntent.assertNotReceived();
+ assertThat(receiver.receivedCount).isEqualTo(0);
+ receiver.reset();
futureIntent = mContextSpy.nextBroadcastIntent(
PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED);
-
mController.setEnabled(false);
assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull();
+ assertThat(receiver.receivedCount).isEqualTo(1);
+ receiver.reset();
+ }
+
+ @Test
+ public void testLowPowerStandbyEnabled_EnabledChangedExplicitBroadcastSent() throws Exception {
+ setLowPowerStandbySupportedConfig(true);
+ List<PackageInfo> packagesHoldingPermission = new ArrayList<>();
+
+ when(mPackageManagerMock.getPackagesHoldingPermissions(Mockito.any(),
+ Mockito.anyInt())).thenReturn(packagesHoldingPermission);
+
+ PackageInfo testInfo = new PackageInfo();
+ testInfo.packageName = mContextSpy.getPackageName();
+ packagesHoldingPermission.add(testInfo);
+ mController.systemReady();
+ TestReceiver receiver = new TestReceiver();
+ mContextSpy.registerReceiver(receiver,
+ new IntentFilter(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED));
+
+ mController.setEnabled(false);
+ assertThat(receiver.receivedCount).isEqualTo(0);
+ receiver.reset();
+
+ mController.setEnabled(true);
+ // Since we added a package manually to the packages that are allowed to
+ // manage LPS, the interceptor should have intercepted two broadcasts, one
+ // implicit via registration and one explicit to the package added above.
+ assertThat(receiver.receivedCount).isEqualTo(2);
+ receiver.reset();
}
@Test
@@ -906,4 +951,19 @@
LocalServices.removeServiceForTest(clazz);
LocalServices.addService(clazz, mock);
}
+
+ public static class TestReceiver extends BroadcastReceiver {
+ public int receivedCount = 0;
+
+ /**
+ * Resets the count of this receiver
+ */
+ public void reset() {
+ receivedCount = 0;
+ }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ receivedCount++;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c7d80ed..8933c6c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -23,6 +23,7 @@
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -31,6 +32,7 @@
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -219,6 +221,8 @@
.isEqualTo(cloneUserProperties.isCredentialShareableWithParent());
assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
+ compareDrawables(mUserManager.getUserBadge(),
+ Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
// Verify clone user parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
@@ -335,7 +339,8 @@
.isEqualTo(privateProfileUserProperties
.isAuthAlwaysRequiredToDisableQuietMode());
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
-
+ compareDrawables(mUserManager.getUserBadge(),
+ Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
// Verify private profile parent
assertThat(mUserManager.getProfileParent(mainUserId)).isNull();
UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id);
@@ -955,6 +960,8 @@
.isEqualTo(userTypeDetails.getBadgeNoBackground());
assertThat(mUserManager.getUserStatusBarIconResId(userId))
.isEqualTo(userTypeDetails.getStatusBarIcon());
+ compareDrawables(mUserManager.getUserBadge(),
+ Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
final int badgeIndex = userInfo.profileBadge;
assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
@@ -1762,4 +1769,10 @@
.getBoolean(com.android.internal.R.bool.config_isMainUserPermanentAdmin);
}
+ private void compareDrawables(Drawable actual, Drawable expected){
+ assertEquals(actual.getIntrinsicWidth(), expected.getIntrinsicWidth());
+ assertEquals(actual.getIntrinsicHeight(), expected.getIntrinsicHeight());
+ assertEquals(actual.getLevel(), expected.getLevel());
+ }
+
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 360fdf3..6853c4c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -30,13 +30,13 @@
import com.android.internal.annotations.Keep;
import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
@Presubmit
@MediumTest
@RunWith(JUnitParamsRunner.class)
@@ -44,6 +44,7 @@
private static final int VENDOR_ID = 0x123;
private static final int PRODUCT_ID = 0x456;
+ private static final int DEVICE_BUS = 0x789;
private static final int META_KEY = KeyEvent.KEYCODE_META_LEFT;
private static final int META_ON = MODIFIER.get(KeyEvent.KEYCODE_META_LEFT);
private static final int ALT_KEY = KeyEvent.KEYCODE_ALT_LEFT;
@@ -71,8 +72,8 @@
KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
{"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
KeyEvent.KEYCODE_BACK, 0},
- {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE},
- KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON},
+ {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
+ KeyboardLogEvent.BACK, KeyEvent.KEYCODE_ESCAPE, META_ON},
{"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
{"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
@@ -291,7 +292,7 @@
@Before
public void setUp() {
setUpPhoneWindowManager(/*supportSettingsUpdate*/ true);
- mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
+ mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID, DEVICE_BUS);
mPhoneWindowManager.overrideLaunchHome();
mPhoneWindowManager.overrideSearchKeyBehavior(
PhoneWindowManager.SEARCH_BEHAVIOR_TARGET_ACTIVITY);
@@ -311,7 +312,8 @@
int expectedKey, int expectedModifierState) {
sendKeyCombination(testKeys, 0 /* duration */);
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, "Failed while executing " + testName);
+ expectedKey, expectedModifierState, DEVICE_BUS,
+ "Failed while executing " + testName);
}
@Test
@@ -321,7 +323,8 @@
mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
sendLongPressKeyCombination(testKeys);
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, "Failed while executing " + testName);
+ expectedKey, expectedModifierState, DEVICE_BUS,
+ "Failed while executing " + testName);
}
@Test
@@ -333,7 +336,8 @@
sendKeyCombination(testKeys, 0 /* duration */);
sendKeyCombination(testKeys, 0 /* duration */);
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, "Failed while executing " + testName);
+ expectedKey, expectedModifierState, DEVICE_BUS,
+ "Failed while executing " + testName);
}
@Test
@@ -344,6 +348,7 @@
mPhoneWindowManager.overrideShortPressOnSettingsBehavior(shortPressOnSettingsBehavior);
sendKeyCombination(testKeys, 0 /* duration */);
mPhoneWindowManager.assertShortcutLogged(VENDOR_ID, PRODUCT_ID, expectedLogEvent,
- expectedKey, expectedModifierState, "Failed while executing " + testName);
+ expectedKey, expectedModifierState, DEVICE_BUS,
+ "Failed while executing " + testName);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7788b33..43c4745 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -483,10 +483,15 @@
doReturn(mPackageManager).when(mContext).getPackageManager();
}
- void overrideKeyEventSource(int vendorId, int productId) {
- InputDevice device = new InputDevice.Builder().setId(1).setVendorId(vendorId).setProductId(
- productId).setSources(InputDevice.SOURCE_KEYBOARD).setKeyboardType(
- InputDevice.KEYBOARD_TYPE_ALPHABETIC).build();
+ void overrideKeyEventSource(int vendorId, int productId, int deviceBus) {
+ InputDevice device = new InputDevice.Builder()
+ .setId(1)
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setDeviceBus(deviceBus)
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+ .build();
doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
doReturn(device).when(mInputManager).getInputDevice(anyInt());
}
@@ -682,11 +687,11 @@
}
void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
- int expectedKey, int expectedModifierState, String errorMsg) {
+ int expectedKey, int expectedModifierState, int deviceBus, String errorMsg) {
mTestLooper.dispatchAll();
verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
- expectedModifierState), description(errorMsg));
+ expectedModifierState, deviceBus), description(errorMsg));
}
void assertSwitchToRecent(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 5a43498..0608cf4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -362,6 +362,8 @@
// Ensure a task has moved over.
ensureTaskPlacement(task, activity);
assertTrue(task.inPinnedWindowingMode());
+ assertFalse("Entering PiP activity must not affect SysUiFlags",
+ activity.canAffectSystemUiFlags());
// The activity with fixed orientation should not apply letterbox when entering PiP.
final int requestedOrientation = task.getConfiguration().orientation
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index b098e82..dc8b5a1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -179,6 +179,31 @@
private final SparseArray<DetectorSession> mDetectorSessions =
new SparseArray<>();
+ /** Listens to changes to voice activation op. */
+ private final AppOpsManager.OnOpChangedListener mOnOpChangedListener =
+ new AppOpsManager.OnOpChangedListener() {
+ @Override
+ public void onOpChanged(String op, String packageName) {
+ if (op.equals(AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO)) {
+ AppOpsManager appOpsManager =
+ mContext.getSystemService(AppOpsManager.class);
+ synchronized (mLock) {
+ int checkOp = appOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ mVoiceInteractorIdentity.uid,
+ mVoiceInteractorIdentity.packageName);
+ // Voice activation app op disabled, safely shutdown hotword detection.
+ if (checkOp == AppOpsManager.MODE_ERRORED) {
+ Slog.i(TAG, "Shutdown hotword detection service on voice "
+ + "activation op disabled.");
+ safelyShutdownHotwordDetectionOnVoiceActivationDisabledLocked();
+ }
+ }
+ }
+ }
+ };
+
+
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
ComponentName visualQueryDetectionServiceName, int userId,
@@ -216,6 +241,10 @@
mLastRestartInstant = Instant.now();
+ AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+ appOpsManager.startWatchingMode(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+ mVoiceInteractorIdentity.packageName, mOnOpChangedListener);
+
if (mReStartPeriodSeconds <= 0) {
mCancellationTaskFuture = null;
} else {
@@ -299,7 +328,11 @@
}
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+ mAudioFlinger = null;
}
+ // Unregister the on op mode changed listener.
+ AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
+ appOpsManager.stopWatchingMode(mOnOpChangedListener);
}
@SuppressWarnings("GuardedBy")
@@ -553,6 +586,51 @@
}
}
+ /**
+ * Shutdowns down hotword detection service, swallowing exceptions.
+ *
+ * Called when voice activation app-op has been disabled.
+ */
+ @SuppressWarnings("GuardedBy")
+ void safelyShutdownHotwordDetectionOnVoiceActivationDisabledLocked() {
+ Slog.v(TAG, "safelyShutdownHotwordDetectionOnVoiceActivationDisabled");
+ try {
+ clearDebugHotwordLoggingTimeoutLocked();
+ mRemoteExceptionListener = null;
+ runForEachDetectorSessionLocked((session) -> {
+ if (!(session instanceof VisualQueryDetectorSession)) {
+ // Inform all detector sessions that they got destroyed due to voice activation
+ // op being disabled.
+ session.reportErrorLocked(
+ new HotwordDetectionServiceFailure(
+ HotwordDetectionServiceFailure
+ .ERROR_CODE_SHUTDOWN_HDS_ON_VOICE_ACTIVATION_OP_DISABLED,
+ "Shutdown hotword detection service on voice "
+ + "activation op disabled!"));
+ session.destroyLocked();
+ }
+ });
+
+ // Remove hotword detection sessions.
+ mDetectorSessions.delete(HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
+ mDetectorSessions.delete(HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
+
+ mDebugHotwordLogging = false;
+ unbindHotwordDetectionService();
+ if (mCancellationTaskFuture != null) {
+ mCancellationTaskFuture.cancel(/* mayInterruptIfRunning= */ true);
+ }
+ if (mAudioFlinger != null) {
+ mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+ mAudioFlinger = null;
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Swallowing error while shutting down hotword detection."
+ + "Error message: " + e.getMessage());
+ }
+ }
+
+
static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
private final HotwordDetectionConnection mHotwordDetectionConnection;
private final IHotwordRecognitionStatusCallback mExternalCallback;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ede4885..1e68687 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -252,6 +252,7 @@
*
* The default value is true.
*/
+ @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
public static final String KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL =
"additional_settings_caller_id_visibility_bool";
@@ -261,6 +262,7 @@
*
* The default value is true.
*/
+ @FlaggedApi(Flags.FLAG_SHOW_CALL_ID_AND_CALL_WAITING_IN_ADDITIONAL_SETTINGS_MENU)
public static final String KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL =
"additional_settings_call_waiting_visibility_bool";
@@ -3349,12 +3351,42 @@
/**
* Determines whether we should show a notification when the phone established a data
* connection in roaming network, to warn users about possible roaming charges.
+ *
+ * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+ * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
* @hide
*/
public static final String KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL =
"show_data_connected_roaming_notification";
/**
+ * Determines what MCCs are exceptions for the value of
+ * {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+ * An empty list indicates that there are no exceptions.
+ *
+ * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+ * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY
+ * @hide
+ */
+ public static final String
+ KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY =
+ "data_connected_roaming_notification_excluded_mccs_string_array";
+
+ /**
+ * Determines what MCC+MNCs are exceptions for the MCCs specified in
+ * {@link #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY}, meaning the
+ * value for the MCC+MNC is {@link #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL}.
+ * An empty list indicates that there are no MNC-specific exceptions.
+ *
+ * @see #KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL
+ * @see #KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY
+ * @hide
+ */
+ public static final String
+ KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY =
+ "data_connected_roaming_notification_included_mcc_mncs_string_array";
+
+ /**
* A list of 4 LTE RSRP thresholds above which a signal level is considered POOR,
* MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting.
*
@@ -10336,6 +10368,11 @@
sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_DATA_CONNECTED_ROAMING_NOTIFICATION_BOOL, false);
+ sDefaults.putStringArray(KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_EXCLUDED_MCCS_STRING_ARRAY,
+ new String[0]);
+ sDefaults.putStringArray(
+ KEY_DATA_CONNECTED_ROAMING_NOTIFICATION_INCLUDED_MCC_MNCS_STRING_ARRAY,
+ new String[0]);
sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
// Boundaries: [-140 dBm, -44 dBm]
new int[] {
diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java
index b528866..54ceaed 100644
--- a/telephony/java/android/telephony/ims/RegistrationManager.java
+++ b/telephony/java/android/telephony/ims/RegistrationManager.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +35,8 @@
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
+import com.android.internal.telephony.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
@@ -77,9 +80,11 @@
/** @hide */
@IntDef(prefix = {"SUGGESTED_ACTION_"},
value = {
- SUGGESTED_ACTION_NONE,
- SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
- SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT
+ SUGGESTED_ACTION_NONE,
+ SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
+ SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
+ SUGGESTED_ACTION_TRIGGER_RAT_BLOCK,
+ SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK
})
@Retention(RetentionPolicy.SOURCE)
public @interface SuggestedAction {}
@@ -109,6 +114,27 @@
@SystemApi
public static final int SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT = 2;
+ /**
+ * Indicates that the IMS registration on current RAT failed multiple times.
+ * The radio shall block the current RAT and search for other available RATs in the
+ * background. If no other RAT is available that meets the carrier requirements, the
+ * radio may remain on the current RAT for internet service. The radio clears all
+ * RATs marked as unavailable if the IMS service is registered to the carrier network.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
+ int SUGGESTED_ACTION_TRIGGER_RAT_BLOCK = 3;
+
+ /**
+ * Indicates that the radio clears all RATs marked as unavailable and tries to find
+ * an available RAT that meets the carrier requirements.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ADD_RAT_RELATED_SUGGESTED_ACTION_TO_IMS_REGISTRATION)
+ int SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCK = 4;
+
/**@hide*/
// Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN
// and WWAN are more accurate constants.
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 1e5f33f..60b5ce7 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -235,13 +235,17 @@
}
public int setFrameRate(float frameRate) {
+ return setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ }
+
+ public int setFrameRate(
+ float frameRate, @Surface.FrameRateCompatibility int compatibility) {
Log.i(TAG,
String.format("Setting frame rate for %s: frameRate=%.2f", mName, frameRate));
int rc = 0;
try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
- transaction.setFrameRate(
- mSurfaceControl, frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ transaction.setFrameRate(mSurfaceControl, frameRate, compatibility);
transaction.apply();
}
return rc;
@@ -668,12 +672,34 @@
}
}
- private void testSurfaceControlFrameRateCategoryInternal(int category)
- throws InterruptedException {
+ private void testSurfaceControlFrameRateCompatibilityInternal(
+ @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+ runOneSurfaceTest((TestSurface surface) -> {
+ Log.i(TAG,
+ "**** Running testSurfaceControlFrameRateCompatibility with compatibility "
+ + compatibility);
+
+ float expectedFrameRate = getExpectedFrameRateForCompatibility(compatibility);
+ int initialNumEvents = mModeChangedEvents.size();
+ surface.setFrameRate(30.f, compatibility);
+ verifyExactAndStableFrameRate(expectedFrameRate, surface);
+ verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
+ });
+ }
+
+ public void testSurfaceControlFrameRateCompatibility(
+ @Surface.FrameRateCompatibility int compatibility) throws InterruptedException {
+ runTestsWithPreconditions(
+ () -> testSurfaceControlFrameRateCompatibilityInternal(compatibility),
+ "frame rate compatibility=" + compatibility);
+ }
+
+ private void testSurfaceControlFrameRateCategoryInternal(
+ @Surface.FrameRateCategory int category) throws InterruptedException {
runOneSurfaceTest((TestSurface surface) -> {
Log.i(TAG, "**** Running testSurfaceControlFrameRateCategory for category " + category);
- float expectedFrameRate = getExpectedFrameRate(category);
+ float expectedFrameRate = getExpectedFrameRateForCategory(category);
int initialNumEvents = mModeChangedEvents.size();
surface.setFrameRateCategory(category);
verifyCompatibleAndStableFrameRate(expectedFrameRate, surface);
@@ -681,7 +707,8 @@
});
}
- public void testSurfaceControlFrameRateCategory(int category) throws InterruptedException {
+ public void testSurfaceControlFrameRateCategory(@Surface.FrameRateCategory int category)
+ throws InterruptedException {
runTestsWithPreconditions(()
-> testSurfaceControlFrameRateCategoryInternal(category),
"frame rate category=" + category);
@@ -744,7 +771,24 @@
"frame rate strategy=" + parentStrategy);
}
- private float getExpectedFrameRate(int category) {
+ private float getExpectedFrameRateForCompatibility(int compatibility) {
+ assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED for compatibility "
+ + compatibility,
+ compatibility == Surface.FRAME_RATE_COMPATIBILITY_GTE);
+
+ Display display = getDisplay();
+ Optional<Float> expectedFrameRate = getRefreshRates(display.getMode(), display)
+ .stream()
+ .filter(rate -> rate >= 30.f)
+ .min(Comparator.naturalOrder());
+
+ assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
+ + "is >= 30",
+ expectedFrameRate.isPresent());
+ return expectedFrameRate.get();
+ }
+
+ private float getExpectedFrameRateForCategory(int category) {
Display display = getDisplay();
List<Float> frameRates = getRefreshRates(display.getMode(), display);
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 29f6879..4b56c10 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -81,6 +81,12 @@
}
@Test
+ public void testSurfaceControlFrameRateCompatibilityGte() throws InterruptedException {
+ GraphicsActivity activity = mActivityRule.getActivity();
+ activity.testSurfaceControlFrameRateCompatibility(Surface.FRAME_RATE_COMPATIBILITY_GTE);
+ }
+
+ @Test
public void testSurfaceControlFrameRateCategoryHigh() throws InterruptedException {
GraphicsActivity activity = mActivityRule.getActivity();
activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_HIGH);
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 44de6a6..9c33576 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -63,6 +63,7 @@
deviceId: Int,
vendorId: Int,
productId: Int,
+ deviceBus: Int,
languageTag: String,
layoutType: String
): InputDevice =
@@ -75,6 +76,7 @@
.setExternal(true)
.setVendorId(vendorId)
.setProductId(productId)
+ .setDeviceBus(deviceBus)
.setKeyboardLanguageTag(languageTag)
.setKeyboardLayoutType(layoutType)
.build()
@@ -94,6 +96,7 @@
const val ENGLISH_QWERTY_DEVICE_ID = 4
const val DEFAULT_VENDOR_ID = 123
const val DEFAULT_PRODUCT_ID = 456
+ const val DEFAULT_DEVICE_BUS = 789
const val USER_ID = 4
const val IME_ID = "ime_id"
const val PACKAGE_NAME = "KeyboardLayoutManagerTests"
@@ -177,12 +180,14 @@
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
- keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, "", "")
- vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1, "", "")
+ keyboardDevice = createKeyboard(DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID,
+ DEFAULT_DEVICE_BUS, "", "")
+ vendorSpecificKeyboardDevice = createKeyboard(VENDOR_SPECIFIC_DEVICE_ID, 1, 1,
+ 1, "", "")
englishDvorakKeyboardDevice = createKeyboard(ENGLISH_DVORAK_DEVICE_ID, DEFAULT_VENDOR_ID,
- DEFAULT_PRODUCT_ID, "en", "dvorak")
+ DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "dvorak")
englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID,
- DEFAULT_PRODUCT_ID, "en", "qwerty")
+ DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "qwerty")
Mockito.`when`(iInputManager.inputDeviceIds)
.thenReturn(intArrayOf(
DEVICE_ID,
@@ -861,7 +866,9 @@
GERMAN_LAYOUT_NAME,
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"de-Latn",
- LAYOUT_TYPE_QWERTZ))
+ LAYOUT_TYPE_QWERTZ),
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
}
@@ -887,7 +894,8 @@
ENGLISH_US_LAYOUT_NAME,
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
"de-Latn",
- LAYOUT_TYPE_QWERTZ))
+ LAYOUT_TYPE_QWERTZ)),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
}
@@ -911,7 +919,9 @@
"Default",
KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT))
+ LAYOUT_TYPE_DEFAULT),
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
}
@@ -929,7 +939,8 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.anyInt(),
ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(ByteArray::class.java)
+ ArgumentMatchers.any(ByteArray::class.java),
+ ArgumentMatchers.anyInt(),
)
}, Mockito.times(0))
}
@@ -972,8 +983,13 @@
}
private fun createByteArray(
- expectedLanguageTag: String, expectedLayoutType: Int, expectedLayoutName: String,
- expectedCriteria: Int, expectedImeLanguageTag: String, expectedImeLayoutType: Int): ByteArray {
+ expectedLanguageTag: String,
+ expectedLayoutType: Int,
+ expectedLayoutName: String,
+ expectedCriteria: Int,
+ expectedImeLanguageTag: String,
+ expectedImeLayoutType: Int
+ ): ByteArray {
val proto = ProtoOutputStream()
val keyboardLayoutConfigToken = proto.start(
KeyboardConfiguredProto.RepeatedKeyboardLayoutConfig.KEYBOARD_LAYOUT_CONFIG)
@@ -1001,7 +1017,7 @@
KeyboardConfiguredProto.KeyboardLayoutConfig.IME_LAYOUT_TYPE,
expectedImeLayoutType
)
- proto.end(keyboardLayoutConfigToken);
+ proto.end(keyboardLayoutConfigToken)
return proto.bytes
}
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 33ff09b..89a47b9 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -30,6 +30,7 @@
deviceId: Int,
vendorId: Int,
productId: Int,
+ deviceBus: Int,
languageTag: String?,
layoutType: String?
): InputDevice =
@@ -42,6 +43,7 @@
.setExternal(true)
.setVendorId(vendorId)
.setProductId(productId)
+ .setDeviceBus(deviceBus)
.setKeyboardLanguageTag(languageTag)
.setKeyboardLayoutType(layoutType)
.build()
@@ -67,6 +69,7 @@
const val DEVICE_ID = 1
const val DEFAULT_VENDOR_ID = 123
const val DEFAULT_PRODUCT_ID = 456
+ const val DEFAULT_DEVICE_BUS = 789
}
@Test
@@ -77,6 +80,7 @@
DEVICE_ID,
DEFAULT_VENDOR_ID,
DEFAULT_PRODUCT_ID,
+ DEFAULT_DEVICE_BUS,
null,
null
)
@@ -92,6 +96,7 @@
DEVICE_ID,
DEFAULT_VENDOR_ID,
DEFAULT_PRODUCT_ID,
+ DEFAULT_DEVICE_BUS,
null,
null
)
@@ -107,6 +112,7 @@
DEVICE_ID,
DEFAULT_VENDOR_ID,
DEFAULT_PRODUCT_ID,
+ DEFAULT_DEVICE_BUS,
"de-CH",
"qwertz"
)
@@ -135,6 +141,11 @@
DEFAULT_PRODUCT_ID,
event.productId
)
+ assertEquals(
+ "KeyboardConfigurationEvent should pick device bus from provided InputDevice",
+ DEFAULT_DEVICE_BUS,
+ event.deviceBus
+ )
assertTrue(event.isFirstConfiguration)
assertEquals(
@@ -178,6 +189,7 @@
DEVICE_ID,
DEFAULT_VENDOR_ID,
DEFAULT_PRODUCT_ID,
+ DEFAULT_DEVICE_BUS,
"und", // Undefined language tag
"azerty"
)
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index 1dfd5c0..d0e5626 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -93,7 +93,7 @@
}
@Test
- @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
fun grantCannotActivelyUnlockDevice() {
// On automotive, trust agents can actively unlock the device.
assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
@@ -120,7 +120,7 @@
}
@Test
- @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+ @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
// On automotive, trust agents can actively unlock the device.
assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index 5a8f828..0121809 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -36,7 +36,8 @@
class LockStateTrackingRule : TestRule {
private val context: Context = getApplicationContext()
private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
- private val keyguardManager = context.getSystemService(KeyguardManager::class.java) as KeyguardManager
+ private val keyguardManager =
+ context.getSystemService(KeyguardManager::class.java) as KeyguardManager
@Volatile lateinit var trustState: TrustState
private set
@@ -63,7 +64,7 @@
wait("not trusted") { trustState.trusted == false }
}
- // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS
+ // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2
fun assertUnlockedButNotReally() {
wait("device unlocked") { !keyguardManager.isDeviceLocked }
wait("not trusted") { trustState.trusted == false }
@@ -87,7 +88,7 @@
trustGrantedMessages: MutableList<String>
) {
Log.d(TAG, "Device became trusted=$enabled")
- trustState = trustState.copy(trusted=enabled)
+ trustState = trustState.copy(trusted = enabled)
}
}
diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
index 91e6814..d97dd7c 100755
--- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
+++ b/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh
@@ -235,10 +235,6 @@
"Duplicate or conflicting argument found: --in-jar" \
""
-EXTRA_ARGS="--quiet" run_hoststubgen_for_failure "Conflicting arg" \
- "Duplicate or conflicting argument found: --quiet" \
- ""
-
echo "All tests passed"
exit 0
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index dbcf3a5..4e0cd09 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -239,7 +239,7 @@
errors: HostStubGenErrors,
) {
log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
- log.i("Checker is %s", if (enableChecker) "enabled" else "disabled")
+ log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
val start = System.currentTimeMillis()
@@ -264,7 +264,7 @@
}
}
val end = System.currentTimeMillis()
- log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
+ log.i("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
}
private fun <T> maybeWithZipOutputStream(filename: String?, block: (ZipOutputStream?) -> T): T {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index 5e71a36..18065ba 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -15,10 +15,12 @@
*/
package com.android.hoststubgen
-import java.io.OutputStream
-import java.io.PrintStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.PrintWriter
+import java.io.Writer
-val log: HostStubGenLogger = HostStubGenLogger()
+val log: HostStubGenLogger = HostStubGenLogger().setConsoleLogLevel(LogLevel.Info)
/** Logging level */
enum class LogLevel {
@@ -30,15 +32,13 @@
Debug,
}
-/** Simple logging class. */
-class HostStubGenLogger(
- private var out: PrintStream = System.out!!,
- var level: LogLevel = LogLevel.Info,
-) {
- companion object {
- private val sNullPrintStream: PrintStream = PrintStream(OutputStream.nullOutputStream())
- }
-
+/**
+ * Simple logging class.
+ *
+ * By default, it has no printers set. Use [setConsoleLogLevel] or [addFilePrinter] to actually
+ * write log.
+ */
+class HostStubGenLogger {
private var indentLevel: Int = 0
get() = field
set(value) {
@@ -47,6 +47,56 @@
}
private var indent: String = ""
+ private val printers: MutableList<LogPrinter> = mutableListOf()
+
+ private var consolePrinter: LogPrinter? = null
+
+ private var maxLogLevel = LogLevel.None
+
+ private fun updateMaxLogLevel() {
+ maxLogLevel = LogLevel.None
+
+ printers.forEach {
+ if (maxLogLevel < it.logLevel) {
+ maxLogLevel = it.logLevel
+ }
+ }
+ }
+
+ private fun addPrinter(printer: LogPrinter) {
+ printers.add(printer)
+ updateMaxLogLevel()
+ }
+
+ private fun removePrinter(printer: LogPrinter) {
+ printers.remove(printer)
+ updateMaxLogLevel()
+ }
+
+ fun setConsoleLogLevel(level: LogLevel): HostStubGenLogger {
+ // If there's already a console log printer set, remove it, and then add a new one
+ consolePrinter?.let {
+ removePrinter(it)
+ }
+ val cp = StreamPrinter(level, PrintWriter(System.out))
+ addPrinter(cp)
+ consolePrinter = cp
+
+ return this
+ }
+
+ fun addFilePrinter(level: LogLevel, logFilename: String): HostStubGenLogger {
+ addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream(
+ FileOutputStream(logFilename)))))
+
+ return this
+ }
+
+ /** Flush all the printers */
+ fun flush() {
+ printers.forEach { it.flush() }
+ }
+
fun indent() {
indentLevel++
}
@@ -68,92 +118,71 @@
}
fun isEnabled(level: LogLevel): Boolean {
- return level.ordinal <= this.level.ordinal
+ return level.ordinal <= maxLogLevel.ordinal
}
- private fun println(message: String) {
- out.print(indent)
- out.println(message)
+ private fun println(level: LogLevel, message: String) {
+ printers.forEach {
+ if (it.logLevel.ordinal >= level.ordinal) {
+ it.println(level, indent, message)
+ }
+ }
+ }
+
+ private fun println(level: LogLevel, format: String, vararg args: Any?) {
+ if (isEnabled(level)) {
+ println(level, String.format(format, *args))
+ }
}
/** Log an error. */
fun e(message: String) {
- if (level.ordinal < LogLevel.Error.ordinal) {
- return
- }
- println(message)
+ println(LogLevel.Error, message)
}
/** Log an error. */
fun e(format: String, vararg args: Any?) {
- if (level.ordinal < LogLevel.Error.ordinal) {
- return
- }
- e(String.format(format, *args))
+ println(LogLevel.Error, format, *args)
}
/** Log a warning. */
fun w(message: String) {
- if (level.ordinal < LogLevel.Warn.ordinal) {
- return
- }
- println(message)
+ println(LogLevel.Warn, message)
}
/** Log a warning. */
fun w(format: String, vararg args: Any?) {
- if (level.ordinal < LogLevel.Warn.ordinal) {
- return
- }
- w(String.format(format, *args))
+ println(LogLevel.Warn, format, *args)
}
/** Log an info message. */
fun i(message: String) {
- if (level.ordinal < LogLevel.Info.ordinal) {
- return
- }
- println(message)
+ println(LogLevel.Info, message)
}
- /** Log a debug message. */
+ /** Log an info message. */
fun i(format: String, vararg args: Any?) {
- if (level.ordinal < LogLevel.Warn.ordinal) {
- return
- }
- i(String.format(format, *args))
+ println(LogLevel.Info, format, *args)
}
/** Log a verbose message. */
fun v(message: String) {
- if (level.ordinal < LogLevel.Verbose.ordinal) {
- return
- }
- println(message)
+ println(LogLevel.Verbose, message)
}
/** Log a verbose message. */
fun v(format: String, vararg args: Any?) {
- if (level.ordinal < LogLevel.Verbose.ordinal) {
- return
- }
- v(String.format(format, *args))
+ println(LogLevel.Verbose, format, *args)
}
/** Log a debug message. */
fun d(message: String) {
- if (level.ordinal < LogLevel.Debug.ordinal) {
- return
- }
- println(message)
+ println(LogLevel.Debug, message)
}
/** Log a debug message. */
fun d(format: String, vararg args: Any?) {
- if (level.ordinal < LogLevel.Warn.ordinal) {
- return
- }
- d(String.format(format, *args))
+ println(LogLevel.Debug, format, *args)
}
inline fun forVerbose(block: () -> Unit) {
@@ -168,31 +197,65 @@
}
}
- /** Return a stream for error. */
- fun getErrorPrintStream(): PrintStream {
- if (level.ordinal < LogLevel.Error.ordinal) {
- return sNullPrintStream
- }
-
- // TODO Apply indent
- return PrintStream(out)
+ /** Return a Writer for a given log level. */
+ fun getWriter(level: LogLevel): Writer {
+ return MultiplexingWriter(level)
}
- /** Return a stream for verbose messages. */
- fun getVerbosePrintStream(): PrintStream {
- if (level.ordinal < LogLevel.Verbose.ordinal) {
- return sNullPrintStream
+ private inner class MultiplexingWriter(val level: LogLevel) : Writer() {
+ private inline fun forPrinters(callback: (LogPrinter) -> Unit) {
+ printers.forEach {
+ if (it.logLevel.ordinal >= level.ordinal) {
+ callback(it)
+ }
+ }
}
- // TODO Apply indent
- return PrintStream(out)
+
+ override fun close() {
+ flush()
+ }
+
+ override fun flush() {
+ forPrinters {
+ it.flush()
+ }
+ }
+
+ override fun write(cbuf: CharArray, off: Int, len: Int) {
+ // TODO Apply indent
+ forPrinters {
+ it.write(cbuf, off, len)
+ }
+ }
+ }
+}
+
+private interface LogPrinter {
+ val logLevel: LogLevel
+
+ fun println(logLevel: LogLevel, indent: String, message: String)
+
+ // TODO: This should be removed once MultiplexingWriter starts applying indent, at which point
+ // println() should be used instead.
+ fun write(cbuf: CharArray, off: Int, len: Int)
+
+ fun flush()
+}
+
+private class StreamPrinter(
+ override val logLevel: LogLevel,
+ val out: PrintWriter,
+) : LogPrinter {
+ override fun println(logLevel: LogLevel, indent: String, message: String) {
+ out.print(indent)
+ out.println(message)
}
- /** Return a stream for debug messages. */
- fun getInfoPrintStream(): PrintStream {
- if (level.ordinal < LogLevel.Info.ordinal) {
- return sNullPrintStream
- }
- // TODO Apply indent
- return PrintStream(out)
+ override fun write(cbuf: CharArray, off: Int, len: Int) {
+ out.write(cbuf, off, len)
}
-}
\ No newline at end of file
+
+ override fun flush() {
+ out.flush()
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 0ae52af..d2ead18 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -101,9 +101,7 @@
var defaultPolicy: SetOnce<FilterPolicy> = SetOnce(FilterPolicy.Remove),
- var logLevel: SetOnce<LogLevel> = SetOnce(LogLevel.Info),
-
- var cleanUpOnError: SetOnce<Boolean> = SetOnce(true),
+ var cleanUpOnError: SetOnce<Boolean> = SetOnce(false),
var enableClassChecker: SetOnce<Boolean> = SetOnce(false),
var enablePreTrace: SetOnce<Boolean> = SetOnce(false),
@@ -143,6 +141,11 @@
return name
}
+ fun setLogFile(level: LogLevel, filename: String) {
+ log.addFilePrinter(level, filename)
+ log.i("$level log file: $filename")
+ }
+
while (true) {
val arg = ai.nextArgOptional()
if (arg == null) {
@@ -161,9 +164,9 @@
// TODO: Write help
"-h", "--help" -> TODO("Help is not implemented yet")
- "-v", "--verbose" -> ret.logLevel.set(LogLevel.Verbose)
- "-d", "--debug" -> ret.logLevel.set(LogLevel.Debug)
- "-q", "--quiet" -> ret.logLevel.set(LogLevel.None)
+ "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose)
+ "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug)
+ "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None)
"--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
"--out-stub-jar" -> ret.outStubJar.setNextStringArg()
@@ -211,7 +214,7 @@
ret.keepStaticInitializerAnnotations.addUniqueAnnotationArg()
"--package-redirect" ->
- ret.packageRedirects += parsePackageRedirect(ai.nextArgRequired(arg))
+ ret.packageRedirects += parsePackageRedirect(nextArg())
"--annotation-allowed-classes-file" ->
ret.annotationAllowedClassesFile.setNextStringArg()
@@ -246,13 +249,15 @@
"--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
+ "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
+ "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
+
else -> throw ArgumentsException("Unknown option: $arg")
}
} catch (e: SetOnce.SetMoreThanOnceException) {
throw ArgumentsException("Duplicate or conflicting argument found: $arg")
}
}
- log.w(ret.toString())
if (!ret.inJar.isSet) {
throw ArgumentsException("Required option missing: --in-jar")
@@ -377,7 +382,6 @@
intersectStubJars=$intersectStubJars,
policyOverrideFile=$policyOverrideFile,
defaultPolicy=$defaultPolicy,
- logLevel=$logLevel,
cleanUpOnError=$cleanUpOnError,
enableClassChecker=$enableClassChecker,
enablePreTrace=$enablePreTrace,
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
index 38ba0cc..4882c00 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
@@ -17,6 +17,8 @@
package com.android.hoststubgen
+import java.io.PrintWriter
+
const val COMMAND_NAME = "HostStubGen"
/**
@@ -25,13 +27,12 @@
fun main(args: Array<String>) {
var success = false
var clanupOnError = false
+
try {
// Parse the command line arguments.
val options = HostStubGenOptions.parseArgs(args)
clanupOnError = options.cleanUpOnError.get
- log.level = options.logLevel.get
-
log.v("HostStubGen started")
log.v("Options: $options")
@@ -39,17 +40,18 @@
HostStubGen(options).run()
success = true
- } catch (e: Exception) {
+ } catch (e: Throwable) {
log.e("$COMMAND_NAME: Error: ${e.message}")
if (e !is UserErrorException) {
- e.printStackTrace(log.getErrorPrintStream())
+ e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
}
if (clanupOnError) {
- TODO("clanupOnError is not implemented yet")
+ TODO("Remove output jars here")
}
+ } finally {
+ log.i("$COMMAND_NAME finished")
+ log.flush()
}
- log.v("HostStubGen finished")
-
System.exit(if (success) 0 else 1 )
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index f25e862..96e4a3f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -16,6 +16,7 @@
package com.android.hoststubgen.visitors
import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.LogLevel
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.getPackageNameFromClassName
import com.android.hoststubgen.asm.resolveClassName
@@ -229,7 +230,7 @@
): ClassVisitor {
var next = nextVisitor
- val verbosePrinter = PrintWriter(log.getVerbosePrintStream())
+ val verbosePrinter = PrintWriter(log.getWriter(LogLevel.Verbose))
// Inject TraceClassVisitor for debugging.
if (options.enablePostTrace) {