Merge "Clean up hoststubgen README files" into main
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 e57efc3b..5e7a383 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -43136,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";
@@ -52054,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();
@@ -52427,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);
@@ -55682,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();
@@ -55729,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
@@ -59546,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 75a90ab..847edd1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12788,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
}
@@ -16141,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/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/FileUtils.java b/core/java/android/os/FileUtils.java
index 5b24dca..3a32b2b 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -52,9 +52,11 @@
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.system.StructStat;
import android.text.TextUtils;
import android.util.DataUnit;
+import android.util.EmptyArray;
import android.util.Log;
import android.util.Slog;
import android.webkit.MimeTypeMap;
@@ -64,7 +66,6 @@
import com.android.internal.util.SizedInputStream;
import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
@@ -94,6 +95,7 @@
/**
* Utility methods useful for working with files.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class FileUtils {
private static final String TAG = "FileUtils";
@@ -116,9 +118,6 @@
private FileUtils() {
}
- private static final String CAMERA_DIR_LOWER_CASE = "/storage/emulated/" + UserHandle.myUserId()
- + "/dcim/camera";
-
/** Regular expression for safe filenames: no spaces or metacharacters.
*
* Use a preload holder so that FileUtils can be compile-time initialized.
@@ -133,6 +132,21 @@
private static final long COPY_CHECKPOINT_BYTES = 524288;
+ static {
+ sEnableCopyOptimizations = shouldEnableCopyOptimizations();
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static boolean shouldEnableCopyOptimizations() {
+ // Advanced copy operations enabled by default
+ return true;
+ }
+
+ private static boolean shouldEnableCopyOptimizations$ravenwood() {
+ // Disabled under Ravenwood due to missing kernel support
+ return false;
+ }
+
/**
* Listener that is called periodically as progress is made.
*/
@@ -150,6 +164,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static int setPermissions(File path, int mode, int uid, int gid) {
return setPermissions(path.getAbsolutePath(), mode, uid, gid);
}
@@ -164,6 +179,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
Os.chmod(path, mode);
@@ -194,6 +210,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
try {
Os.fchmod(fd, mode);
@@ -221,6 +238,7 @@
* @param to File where attributes should be copied to.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
try {
final StructStat stat = Os.stat(from.getAbsolutePath());
@@ -236,6 +254,7 @@
* @hide
*/
@Deprecated
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static int getUid(String path) {
try {
return Os.stat(path).st_uid;
@@ -314,11 +333,7 @@
}
try (FileOutputStream out = new FileOutputStream(destFile)) {
copy(in, out);
- try {
- Os.fsync(out.getFD());
- } catch (ErrnoException e) {
- throw e.rethrowAsIOException();
- }
+ sync(out);
}
}
@@ -475,6 +490,7 @@
* @hide
*/
@VisibleForTesting
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
@@ -516,6 +532,7 @@
* @hide
*/
@VisibleForTesting
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
@@ -699,6 +716,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static void bytesToFile(String filename, byte[] content) throws IOException {
if (filename.startsWith("/proc/")) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -714,6 +732,14 @@
}
}
+ /** @hide */
+ public static void bytesToFile$ravenwood(String filename, byte[] content) throws IOException {
+ // No StrictMode support, so we can just directly write
+ try (FileOutputStream fos = new FileOutputStream(filename)) {
+ fos.write(content);
+ }
+ }
+
/**
* Writes string to file. Basically same as "echo -n $string > $filename"
*
@@ -1176,6 +1202,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = MimeTypeMap.class)
public static String[] splitFileName(String mimeType, String displayName) {
String name;
String ext;
@@ -1423,11 +1450,13 @@
* indicate a failure to flush bytes to the underlying resource.
*/
@Deprecated
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires ART support")
public static void closeQuietly(@Nullable FileDescriptor fd) {
IoUtils.closeQuietly(fd);
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModeStringToPosix(String mode) {
// Quick check for invalid chars
for (int i = 0; i < mode.length(); i++) {
@@ -1462,6 +1491,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static String translateModePosixToString(int mode) {
String res = "";
if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1483,6 +1513,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModePosixToPfd(int mode) {
int res = 0;
if ((mode & O_ACCMODE) == O_RDWR) {
@@ -1507,6 +1538,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModePfdToPosix(int mode) {
int res = 0;
if ((mode & MODE_READ_WRITE) == MODE_READ_WRITE) {
@@ -1531,6 +1563,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = OsConstants.class)
public static int translateModeAccessToPosix(int mode) {
if (mode == F_OK) {
// There's not an exact mapping, so we attempt a read-only open to
@@ -1549,6 +1582,7 @@
/** {@hide} */
@VisibleForTesting
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static ParcelFileDescriptor convertToModernFd(FileDescriptor fd) {
Context context = AppGlobals.getInitialApplication();
if (UserHandle.getAppId(Process.myUid()) == getMediaProviderAppId(context)) {
@@ -1565,6 +1599,7 @@
}
}
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
private static int getMediaProviderAppId(Context context) {
if (sMediaProviderAppId != -1) {
return sMediaProviderAppId;
@@ -1605,10 +1640,12 @@
return this;
}
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static MemoryPipe createSource(byte[] data) throws IOException {
return new MemoryPipe(data, false).startInternal();
}
+ @android.ravenwood.annotation.RavenwoodThrow(reason = "Requires kernel support")
public static MemoryPipe createSink(byte[] data) throws IOException {
return new MemoryPipe(data, true).startInternal();
}
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/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/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/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index d084a9e..36800ea 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -48,6 +48,7 @@
* be accessed or modified concurrently by multiple threads or processes. The caller is responsible
* for ensuring appropriate mutual exclusion invariants whenever it accesses the file.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class AtomicFile {
private static final String LOG_TAG = "AtomicFile";
@@ -68,6 +69,8 @@
* @hide Internal constructor that also allows you to have the class
* automatically log commit events.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy =
+ SystemConfigFileCommitEventLogger.class)
public AtomicFile(File baseName, String commitTag) {
this(baseName, new SystemConfigFileCommitEventLogger(commitTag));
}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 3f7fdd8..be1ec41 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -18,6 +18,7 @@
import android.compat.annotation.UnsupportedAppUsage;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -61,6 +62,7 @@
* of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's
* Support Package</a> for earlier releases.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class LruCache<K, V> {
@UnsupportedAppUsage
private final LinkedHashMap<K, V> map;
@@ -208,7 +210,7 @@
break;
}
- Map.Entry<K, V> toEvict = map.eldest();
+ Map.Entry<K, V> toEvict = eldest();
if (toEvict == null) {
break;
}
@@ -224,6 +226,16 @@
}
}
+ @android.ravenwood.annotation.RavenwoodReplace
+ private Map.Entry<K, V> eldest() {
+ return map.eldest();
+ }
+
+ private Map.Entry<K, V> eldest$ravenwood() {
+ final Iterator<Map.Entry<K, V>> it = map.entrySet().iterator();
+ return it.hasNext() ? it.next() : null;
+ }
+
/**
* Removes the entry for {@code key} if it exists.
*
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 ff1e831..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;
@@ -11361,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/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/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/res/res/values/config.xml b/core/res/res/values/config.xml
index c9d4ba4..ba1f392 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6833,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 efbab30..7787c5d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -407,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/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 445ddf5..37f592f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -66,6 +66,7 @@
"testables",
"com.android.text.flags-aconfig-java",
"flag-junit",
+ "ravenwood-junit",
],
libs: [
@@ -163,3 +164,15 @@
"framework-res",
],
}
+
+android_ravenwood_test {
+ name: "FrameworksCoreTestsRavenwood",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "src/android/os/FileUtilsTest.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index a0d8183..60500d5 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -51,20 +51,17 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.content.Context;
import android.os.FileUtils.MemoryPipe;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DocumentsContract.Document;
import android.util.DataUnit;
-import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
-import com.google.android.collect.Sets;
-
-import libcore.io.Streams;
-
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,12 +71,19 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private static final String TEST_DATA =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -90,17 +94,13 @@
private final int[] DATA_SIZES = { 32, 32_000, 32_000_000 };
- private Context getContext() {
- return InstrumentationRegistry.getContext();
- }
-
@Before
public void setUp() throws Exception {
- mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
+ mDir = Files.createTempDirectory("FileUtils").toFile();
mTestFile = new File(mDir, "test.file");
mCopyFile = new File(mDir, "copy.file");
- mTarget = getContext().getFilesDir();
+ mTarget = mDir;
FileUtils.deleteContents(mTarget);
}
@@ -152,6 +152,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_FileToPipe() throws Exception {
for (int size : DATA_SIZES) {
final File src = new File(mTarget, "src");
@@ -172,6 +173,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_PipeToFile() throws Exception {
for (int size : DATA_SIZES) {
final File dest = new File(mTarget, "dest");
@@ -191,6 +193,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_PipeToPipe() throws Exception {
for (int size : DATA_SIZES) {
byte[] expected = new byte[size];
@@ -208,6 +211,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = MemoryPipe.class)
public void testCopy_ShortPipeToFile() throws Exception {
byte[] source = new byte[33_000_000];
new Random().nextBytes(source);
@@ -424,6 +428,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_normal() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test"));
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
@@ -443,6 +448,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_unknown() throws Exception {
assertNameEquals("test",
FileUtils.buildUniqueFile(mTarget, "application/octet-stream", "test"));
@@ -456,6 +462,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_dir() throws Exception {
assertNameEquals("test", FileUtils.buildUniqueFile(mTarget, Document.MIME_TYPE_DIR, "test"));
new File(mTarget, "test").mkdir();
@@ -470,6 +477,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_increment() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "image/jpeg", "test.jpg"));
new File(mTarget, "test.jpg").createNewFile();
@@ -489,6 +497,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = android.webkit.MimeTypeMap.class)
public void testBuildUniqueFile_mimeless() throws Exception {
assertNameEquals("test.jpg", FileUtils.buildUniqueFile(mTarget, "test.jpg"));
new File(mTarget, "test.jpg").createNewFile();
@@ -584,6 +593,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testTranslateMode() throws Exception {
assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
@@ -603,6 +613,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testMalformedTransate_int() throws Exception {
try {
// The non-standard Linux access mode 3 should throw
@@ -614,6 +625,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testMalformedTransate_string() throws Exception {
try {
// The non-standard Linux access mode 3 should throw
@@ -625,6 +637,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testTranslateMode_Invalid() throws Exception {
try {
translateModeStringToPosix("rwx");
@@ -639,6 +652,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testTranslateMode_Access() throws Exception {
assertEquals(O_RDONLY, translateModeAccessToPosix(F_OK));
assertEquals(O_RDONLY, translateModeAccessToPosix(R_OK));
@@ -648,6 +662,7 @@
}
@Test
+ @IgnoreUnderRavenwood(reason = "Requires kernel support")
public void testConvertToModernFd() throws Exception {
final String nonce = String.valueOf(System.nanoTime());
@@ -720,13 +735,24 @@
private byte[] readFile(File file) throws Exception {
try (FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
- Streams.copy(in, out);
+ copy(in, out);
return out.toByteArray();
}
}
+ private static int copy(InputStream in, OutputStream out) throws IOException {
+ int total = 0;
+ byte[] buffer = new byte[8192];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ total += c;
+ out.write(buffer, 0, c);
+ }
+ return total;
+ }
+
private void assertDirContents(String... expected) {
- final HashSet<String> expectedSet = Sets.newHashSet(expected);
+ final HashSet<String> expectedSet = new HashSet<>(Arrays.asList(expected));
String[] actual = mDir.list();
if (actual == null) actual = new String[0];
diff --git a/core/tests/utiltests/Android.bp b/core/tests/utiltests/Android.bp
index 06340a2..967047e 100644
--- a/core/tests/utiltests/Android.bp
+++ b/core/tests/utiltests/Android.bp
@@ -57,8 +57,10 @@
static_libs: [
"androidx.annotation_annotation",
"androidx.test.rules",
+ "mockito_ravenwood",
],
srcs: [
+ "src/android/util/AtomicFileTest.java",
"src/android/util/DataUnitTest.java",
"src/android/util/EventLogTest.java",
"src/android/util/IndentingPrintWriterTest.java",
diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java
index 6f59714..742307b 100644
--- a/core/tests/utiltests/src/android/util/AtomicFileTest.java
+++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java
@@ -23,16 +23,16 @@
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.spy;
-import android.app.Instrumentation;
-import android.content.Context;
import android.os.SystemClock;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -46,9 +46,13 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
@RunWith(Parameterized.class)
public class AtomicFileTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private static final String BASE_NAME = "base";
private static final String NEW_NAME = BASE_NAME + ".new";
private static final String LEGACY_BACKUP_NAME = BASE_NAME + ".bak";
@@ -80,14 +84,10 @@
@Parameterized.Parameter(3)
public byte[] mExpectedBytes;
- private final Instrumentation mInstrumentation =
- InstrumentationRegistry.getInstrumentation();
- private final Context mContext = mInstrumentation.getContext();
-
- private final File mDirectory = mContext.getFilesDir();
- private final File mBaseFile = new File(mDirectory, BASE_NAME);
- private final File mNewFile = new File(mDirectory, NEW_NAME);
- private final File mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME);
+ private File mDirectory;
+ private File mBaseFile;
+ private File mNewFile;
+ private File mLegacyBackupFile;
@Parameterized.Parameters(name = "{0}")
public static Object[][] data() {
@@ -199,6 +199,13 @@
}
@Before
+ public void setUp() throws Exception {
+ mDirectory = Files.createTempDirectory("AtomicFile").toFile();
+ mBaseFile = new File(mDirectory, BASE_NAME);
+ mNewFile = new File(mDirectory, NEW_NAME);
+ mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME);
+ }
+
@After
public void deleteFiles() {
mBaseFile.delete();
@@ -274,6 +281,7 @@
}
@Test
+ @IgnoreUnderRavenwood(blockedBy = SystemConfigFileCommitEventLogger.class)
public void testTimeLogging() throws Exception {
var logger = spy(new SystemConfigFileCommitEventLogger("name"));
var file = new AtomicFile(mBaseFile, logger);
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/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/androidfw/Android.bp b/libs/androidfw/Android.bp
index 2f28363..47a7f35 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -63,21 +63,15 @@
"AssetsProvider.cpp",
"AttributeResolution.cpp",
"BigBuffer.cpp",
- "BigBufferStream.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
- "FileStream.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"Locale.cpp",
"LocaleData.cpp",
"misc.cpp",
- "NinePatch.cpp",
"ObbFile.cpp",
"PosixUtils.cpp",
- "Png.cpp",
- "PngChunkFilter.cpp",
- "PngCrunch.cpp",
"ResourceTimer.cpp",
"ResourceTypes.cpp",
"ResourceUtils.cpp",
@@ -90,10 +84,7 @@
],
export_include_dirs: ["include"],
export_shared_lib_headers: ["libz"],
- static_libs: [
- "libincfs-utils",
- "libpng",
- ],
+ static_libs: ["libincfs-utils"],
whole_static_libs: [
"libandroidfw_pathutils",
"libincfs-utils",
@@ -207,11 +198,9 @@
"tests/ConfigDescription_test.cpp",
"tests/ConfigLocale_test.cpp",
"tests/DynamicRefTable_test.cpp",
- "tests/FileStream_test.cpp",
"tests/Idmap_test.cpp",
"tests/LoadedArsc_test.cpp",
"tests/Locale_test.cpp",
- "tests/NinePatch_test.cpp",
"tests/ResourceTimer_test.cpp",
"tests/ResourceUtils_test.cpp",
"tests/ResTable_test.cpp",
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/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/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/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/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/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index e294733..eba6e0b 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,16 +2,20 @@
com.android.internal.util.ArrayUtils
+android.util.AtomicFile
android.util.DataUnit
android.util.EventLog
android.util.IntArray
android.util.LongArray
+android.util.LruCache
android.util.Slog
android.util.TimeUtils
android.util.Xml
android.os.Binder
android.os.Binder$IdentitySupplier
+android.os.FileUtils
+android.os.FileUtils$MemoryPipe
android.os.Handler
android.os.HandlerExecutor
android.os.HandlerThread
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/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 c2ca64b..4f6c6d6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -95,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;
@@ -107,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;
@@ -234,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;
@@ -7687,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()
@@ -7698,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/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/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
deleted file mode 100644
index 8cb334d..0000000
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ /dev/null
@@ -1,125 +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.
- */
-
-package com.android.server.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.MediaRoute2Info;
-
-import com.android.media.flags.Flags;
-
-/* package */ final class AudioAttributesUtils {
-
- /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_MEDIA)
- .build();
-
- private AudioAttributesUtils() {
- // no-op to prevent instantiation.
- }
-
- @MediaRoute2Info.Type
- /* package */ static int mapToMediaRouteType(
- @NonNull AudioDeviceAttributes audioDeviceAttributes) {
- if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
- switch (audioDeviceAttributes.getType()) {
- case AudioDeviceInfo.TYPE_HDMI_ARC:
- return MediaRoute2Info.TYPE_HDMI_ARC;
- case AudioDeviceInfo.TYPE_HDMI_EARC:
- return MediaRoute2Info.TYPE_HDMI_EARC;
- }
- }
- switch (audioDeviceAttributes.getType()) {
- case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
- case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
- return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
- case AudioDeviceInfo.TYPE_WIRED_HEADSET:
- return MediaRoute2Info.TYPE_WIRED_HEADSET;
- case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
- return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
- case AudioDeviceInfo.TYPE_DOCK:
- case AudioDeviceInfo.TYPE_DOCK_ANALOG:
- return MediaRoute2Info.TYPE_DOCK;
- case AudioDeviceInfo.TYPE_HDMI:
- case AudioDeviceInfo.TYPE_HDMI_ARC:
- case AudioDeviceInfo.TYPE_HDMI_EARC:
- return MediaRoute2Info.TYPE_HDMI;
- case AudioDeviceInfo.TYPE_USB_DEVICE:
- return MediaRoute2Info.TYPE_USB_DEVICE;
- case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
- return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
- case AudioDeviceInfo.TYPE_BLE_HEADSET:
- return MediaRoute2Info.TYPE_BLE_HEADSET;
- case AudioDeviceInfo.TYPE_HEARING_AID:
- return MediaRoute2Info.TYPE_HEARING_AID;
- default:
- return MediaRoute2Info.TYPE_UNKNOWN;
- }
- }
-
- /* package */ static boolean isDeviceOutputAttributes(
- @Nullable AudioDeviceAttributes audioDeviceAttributes) {
- if (audioDeviceAttributes == null) {
- return false;
- }
-
- if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
- return false;
- }
-
- switch (audioDeviceAttributes.getType()) {
- case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
- case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
- case AudioDeviceInfo.TYPE_WIRED_HEADSET:
- case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
- case AudioDeviceInfo.TYPE_DOCK:
- case AudioDeviceInfo.TYPE_DOCK_ANALOG:
- case AudioDeviceInfo.TYPE_HDMI:
- case AudioDeviceInfo.TYPE_HDMI_ARC:
- case AudioDeviceInfo.TYPE_HDMI_EARC:
- case AudioDeviceInfo.TYPE_USB_DEVICE:
- return true;
- default:
- return false;
- }
- }
-
- /* package */ static boolean isBluetoothOutputAttributes(
- @Nullable AudioDeviceAttributes audioDeviceAttributes) {
- if (audioDeviceAttributes == null) {
- return false;
- }
-
- if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
- return false;
- }
-
- switch (audioDeviceAttributes.getType()) {
- case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
- case AudioDeviceInfo.TYPE_BLE_HEADSET:
- case AudioDeviceInfo.TYPE_BLE_SPEAKER:
- case AudioDeviceInfo.TYPE_HEARING_AID:
- return true;
- default:
- return false;
- }
- }
-
-}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
index 8bc69c2..a00999d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java
@@ -17,7 +17,6 @@
package com.android.server.media;
import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO;
-import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,38 +30,37 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.os.UserHandle;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
- * Controls bluetooth routes and provides selected route override.
+ * Maintains a list of connected {@link BluetoothDevice bluetooth devices} and allows their
+ * activation.
*
- * <p>The controller offers similar functionality to {@link LegacyBluetoothRouteController} but does
- * not support routes selection logic. Instead, relies on external clients to make a decision
- * about currently selected route.
- *
- * <p>Selected route override should be used by {@link AudioManager} which is aware of Audio
- * Policies.
+ * <p>This class also serves as ground truth for assigning {@link MediaRoute2Info#getId() route ids}
+ * for bluetooth routes via {@link #getRouteIdForBluetoothAddress}.
*/
-/* package */ class AudioPoliciesBluetoothRouteController
- implements BluetoothRouteController {
- private static final String TAG = "APBtRouteController";
+// TODO: b/305199571 - Rename this class to remove the RouteController suffix, which causes
+// confusion with the BluetoothRouteController interface.
+/* package */ class AudioPoliciesBluetoothRouteController {
+ private static final String TAG = SystemMediaRoute2Provider.TAG;
private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_";
private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_";
@@ -75,11 +73,8 @@
private final DeviceStateChangedReceiver mDeviceStateChangedReceiver =
new DeviceStateChangedReceiver();
- @NonNull
- private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
-
- @NonNull
- private final SparseIntArray mVolumeMap = new SparseIntArray();
+ @NonNull private Map<String, BluetoothDevice> mAddressToBondedDevice = new HashMap<>();
+ @NonNull private final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
@NonNull
private final Context mContext;
@@ -89,11 +84,6 @@
private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
@NonNull
private final BluetoothProfileMonitor mBluetoothProfileMonitor;
- @NonNull
- private final AudioManager mAudioManager;
-
- @Nullable
- private BluetoothRouteInfo mSelectedBluetoothRoute;
AudioPoliciesBluetoothRouteController(@NonNull Context context,
@NonNull BluetoothAdapter bluetoothAdapter,
@@ -107,21 +97,12 @@
@NonNull BluetoothAdapter bluetoothAdapter,
@NonNull BluetoothProfileMonitor bluetoothProfileMonitor,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
- Objects.requireNonNull(context);
- Objects.requireNonNull(bluetoothAdapter);
- Objects.requireNonNull(bluetoothProfileMonitor);
- Objects.requireNonNull(listener);
-
- mContext = context;
- mBluetoothAdapter = bluetoothAdapter;
- mBluetoothProfileMonitor = bluetoothProfileMonitor;
- mAudioManager = mContext.getSystemService(AudioManager.class);
- mListener = listener;
-
- updateBluetoothRoutes();
+ mContext = Objects.requireNonNull(context);
+ mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
+ mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor);
+ mListener = Objects.requireNonNull(listener);
}
- @Override
public void start(UserHandle user) {
mBluetoothProfileMonitor.start();
@@ -133,122 +114,63 @@
IntentFilter deviceStateChangedIntentFilter = new IntentFilter();
- deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
deviceStateChangedIntentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
deviceStateChangedIntentFilter.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
deviceStateChangedIntentFilter.addAction(
BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
deviceStateChangedIntentFilter.addAction(
BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
- deviceStateChangedIntentFilter.addAction(
- BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
mContext.registerReceiverAsUser(mDeviceStateChangedReceiver, user,
deviceStateChangedIntentFilter, null, null);
+ updateBluetoothRoutes();
}
- @Override
public void stop() {
mContext.unregisterReceiver(mAdapterStateChangedReceiver);
mContext.unregisterReceiver(mDeviceStateChangedReceiver);
}
- @Override
- public boolean selectRoute(@Nullable String deviceAddress) {
- synchronized (this) {
- // Fetch all available devices in order to avoid race conditions with Bluetooth stack.
- updateBluetoothRoutes();
-
- if (deviceAddress == null) {
- mSelectedBluetoothRoute = null;
- return true;
- }
-
- BluetoothRouteInfo bluetoothRouteInfo = mBluetoothRoutes.get(deviceAddress);
-
- if (bluetoothRouteInfo == null) {
- Slog.w(TAG, "Cannot find bluetooth route for " + deviceAddress);
- return false;
- }
-
- mSelectedBluetoothRoute = bluetoothRouteInfo;
- setRouteConnectionState(mSelectedBluetoothRoute, STATE_CONNECTED);
-
- updateConnectivityStateForDevicesInTheSameGroup();
-
- return true;
- }
+ @Nullable
+ public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) {
+ BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address);
+ // TODO: b/305199571 - Optimize the following statement to avoid creating the full
+ // MediaRoute2Info instance. We just need the id.
+ return bluetoothDevice != null
+ ? createBluetoothRoute(bluetoothDevice).mRoute.getId()
+ : null;
}
- /**
- * Updates connectivity state for devices in the same devices group.
- *
- * <p>{@link BluetoothProfile#LE_AUDIO} and {@link BluetoothProfile#HEARING_AID} support
- * grouping devices. Devices that belong to the same group should have the same routeId but
- * different physical address.
- *
- * <p>In case one of the devices from the group is selected then other devices should also
- * reflect this by changing their connectivity status to
- * {@link MediaRoute2Info#CONNECTION_STATE_CONNECTED}.
- */
- private void updateConnectivityStateForDevicesInTheSameGroup() {
- synchronized (this) {
- for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
- if (TextUtils.equals(btRoute.mRoute.getId(), mSelectedBluetoothRoute.mRoute.getId())
- && !TextUtils.equals(btRoute.mBtDevice.getAddress(),
- mSelectedBluetoothRoute.mBtDevice.getAddress())) {
- setRouteConnectionState(btRoute, STATE_CONNECTED);
- }
- }
- }
- }
-
- @Override
- public void transferTo(@Nullable String routeId) {
- if (routeId == null) {
- mBluetoothAdapter.removeActiveDevice(ACTIVE_DEVICE_AUDIO);
- return;
- }
-
- BluetoothRouteInfo btRouteInfo = findBluetoothRouteWithRouteId(routeId);
+ public synchronized void activateBluetoothDeviceWithAddress(String address) {
+ BluetoothRouteInfo btRouteInfo = mBluetoothRoutes.get(address);
if (btRouteInfo == null) {
- Slog.w(TAG, "transferTo: Unknown route. ID=" + routeId);
+ Slog.w(TAG, "activateBluetoothDeviceWithAddress: Ignoring unknown address " + address);
return;
}
-
mBluetoothAdapter.setActiveDevice(btRouteInfo.mBtDevice, ACTIVE_DEVICE_AUDIO);
}
- @Nullable
- private BluetoothRouteInfo findBluetoothRouteWithRouteId(@Nullable String routeId) {
- if (routeId == null) {
- return null;
- }
- synchronized (this) {
- for (BluetoothRouteInfo btRouteInfo : mBluetoothRoutes.values()) {
- if (TextUtils.equals(btRouteInfo.mRoute.getId(), routeId)) {
- return btRouteInfo;
- }
- }
- }
- return null;
- }
-
private void updateBluetoothRoutes() {
Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
- if (bondedDevices == null) {
- return;
- }
-
synchronized (this) {
mBluetoothRoutes.clear();
-
- // We need to query all available to BT stack devices in order to avoid inconsistency
- // between external services, like, AndroidManager, and BT stack.
+ if (bondedDevices == null) {
+ // Bonded devices is null upon running into a BluetoothAdapter error.
+ Log.w(TAG, "BluetoothAdapter.getBondedDevices returned null.");
+ return;
+ }
+ // We don't clear bonded devices if we receive a null getBondedDevices result, because
+ // that probably means that the bluetooth stack ran into an issue. Not that all devices
+ // have been unpaired.
+ mAddressToBondedDevice =
+ bondedDevices.stream()
+ .collect(
+ Collectors.toMap(
+ BluetoothDevice::getAddress, Function.identity()));
for (BluetoothDevice device : bondedDevices) {
- if (isDeviceConnected(device)) {
+ if (device.isConnected()) {
BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
if (newBtRoute.mConnectedProfiles.size() > 0) {
mBluetoothRoutes.put(device.getAddress(), newBtRoute);
@@ -258,106 +180,51 @@
}
}
- @VisibleForTesting
- /* package */ boolean isDeviceConnected(@NonNull BluetoothDevice device) {
- return device.isConnected();
- }
-
- @Nullable
- @Override
- public MediaRoute2Info getSelectedRoute() {
- synchronized (this) {
- if (mSelectedBluetoothRoute == null) {
- return null;
- }
-
- return mSelectedBluetoothRoute.mRoute;
- }
- }
-
@NonNull
- @Override
- public List<MediaRoute2Info> getTransferableRoutes() {
- List<MediaRoute2Info> routes = getAllBluetoothRoutes();
- synchronized (this) {
- if (mSelectedBluetoothRoute != null) {
- routes.remove(mSelectedBluetoothRoute.mRoute);
- }
- }
- return routes;
- }
-
- @NonNull
- @Override
- public List<MediaRoute2Info> getAllBluetoothRoutes() {
+ public List<MediaRoute2Info> getAvailableBluetoothRoutes() {
List<MediaRoute2Info> routes = new ArrayList<>();
- List<String> routeIds = new ArrayList<>();
-
- MediaRoute2Info selectedRoute = getSelectedRoute();
- if (selectedRoute != null) {
- routes.add(selectedRoute);
- routeIds.add(selectedRoute.getId());
- }
+ Set<String> routeIds = new HashSet<>();
synchronized (this) {
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
- // A pair of hearing aid devices or having the same hardware address
- if (routeIds.contains(btRoute.mRoute.getId())) {
- continue;
+ // See createBluetoothRoute for info on why we do this.
+ if (routeIds.add(btRoute.mRoute.getId())) {
+ routes.add(btRoute.mRoute);
}
- routes.add(btRoute.mRoute);
- routeIds.add(btRoute.mRoute.getId());
}
}
return routes;
}
- @Override
- public boolean updateVolumeForDevices(int devices, int volume) {
- int routeType;
- if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
- routeType = MediaRoute2Info.TYPE_HEARING_AID;
- } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
- | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
- | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
- routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
- } else if ((devices & (AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0) {
- routeType = MediaRoute2Info.TYPE_BLE_HEADSET;
- } else {
- return false;
- }
-
- synchronized (this) {
- mVolumeMap.put(routeType, volume);
- if (mSelectedBluetoothRoute == null
- || mSelectedBluetoothRoute.mRoute.getType() != routeType) {
- return false;
- }
-
- mSelectedBluetoothRoute.mRoute =
- new MediaRoute2Info.Builder(mSelectedBluetoothRoute.mRoute)
- .setVolume(volume)
- .build();
- }
-
- notifyBluetoothRoutesUpdated();
- return true;
- }
-
private void notifyBluetoothRoutesUpdated() {
mListener.onBluetoothRoutesUpdated();
}
+ /**
+ * Creates a new {@link BluetoothRouteInfo}, including its member {@link
+ * BluetoothRouteInfo#mRoute}.
+ *
+ * <p>The most important logic in this method is around the {@link MediaRoute2Info#getId() route
+ * id} assignment. In some cases we want to group multiple {@link BluetoothDevice bluetooth
+ * devices} as a single media route. For example, the left and right hearing aids get exposed as
+ * two different BluetoothDevice instances, but we want to show them as a single route. In this
+ * case, we assign the same route id to all "group" bluetooth devices (like left and right
+ * hearing aids), so that a single route is exposed for both of them.
+ *
+ * <p>Deduplication by id happens downstream because we need to be able to refer to all
+ * bluetooth devices individually, since the audio stack refers to a bluetooth device group by
+ * any of its member devices.
+ */
private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
BluetoothRouteInfo
newBtRoute = new BluetoothRouteInfo();
newBtRoute.mBtDevice = device;
-
- String routeId = device.getAddress();
String deviceName = device.getName();
if (TextUtils.isEmpty(deviceName)) {
deviceName = mContext.getResources().getText(R.string.unknownName).toString();
}
+
+ String routeId = device.getAddress();
int type = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
newBtRoute.mConnectedProfiles = new SparseBooleanArray();
if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.A2DP, device)) {
@@ -365,7 +232,6 @@
}
if (mBluetoothProfileMonitor.isProfileSupported(BluetoothProfile.HEARING_AID, device)) {
newBtRoute.mConnectedProfiles.put(BluetoothProfile.HEARING_AID, true);
- // Intentionally assign the same ID for a pair of devices to publish only one of them.
routeId = HEARING_AID_ROUTE_ID_PREFIX
+ mBluetoothProfileMonitor.getGroupId(BluetoothProfile.HEARING_AID, device);
type = MediaRoute2Info.TYPE_HEARING_AID;
@@ -377,66 +243,27 @@
type = MediaRoute2Info.TYPE_BLE_HEADSET;
}
- // Current volume will be set when connected.
- newBtRoute.mRoute = new MediaRoute2Info.Builder(routeId, deviceName)
- .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
- .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
- .setDescription(mContext.getResources().getText(
- R.string.bluetooth_a2dp_audio_route_name).toString())
- .setType(type)
- .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setAddress(device.getAddress())
- .build();
+ // Note that volume is only relevant for active bluetooth routes, and those are managed via
+ // AudioManager.
+ newBtRoute.mRoute =
+ new MediaRoute2Info.Builder(routeId, deviceName)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .addFeature(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+ .setDescription(
+ mContext.getResources()
+ .getText(R.string.bluetooth_a2dp_audio_route_name)
+ .toString())
+ .setType(type)
+ .setAddress(device.getAddress())
+ .build();
return newBtRoute;
}
- private void setRouteConnectionState(@NonNull BluetoothRouteInfo btRoute,
- @MediaRoute2Info.ConnectionState int state) {
- if (btRoute == null) {
- Slog.w(TAG, "setRouteConnectionState: route shouldn't be null");
- return;
- }
- if (btRoute.mRoute.getConnectionState() == state) {
- return;
- }
-
- MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.mRoute)
- .setConnectionState(state);
- builder.setType(btRoute.getRouteType());
-
-
-
- if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
- int currentVolume;
- synchronized (this) {
- currentVolume = mVolumeMap.get(btRoute.getRouteType(), 0);
- }
- builder.setVolume(currentVolume);
- }
-
- btRoute.mRoute = builder.build();
- }
-
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
private MediaRoute2Info mRoute;
private SparseBooleanArray mConnectedProfiles;
-
- @MediaRoute2Info.Type
- int getRouteType() {
- // Let hearing aid profile have a priority.
- if (mConnectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
- return MediaRoute2Info.TYPE_HEARING_AID;
- }
-
- if (mConnectedProfiles.get(BluetoothProfile.LE_AUDIO, false)) {
- return MediaRoute2Info.TYPE_BLE_HEADSET;
- }
-
- return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
- }
}
private class AdapterStateChangedReceiver extends BroadcastReceiver {
@@ -468,9 +295,6 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
- case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
- case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
- case BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED:
case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 6bdfae2..246d68d 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -17,228 +17,596 @@
package com.android.server.media;
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
-import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.IAudioService;
import android.media.MediaRoute2Info;
-import android.os.RemoteException;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
+/**
+ * Maintains a list of all available routes and supports transfers to any of them.
+ *
+ * <p>This implementation is intended for use in conjunction with {@link
+ * NoOpBluetoothRouteController}, as it manages bluetooth devices directly.
+ *
+ * <p>This implementation obtains and manages all routes via {@link AudioManager}, with the
+ * exception of {@link AudioManager#handleBluetoothActiveDeviceChanged inactive bluetooth} routes
+ * which are managed by {@link AudioPoliciesBluetoothRouteController}, which depends on the
+ * bluetooth stack (for example {@link BluetoothAdapter}.
+ */
+// TODO: b/305199571 - Rename this class to avoid the AudioPolicies prefix, which has been flagged
+// by the audio team as a confusing name.
/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
-
- private static final String TAG = "APDeviceRoutesController";
+ private static final String TAG = SystemMediaRoute2Provider.TAG;
@NonNull
- private final Context mContext;
- @NonNull
- private final AudioManager mAudioManager;
- @NonNull
- private final IAudioService mAudioService;
+ private static final AudioAttributes MEDIA_USAGE_AUDIO_ATTRIBUTES =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
@NonNull
- private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
- @NonNull
- private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+ private static final SparseArray<SystemRouteInfo> AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO =
+ new SparseArray<>();
- private int mDeviceVolume;
+ @NonNull private final Context mContext;
+ @NonNull private final AudioManager mAudioManager;
+ @NonNull private final Handler mHandler;
+ @NonNull private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ @NonNull private final AudioPoliciesBluetoothRouteController mBluetoothRouteController;
@NonNull
- private MediaRoute2Info mDeviceRoute;
- @Nullable
- private MediaRoute2Info mSelectedRoute;
+ private final Map<String, MediaRoute2InfoHolder> mRouteIdToAvailableDeviceRoutes =
+ new HashMap<>();
- @VisibleForTesting
- /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context,
+ @NonNull private final AudioProductStrategy mStrategyForMedia;
+
+ @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
+
+ @NonNull
+ private final AudioManager.OnDevicesForAttributesChangedListener
+ mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
+
+ @NonNull private MediaRoute2Info mSelectedRoute;
+
+ // TODO: b/305199571 - Support nullable btAdapter and strategyForMedia which, when null, means
+ // no support for transferring to inactive bluetooth routes and transferring to any routes
+ // respectively.
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ /* package */ AudioPoliciesDeviceRouteController(
+ @NonNull Context context,
@NonNull AudioManager audioManager,
- @NonNull IAudioService audioService,
+ @NonNull Looper looper,
+ @NonNull AudioProductStrategy strategyForMedia,
+ @NonNull BluetoothAdapter btAdapter,
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
- Objects.requireNonNull(context);
- Objects.requireNonNull(audioManager);
- Objects.requireNonNull(audioService);
- Objects.requireNonNull(onDeviceRouteChangedListener);
-
- mContext = context;
- mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
-
- mAudioManager = audioManager;
- mAudioService = audioService;
-
- AudioRoutesInfo newAudioRoutes = null;
- try {
- newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
- } catch (RemoteException e) {
- Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
- }
-
- mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ mContext = Objects.requireNonNull(context);
+ mAudioManager = Objects.requireNonNull(audioManager);
+ mHandler = new Handler(Objects.requireNonNull(looper));
+ mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
+ mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+ mBluetoothRouteController =
+ new AudioPoliciesBluetoothRouteController(
+ 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();
}
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
@Override
- public synchronized boolean selectRoute(@Nullable Integer type) {
- if (type == null) {
- mSelectedRoute = null;
- return true;
- }
+ public void start(UserHandle mUser) {
+ mBluetoothRouteController.start(mUser);
+ mAudioManager.registerAudioDeviceCallback(mAudioDeviceCallback, mHandler);
+ mAudioManager.addOnDevicesForAttributesChangedListener(
+ AudioRoutingUtils.ATTRIBUTES_MEDIA,
+ new HandlerExecutor(mHandler),
+ mOnDevicesForAttributesChangedListener);
+ }
- if (!isDeviceRouteType(type)) {
- return false;
- }
-
- mSelectedRoute = createRouteFromAudioInfo(type);
- return true;
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ @Override
+ public void stop() {
+ mAudioManager.removeOnDevicesForAttributesChangedListener(
+ mOnDevicesForAttributesChangedListener);
+ mAudioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
+ mBluetoothRouteController.stop();
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
}
@Override
@NonNull
public synchronized MediaRoute2Info getSelectedRoute() {
- if (mSelectedRoute != null) {
- return mSelectedRoute;
- }
- return mDeviceRoute;
+ return mSelectedRoute;
}
@Override
+ @NonNull
+ public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+ return mRouteIdToAvailableDeviceRoutes.values().stream()
+ .map(it -> it.mMediaRoute2Info)
+ .toList();
+ }
+
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @Override
+ public synchronized void transferTo(@Nullable String routeId) {
+ if (routeId == null) {
+ // This should never happen: This branch should only execute when the matching bluetooth
+ // route controller is not the no-op one.
+ // TODO: b/305199571 - Make routeId non-null and remove this branch once we remove the
+ // legacy route controller implementations.
+ Slog.e(TAG, "Unexpected call to AudioPoliciesDeviceRouteController#transferTo(null)");
+ return;
+ }
+ MediaRoute2InfoHolder mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId);
+ if (mediaRoute2InfoHolder == null) {
+ Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId);
+ return;
+ }
+ if (mediaRoute2InfoHolder.mCorrespondsToInactiveBluetoothRoute) {
+ // By default, the last connected device is the active route so we don't need to apply a
+ // routing audio policy.
+ mBluetoothRouteController.activateBluetoothDeviceWithAddress(
+ mediaRoute2InfoHolder.mMediaRoute2Info.getAddress());
+ mAudioManager.removePreferredDeviceForStrategy(mStrategyForMedia);
+ } else {
+ AudioDeviceAttributes attr =
+ new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ mediaRoute2InfoHolder.mAudioDeviceInfoType,
+ /* address= */ ""); // This is not a BT device, hence no address needed.
+ mAudioManager.setPreferredDeviceForStrategy(mStrategyForMedia, attr);
+ }
+ }
+
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ @Override
public synchronized boolean updateVolume(int volume) {
- if (mDeviceVolume == volume) {
- return false;
- }
-
- mDeviceVolume = volume;
-
- if (mSelectedRoute != null) {
- mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute)
- .setVolume(volume)
- .build();
- }
-
- mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
- .setVolume(volume)
- .build();
-
+ // TODO: b/305199571 - Optimize so that we only update the volume of the selected route. We
+ // don't need to rebuild all available routes.
+ rebuildAvailableRoutesAndNotify();
return true;
}
- @NonNull
- private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
- int type = TYPE_BUILTIN_SPEAKER;
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ private void onDevicesForAttributesChangedListener(
+ AudioAttributes attributes, List<AudioDeviceAttributes> unusedAudioDeviceAttributes) {
+ if (attributes.getUsage() == AudioAttributes.USAGE_MEDIA) {
+ // We only care about the media usage. Ignore everything else.
+ rebuildAvailableRoutesAndNotify();
+ }
+ }
- if (newRoutes != null) {
- if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
- type = TYPE_WIRED_HEADPHONES;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
- type = TYPE_WIRED_HEADSET;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
- type = TYPE_DOCK;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
- type = TYPE_HDMI;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
- type = TYPE_USB_DEVICE;
+ private synchronized void rebuildAvailableRoutesAndNotify() {
+ rebuildAvailableRoutes();
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+ }
+
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ private synchronized void rebuildAvailableRoutes() {
+ List<AudioDeviceAttributes> attributesOfSelectedOutputDevices =
+ mAudioManager.getDevicesForAttributes(MEDIA_USAGE_AUDIO_ATTRIBUTES);
+ int selectedDeviceAttributesType;
+ if (attributesOfSelectedOutputDevices.isEmpty()) {
+ Slog.e(
+ TAG,
+ "Unexpected empty list of output devices for media. Using built-in speakers.");
+ selectedDeviceAttributesType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ } else {
+ if (attributesOfSelectedOutputDevices.size() > 1) {
+ Slog.w(
+ TAG,
+ "AudioManager.getDevicesForAttributes returned more than one element. Using"
+ + " the first one.");
+ }
+ selectedDeviceAttributesType = attributesOfSelectedOutputDevices.get(0).getType();
+ }
+
+ AudioDeviceInfo[] audioDeviceInfos =
+ mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ mRouteIdToAvailableDeviceRoutes.clear();
+ MediaRoute2InfoHolder newSelectedRouteHolder = null;
+ for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) {
+ MediaRoute2Info mediaRoute2Info =
+ createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo);
+ // Null means audioDeviceInfo is not a supported media output, like a phone's builtin
+ // earpiece. We ignore those.
+ if (mediaRoute2Info != null) {
+ int audioDeviceInfoType = audioDeviceInfo.getType();
+ MediaRoute2InfoHolder newHolder =
+ MediaRoute2InfoHolder.createForAudioManagerRoute(
+ mediaRoute2Info, audioDeviceInfoType);
+ mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder);
+ if (selectedDeviceAttributesType == audioDeviceInfoType) {
+ newSelectedRouteHolder = newHolder;
+ }
}
}
- return createRouteFromAudioInfo(type);
- }
-
- @NonNull
- private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
- int name = R.string.default_audio_route_name;
- switch (type) {
- case TYPE_WIRED_HEADPHONES:
- case TYPE_WIRED_HEADSET:
- name = R.string.default_audio_route_name_headphones;
- break;
- case TYPE_DOCK:
- name = R.string.default_audio_route_name_dock_speakers;
- break;
- case TYPE_HDMI:
- case TYPE_HDMI_ARC:
- case TYPE_HDMI_EARC:
- name = R.string.default_audio_route_name_external_device;
- break;
- case TYPE_USB_DEVICE:
- name = R.string.default_audio_route_name_usb;
- break;
+ if (mRouteIdToAvailableDeviceRoutes.isEmpty()) {
+ // Due to an unknown reason (possibly an audio server crash), we ended up with an empty
+ // list of routes. Our entire codebase assumes at least one system route always exists,
+ // so we create a placeholder route represented as a built-in speaker for
+ // user-presentation purposes.
+ Slog.e(TAG, "Ended up with an empty list of routes. Creating a placeholder route.");
+ MediaRoute2InfoHolder placeholderRouteHolder = createPlaceholderBuiltinSpeakerRoute();
+ String placeholderRouteId = placeholderRouteHolder.mMediaRoute2Info.getId();
+ mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder);
}
- synchronized (this) {
- return new MediaRoute2Info.Builder(
- MediaRoute2Info.ROUTE_ID_DEVICE,
- mContext.getResources().getText(name).toString())
- .setVolumeHandling(
- mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolume(mDeviceVolume)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setType(type)
- .addFeature(FEATURE_LIVE_AUDIO)
- .addFeature(FEATURE_LIVE_VIDEO)
- .addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
+ if (newSelectedRouteHolder == null) {
+ Slog.e(
+ TAG,
+ "Could not map this selected device attribute type to an available route: "
+ + selectedDeviceAttributesType);
+ // We know mRouteIdToAvailableDeviceRoutes is not empty.
+ newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
+ }
+ MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo =
+ newSelectedRouteHolder.copyWithVolumeInfoFromAudioManager(mAudioManager);
+ mRouteIdToAvailableDeviceRoutes.put(
+ newSelectedRouteHolder.mMediaRoute2Info.getId(),
+ selectedRouteHolderWithUpdatedVolumeInfo);
+ mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info;
+
+ // We only add those BT routes that we have not already obtained from audio manager (which
+ // are active).
+ mBluetoothRouteController.getAvailableBluetoothRoutes().stream()
+ .filter(it -> !mRouteIdToAvailableDeviceRoutes.containsKey(it.getId()))
+ .map(MediaRoute2InfoHolder::createForInactiveBluetoothRoute)
+ .forEach(
+ it -> mRouteIdToAvailableDeviceRoutes.put(it.mMediaRoute2Info.getId(), it));
+ }
+
+ private MediaRoute2InfoHolder createPlaceholderBuiltinSpeakerRoute() {
+ int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ return MediaRoute2InfoHolder.createForAudioManagerRoute(
+ createMediaRoute2Info(
+ /* routeId= */ null, type, /* productName= */ null, /* address= */ null),
+ type);
+ }
+
+ @Nullable
+ private MediaRoute2Info createMediaRoute2InfoFromAudioDeviceInfo(
+ AudioDeviceInfo audioDeviceInfo) {
+ String address = audioDeviceInfo.getAddress();
+ // Passing a null route id means we want to get the default id for the route. Generally, we
+ // only expect to pass null for non-Bluetooth routes.
+ String routeId =
+ TextUtils.isEmpty(address)
+ ? null
+ : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
+ return createMediaRoute2Info(
+ routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address);
+ }
+
+ /**
+ * Creates a new {@link MediaRoute2Info} using the provided information.
+ *
+ * @param routeId A route id, or null to use an id pre-defined for the given {@code type}.
+ * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}.
+ * @param productName The product name as obtained from {@link
+ * AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code
+ * type}.
+ * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link
+ * BluetoothDevice#getAddress()}.
+ * @return The new {@link MediaRoute2Info}.
+ */
+ @Nullable
+ private MediaRoute2Info createMediaRoute2Info(
+ @Nullable String routeId,
+ int audioDeviceInfoType,
+ @Nullable CharSequence productName,
+ @Nullable String address) {
+ SystemRouteInfo systemRouteInfo =
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType);
+ if (systemRouteInfo == null) {
+ // Device type that's intentionally unsupported for media output, like the built-in
+ // earpiece.
+ return null;
+ }
+ CharSequence humanReadableName = productName;
+ if (TextUtils.isEmpty(humanReadableName)) {
+ humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource);
+ }
+ if (routeId == null) {
+ // The caller hasn't provided an id, so we use a pre-defined one. This happens when we
+ // are creating a non-BT route, or we are creating a BT route but a race condition
+ // caused AudioManager to expose the BT route before BluetoothAdapter, preventing us
+ // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
+ routeId = systemRouteInfo.mDefaultRouteId;
+ }
+ return new MediaRoute2Info.Builder(routeId, humanReadableName)
+ .setType(systemRouteInfo.mMediaRoute2InfoType)
+ .setAddress(address)
+ .setSystemRoute(true)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+ .build();
+ }
+
+ /**
+ * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the
+ * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this
+ * class.
+ */
+ private static class MediaRoute2InfoHolder {
+
+ public final MediaRoute2Info mMediaRoute2Info;
+ public final int mAudioDeviceInfoType;
+ public final boolean mCorrespondsToInactiveBluetoothRoute;
+
+ public static MediaRoute2InfoHolder createForAudioManagerRoute(
+ MediaRoute2Info mediaRoute2Info, int audioDeviceInfoType) {
+ return new MediaRoute2InfoHolder(
+ mediaRoute2Info,
+ audioDeviceInfoType,
+ /* correspondsToInactiveBluetoothRoute= */ false);
+ }
+
+ public static MediaRoute2InfoHolder createForInactiveBluetoothRoute(
+ MediaRoute2Info mediaRoute2Info) {
+ // There's no corresponding audio device info, hence the audio device info type is
+ // unknown.
+ return new MediaRoute2InfoHolder(
+ mediaRoute2Info,
+ /* audioDeviceInfoType= */ AudioDeviceInfo.TYPE_UNKNOWN,
+ /* correspondsToInactiveBluetoothRoute= */ true);
+ }
+
+ private MediaRoute2InfoHolder(
+ MediaRoute2Info mediaRoute2Info,
+ int audioDeviceInfoType,
+ boolean correspondsToInactiveBluetoothRoute) {
+ mMediaRoute2Info = mediaRoute2Info;
+ mAudioDeviceInfoType = audioDeviceInfoType;
+ mCorrespondsToInactiveBluetoothRoute = correspondsToInactiveBluetoothRoute;
+ }
+
+ public MediaRoute2InfoHolder copyWithVolumeInfoFromAudioManager(
+ AudioManager mAudioManager) {
+ MediaRoute2Info routeInfoWithVolumeInfo =
+ new MediaRoute2Info.Builder(mMediaRoute2Info)
+ .setVolumeHandling(
+ mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
+ .setVolumeMax(
+ mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .build();
+ return new MediaRoute2InfoHolder(
+ routeInfoWithVolumeInfo,
+ mAudioDeviceInfoType,
+ mCorrespondsToInactiveBluetoothRoute);
}
}
/**
- * Checks if the given type is a device route.
- *
- * <p>Device route means a route which is either built-in or wired to the current device.
- *
- * @param type specifies the type of the device.
- * @return {@code true} if the device is wired or built-in and {@code false} otherwise.
+ * Holds route information about an {@link AudioDeviceInfo#getType() audio device info type}.
*/
- private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) {
- switch (type) {
- case TYPE_BUILTIN_SPEAKER:
- case TYPE_WIRED_HEADPHONES:
- case TYPE_WIRED_HEADSET:
- case TYPE_DOCK:
- case TYPE_HDMI:
- case TYPE_HDMI_ARC:
- case TYPE_HDMI_EARC:
- case TYPE_USB_DEVICE:
- return true;
- default:
- return false;
+ private static class SystemRouteInfo {
+ /** The type to use for {@link MediaRoute2Info#getType()}. */
+ public final int mMediaRoute2InfoType;
+
+ /**
+ * Holds the route id to use if no other id is provided.
+ *
+ * <p>We only expect this id to be used for non-bluetooth routes. For bluetooth routes, in a
+ * normal scenario, the id is generated from the device information (like address, or
+ * hiSyncId), and this value is ignored. A non-normal scenario may occur when there's race
+ * condition between {@link BluetoothAdapter} and {@link AudioManager}, who are not
+ * synchronized.
+ */
+ public final String mDefaultRouteId;
+
+ /**
+ * The name to use for {@link MediaRoute2Info#getName()}.
+ *
+ * <p>Usually replaced by the UI layer with a localized string.
+ */
+ public final int mNameResource;
+
+ private SystemRouteInfo(int mediaRoute2InfoType, String defaultRouteId, int nameResource) {
+ mMediaRoute2InfoType = mediaRoute2InfoType;
+ mDefaultRouteId = defaultRouteId;
+ mNameResource = nameResource;
}
}
- private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
-
+ private class AudioDeviceCallbackImpl extends AudioDeviceCallback {
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
@Override
- public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
- boolean isDeviceRouteChanged;
- MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
-
- synchronized (AudioPoliciesDeviceRouteController.this) {
- mDeviceRoute = deviceRoute;
- isDeviceRouteChanged = mSelectedRoute == null;
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ for (AudioDeviceInfo deviceInfo : addedDevices) {
+ if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+ // When a new valid media output is connected, we clear any routing policies so
+ // that the default routing logic from the audio framework kicks in. As a result
+ // 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);
+ rebuildAvailableRoutesAndNotify();
+ break;
+ }
}
+ }
- if (isDeviceRouteChanged) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged();
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MODIFY_AUDIO_ROUTING,
+ Manifest.permission.QUERY_AUDIO_STATE
+ })
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ for (AudioDeviceInfo deviceInfo : removedDevices) {
+ if (AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.contains(deviceInfo.getType())) {
+ rebuildAvailableRoutesAndNotify();
+ break;
+ }
}
}
}
+ static {
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+ /* defaultRouteId= */ "ROUTE_ID_BUILTIN_SPEAKER",
+ /* nameResource= */ R.string.default_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_WIRED_HEADSET,
+ /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADSET",
+ /* nameResource= */ R.string.default_audio_route_name_headphones));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+ /* defaultRouteId= */ "ROUTE_ID_WIRED_HEADPHONES",
+ /* nameResource= */ R.string.default_audio_route_name_headphones));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_BLUETOOTH_A2DP,
+ /* defaultRouteId= */ "ROUTE_ID_BLUETOOTH_A2DP",
+ /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_HDMI,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_HDMI,
+ /* defaultRouteId= */ "ROUTE_ID_HDMI",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_DOCK,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_DOCK,
+ /* defaultRouteId= */ "ROUTE_ID_DOCK",
+ /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_USB_DEVICE,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_USB_DEVICE,
+ /* defaultRouteId= */ "ROUTE_ID_USB_DEVICE",
+ /* nameResource= */ R.string.default_audio_route_name_usb));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_USB_HEADSET,
+ /* defaultRouteId= */ "ROUTE_ID_USB_HEADSET",
+ /* nameResource= */ R.string.default_audio_route_name_usb));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_HDMI_ARC,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_HDMI_ARC,
+ /* defaultRouteId= */ "ROUTE_ID_HDMI_ARC",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_HDMI_EARC,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_HDMI_EARC,
+ /* defaultRouteId= */ "ROUTE_ID_HDMI_EARC",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ // TODO: b/305199571 - Add a proper type constants and human readable names for AUX_LINE,
+ // LINE_ANALOG, LINE_DIGITAL, BLE_BROADCAST, BLE_SPEAKER, BLE_HEADSET, and HEARING_AID.
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_HEARING_AID,
+ /* defaultRouteId= */ "ROUTE_ID_HEARING_AID",
+ /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_BLE_HEADSET,
+ /* defaultRouteId= */ "ROUTE_ID_BLE_HEADSET",
+ /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_BLE_HEADSET, // TODO: b/305199571 - Make a new type.
+ /* defaultRouteId= */ "ROUTE_ID_BLE_SPEAKER",
+ /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_BLE_HEADSET,
+ /* defaultRouteId= */ "ROUTE_ID_BLE_BROADCAST",
+ /* nameResource= */ R.string.bluetooth_a2dp_audio_route_name));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_LINE_DIGITAL,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_UNKNOWN,
+ /* defaultRouteId= */ "ROUTE_ID_LINE_DIGITAL",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_LINE_ANALOG,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_UNKNOWN,
+ /* defaultRouteId= */ "ROUTE_ID_LINE_ANALOG",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_AUX_LINE,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_UNKNOWN,
+ /* defaultRouteId= */ "ROUTE_ID_AUX_LINE",
+ /* nameResource= */ R.string.default_audio_route_name_external_device));
+ AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+ AudioDeviceInfo.TYPE_DOCK_ANALOG,
+ new SystemRouteInfo(
+ MediaRoute2Info.TYPE_DOCK,
+ /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
+ /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+ }
}
diff --git a/services/core/java/com/android/server/media/AudioRoutingUtils.java b/services/core/java/com/android/server/media/AudioRoutingUtils.java
new file mode 100644
index 0000000..13f11eb
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioRoutingUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.media;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioProductStrategy;
+
+/** Holds utils related to routing in the audio framework. */
+/* package */ final class AudioRoutingUtils {
+
+ /* package */ static final AudioAttributes ATTRIBUTES_MEDIA =
+ new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ @Nullable
+ /* package */ static AudioProductStrategy getMediaAudioProductStrategy() {
+ for (AudioProductStrategy strategy : AudioManager.getAudioProductStrategies()) {
+ if (strategy.supportsAudioAttributes(AudioRoutingUtils.ATTRIBUTES_MEDIA)) {
+ return strategy;
+ }
+ }
+ return null;
+ }
+
+ private AudioRoutingUtils() {
+ // no-op to prevent instantiation.
+ }
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 2b01001..74fdf6e 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -44,19 +44,11 @@
@NonNull
static BluetoothRouteController createInstance(@NonNull Context context,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
- Objects.requireNonNull(context);
Objects.requireNonNull(listener);
+ BluetoothAdapter btAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
- BluetoothManager bluetoothManager = (BluetoothManager)
- context.getSystemService(Context.BLUETOOTH_SERVICE);
- BluetoothAdapter btAdapter = bluetoothManager.getAdapter();
-
- if (btAdapter == null) {
+ if (btAdapter == null || Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
return new NoOpBluetoothRouteController();
- }
-
- if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
- return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
} else {
return new LegacyBluetoothRouteController(context, btAdapter, listener);
}
@@ -74,17 +66,6 @@
*/
void stop();
-
- /**
- * Selects the route with the given {@code deviceAddress}.
- *
- * @param deviceAddress The physical address of the device to select. May be null to unselect
- * the currently selected device.
- * @return Whether the selection succeeds. If the selection fails, the state of the instance
- * remains unaltered.
- */
- boolean selectRoute(@Nullable String deviceAddress);
-
/**
* Transfers Bluetooth output to the given route.
*
@@ -158,12 +139,6 @@
}
@Override
- public boolean selectRoute(String deviceAddress) {
- // no op
- return false;
- }
-
- @Override
public void transferTo(String routeId) {
// no op
}
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 0fdaaa7..9f175a9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -16,17 +16,25 @@
package com.android.server.media;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.media.AudioManager;
-import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
import android.media.MediaRoute2Info;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.os.Looper;
import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.media.flags.Flags;
+import java.util.List;
+
/**
* Controls device routes.
*
@@ -37,46 +45,67 @@
*/
/* package */ interface DeviceRouteController {
- /**
- * Returns a new instance of {@link DeviceRouteController}.
- */
- /* package */ static DeviceRouteController createInstance(@NonNull Context context,
+ /** Returns a new instance of {@link DeviceRouteController}. */
+ @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+ /* package */ static DeviceRouteController createInstance(
+ @NonNull Context context,
+ @NonNull Looper looper,
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
AudioManager audioManager = context.getSystemService(AudioManager.class);
- IAudioService audioService = IAudioService.Stub.asInterface(
- ServiceManager.getService(Context.AUDIO_SERVICE));
+ AudioProductStrategy strategyForMedia = AudioRoutingUtils.getMediaAudioProductStrategy();
- if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
- return new AudioPoliciesDeviceRouteController(context,
+ BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ BluetoothAdapter btAdapter =
+ bluetoothManager != null ? bluetoothManager.getAdapter() : null;
+
+ // TODO: b/305199571 - Make the audio policies implementation work without the need for a
+ // bluetooth adapter or a strategy for media. If no strategy for media is available we can
+ // disallow media router transfers, and without a bluetooth adapter we can remove support
+ // for transfers to inactive bluetooth routes.
+ if (strategyForMedia != null
+ && btAdapter != null
+ && Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ return new AudioPoliciesDeviceRouteController(
+ context,
audioManager,
- audioService,
+ looper,
+ strategyForMedia,
+ btAdapter,
onDeviceRouteChangedListener);
} else {
- return new LegacyDeviceRouteController(context,
- audioManager,
- audioService,
- onDeviceRouteChangedListener);
+ IAudioService audioService =
+ IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+ return new LegacyDeviceRouteController(
+ context, audioManager, audioService, onDeviceRouteChangedListener);
}
}
- /**
- * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}.
- *
- * <p>If the type is {@code null} then unselects the route and falls back to the default device
- * route observed from
- * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}.
- *
- * @param type device type. May be {@code null} to unselect currently selected route.
- * @return whether the selection succeeds. If the selection fails the state of the controller
- * remains intact.
- */
- boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
-
/** Returns the currently selected device (built-in or wired) route. */
@NonNull
MediaRoute2Info getSelectedRoute();
/**
+ * Returns all available routes.
+ *
+ * <p>Note that this method returns available routes including the selected route because (a)
+ * this interface doesn't guarantee that the internal state of the controller won't change
+ * between calls to {@link #getSelectedRoute()} and this method and (b) {@link
+ * #getSelectedRoute()} may be treated as a transferable route (not a selected route) if the
+ * selected route is from {@link BluetoothRouteController}.
+ */
+ List<MediaRoute2Info> getAvailableRoutes();
+
+ /**
+ * Transfers device output to the given route.
+ *
+ * <p>If the route is {@code null} then active route will be deactivated.
+ *
+ * @param routeId to switch to or {@code null} to unset the active device.
+ */
+ void transferTo(@Nullable String routeId);
+
+ /**
* Updates device route volume.
*
* @param volume specifies a volume for the device route or 0 for unknown.
@@ -85,6 +114,18 @@
boolean updateVolume(int volume);
/**
+ * Starts listening for changes in the system to keep an up to date view of available and
+ * selected devices.
+ */
+ void start(UserHandle mUser);
+
+ /**
+ * Stops keeping the internal state up to date with the system, releasing any resources acquired
+ * in {@link #start}
+ */
+ void stop();
+
+ /**
* Interface for receiving events when device route has changed.
*/
interface OnDeviceRouteChangedListener {
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index ba3cecf..041fceaf 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -132,12 +132,6 @@
mContext.unregisterReceiver(mDeviceStateChangedReceiver);
}
- @Override
- public boolean selectRoute(String deviceAddress) {
- // No-op as the class decides if a route is selected based on Bluetooth events.
- return false;
- }
-
/**
* Transfers to a given bluetooth route.
* The dedicated BT device with the route would be activated.
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index 65874e2..c0f2834 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -35,11 +35,13 @@
import android.media.IAudioService;
import android.media.MediaRoute2Info;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -73,7 +75,6 @@
private int mDeviceVolume;
private MediaRoute2Info mDeviceRoute;
- @VisibleForTesting
/* package */ LegacyDeviceRouteController(@NonNull Context context,
@NonNull AudioManager audioManager,
@NonNull IAudioService audioService,
@@ -100,9 +101,13 @@
}
@Override
- public boolean selectRoute(@Nullable Integer type) {
- // No-op as the controller does not support selection from the outside of the class.
- return false;
+ public void start(UserHandle mUser) {
+ // Nothing to do.
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do.
}
@Override
@@ -112,6 +117,17 @@
}
@Override
+ public synchronized List<MediaRoute2Info> getAvailableRoutes() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public synchronized void transferTo(@Nullable String routeId) {
+ // Unsupported. This implementation doesn't support transferable routes (always exposes a
+ // single non-bluetooth route).
+ }
+
+ @Override
public synchronized boolean updateVolume(int volume) {
if (mDeviceVolume == volume) {
return false;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index c8dba80..86d7833 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,15 +16,12 @@
package com.android.server.media;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
@@ -51,7 +48,8 @@
*/
// TODO: check thread safety. We may need to use lock to protect variables.
class SystemMediaRoute2Provider extends MediaRoute2Provider {
- private static final String TAG = "MR2SystemProvider";
+ // Package-visible to use this tag for all system routing logic (done across multiple classes).
+ /* package */ static final String TAG = "MR2SystemProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final ComponentName COMPONENT_NAME = new ComponentName(
@@ -77,26 +75,6 @@
private final AudioManagerBroadcastReceiver mAudioReceiver =
new AudioManagerBroadcastReceiver();
- private final AudioManager.OnDevicesForAttributesChangedListener
- mOnDevicesForAttributesChangedListener =
- new AudioManager.OnDevicesForAttributesChangedListener() {
- @Override
- public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
- @NonNull List<AudioDeviceAttributes> devices) {
- if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
- return;
- }
-
- mHandler.post(() -> {
- updateSelectedAudioDevice(devices);
- notifyProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- }
- };
-
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -106,7 +84,8 @@
mIsSystemRouteProvider = true;
mContext = context;
mUser = user;
- mHandler = new Handler(Looper.getMainLooper());
+ Looper looper = Looper.getMainLooper();
+ mHandler = new Handler(looper);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -123,25 +102,15 @@
mDeviceRouteController =
DeviceRouteController.createInstance(
context,
- () -> {
- mHandler.post(
- () -> {
- publishProviderState();
- if (updateSessionInfosIfNeeded()) {
- notifySessionInfoUpdated();
- }
- });
- });
-
- mAudioManager.addOnDevicesForAttributesChangedListener(
- AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
- mOnDevicesForAttributesChangedListener);
-
- // These methods below should be called after all fields are initialized, as they
- // access the fields inside.
- List<AudioDeviceAttributes> devices =
- mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
- updateSelectedAudioDevice(devices);
+ looper,
+ () ->
+ mHandler.post(
+ () -> {
+ publishProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ }));
updateProviderState();
updateSessionInfosIfNeeded();
}
@@ -151,20 +120,21 @@
intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
mContext.registerReceiverAsUser(mAudioReceiver, mUser,
intentFilter, null, null);
-
- mHandler.post(() -> {
- mBluetoothRouteController.start(mUser);
- notifyProviderState();
- });
- updateVolume();
+ mHandler.post(
+ () -> {
+ mDeviceRouteController.start(mUser);
+ mBluetoothRouteController.start(mUser);
+ });
}
public void stop() {
mContext.unregisterReceiver(mAudioReceiver);
- mHandler.post(() -> {
- mBluetoothRouteController.stop();
- notifyProviderState();
- });
+ mHandler.post(
+ () -> {
+ mBluetoothRouteController.stop();
+ mDeviceRouteController.stop();
+ notifyProviderState();
+ });
}
@Override
@@ -225,13 +195,26 @@
public void transferToRoute(long requestId, String sessionId, String routeId) {
if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
// The currently selected route is the default route.
+ Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
return;
}
-
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
- if (TextUtils.equals(routeId, selectedDeviceRoute.getId())) {
+ boolean isAvailableDeviceRoute =
+ mDeviceRouteController.getAvailableRoutes().stream()
+ .anyMatch(it -> it.getId().equals(routeId));
+ boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId());
+
+ if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
+ // The requested route is managed by the device route controller. Note that the selected
+ // device route doesn't necessarily match mSelectedRouteId (which is the selected route
+ // of the routing session). If the selected device route is transferred to, we need to
+ // make the bluetooth routes inactive so that the device route becomes the selected
+ // route of the routing session.
+ mDeviceRouteController.transferTo(routeId);
mBluetoothRouteController.transferTo(null);
} else {
+ // The requested route is managed by the bluetooth route controller.
+ mDeviceRouteController.transferTo(null);
mBluetoothRouteController.transferTo(routeId);
}
}
@@ -280,41 +263,38 @@
MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
- RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
- SYSTEM_SESSION_ID, packageName).setSystemSession(true);
+ RoutingSessionInfo.Builder builder =
+ new RoutingSessionInfo.Builder(SYSTEM_SESSION_ID, packageName)
+ .setSystemSession(true);
builder.addSelectedRoute(selectedDeviceRoute.getId());
for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
builder.addTransferableRoute(route.getId());
}
+
+ if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+ if (!TextUtils.equals(selectedDeviceRoute.getId(), route.getId())) {
+ builder.addTransferableRoute(route.getId());
+ }
+ }
+ }
return builder.setProviderId(mUniqueId).build();
}
}
- private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
- if (devices.isEmpty()) {
- Slog.w(TAG, "The list of preferred devices was empty.");
- return;
- }
-
- AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
-
- if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
- mDeviceRouteController.selectRoute(
- AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
- mBluetoothRouteController.selectRoute(null);
- } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
- mDeviceRouteController.selectRoute(null);
- mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
- } else {
- Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
- }
- }
-
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
// We must have a device route in the provider info.
- builder.addRoute(mDeviceRouteController.getSelectedRoute());
+ if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ List<MediaRoute2Info> deviceRoutes = mDeviceRouteController.getAvailableRoutes();
+ for (MediaRoute2Info route : deviceRoutes) {
+ builder.addRoute(route);
+ }
+ setProviderState(builder.build());
+ } else {
+ builder.addRoute(mDeviceRouteController.getSelectedRoute());
+ }
for (MediaRoute2Info route : mBluetoothRouteController.getAllBluetoothRoutes()) {
builder.addRoute(route);
@@ -352,7 +332,12 @@
.setProviderId(mUniqueId)
.build();
builder.addSelectedRoute(mSelectedRouteId);
-
+ for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
+ String routeId = route.getId();
+ if (!mSelectedRouteId.equals(routeId)) {
+ builder.addTransferableRoute(routeId);
+ }
+ }
for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
builder.addTransferableRoute(route.getId());
}
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/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/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/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java b/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
deleted file mode 100644
index 0ad4184..0000000
--- a/services/robotests/src/com/android/server/media/AudioPoliciesBluetoothRouteControllerTest.java
+++ /dev/null
@@ -1,293 +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.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.when;
-
-import android.app.Application;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.BluetoothProfile;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.MediaRoute2Info;
-import android.os.UserHandle;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.Shadows;
-import org.robolectric.shadows.ShadowBluetoothAdapter;
-import org.robolectric.shadows.ShadowBluetoothDevice;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-@RunWith(RobolectricTestRunner.class)
-public class AudioPoliciesBluetoothRouteControllerTest {
-
- private static final String DEVICE_ADDRESS_UNKNOWN = ":unknown:ip:address:";
- private static final String DEVICE_ADDRESS_SAMPLE_1 = "30:59:8B:E4:C6:35";
- private static final String DEVICE_ADDRESS_SAMPLE_2 = "0D:0D:A6:FF:8D:B6";
- private static final String DEVICE_ADDRESS_SAMPLE_3 = "2D:9B:0C:C2:6F:78";
- private static final String DEVICE_ADDRESS_SAMPLE_4 = "66:88:F9:2D:A8:1E";
-
- private Context mContext;
-
- private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-
- @Mock
- private BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
-
- @Mock
- private BluetoothProfileMonitor mBluetoothProfileMonitor;
-
- private AudioPoliciesBluetoothRouteController mAudioPoliciesBluetoothRouteController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- Application application = ApplicationProvider.getApplicationContext();
- mContext = application;
-
- BluetoothManager bluetoothManager = (BluetoothManager)
- mContext.getSystemService(Context.BLUETOOTH_SERVICE);
-
- BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
- mShadowBluetoothAdapter = Shadows.shadowOf(bluetoothAdapter);
-
- mAudioPoliciesBluetoothRouteController =
- new AudioPoliciesBluetoothRouteController(mContext, bluetoothAdapter,
- mBluetoothProfileMonitor, mListener) {
- @Override
- boolean isDeviceConnected(BluetoothDevice device) {
- return true;
- }
- };
-
- // Enable A2DP profile.
- when(mBluetoothProfileMonitor.isProfileSupported(eq(BluetoothProfile.A2DP), any()))
- .thenReturn(true);
- mShadowBluetoothAdapter.setProfileConnectionState(BluetoothProfile.A2DP,
- BluetoothProfile.STATE_CONNECTED);
-
- mAudioPoliciesBluetoothRouteController.start(UserHandle.of(0));
- }
-
- @Test
- public void getSelectedRoute_noBluetoothRoutesAvailable_returnsNull() {
- assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
- }
-
- @Test
- public void selectRoute_noBluetoothRoutesAvailable_returnsFalse() {
- assertThat(mAudioPoliciesBluetoothRouteController
- .selectRoute(DEVICE_ADDRESS_UNKNOWN)).isFalse();
- }
-
- @Test
- public void selectRoute_noDeviceWithGivenAddress_returnsFalse() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_3);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- assertThat(mAudioPoliciesBluetoothRouteController
- .selectRoute(DEVICE_ADDRESS_SAMPLE_2)).isFalse();
- }
-
- @Test
- public void selectRoute_deviceIsInDevicesSet_returnsTrue() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- assertThat(mAudioPoliciesBluetoothRouteController
- .selectRoute(DEVICE_ADDRESS_SAMPLE_1)).isTrue();
- }
-
- @Test
- public void selectRoute_resetSelectedDevice_returnsTrue() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_1);
- assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
- }
-
- @Test
- public void selectRoute_noSelectedDevice_returnsTrue() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- assertThat(mAudioPoliciesBluetoothRouteController.selectRoute(null)).isTrue();
- }
-
- @Test
- public void getSelectedRoute_updateRouteFailed_returnsNull() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
- mAudioPoliciesBluetoothRouteController
- .selectRoute(DEVICE_ADDRESS_SAMPLE_3);
-
- assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
- }
-
- @Test
- public void getSelectedRoute_updateRouteSuccessful_returnsUpdateDevice() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
- assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute()).isNull();
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- assertThat(mAudioPoliciesBluetoothRouteController
- .selectRoute(DEVICE_ADDRESS_SAMPLE_4)).isTrue();
-
- MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
- assertThat(selectedRoute.getAddress()).isEqualTo(DEVICE_ADDRESS_SAMPLE_4);
- }
-
- @Test
- public void getSelectedRoute_resetSelectedRoute_returnsNull() {
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(
- DEVICE_ADDRESS_SAMPLE_1, DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4);
-
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- // Device is not null now.
- mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
- // Rest the device.
- mAudioPoliciesBluetoothRouteController.selectRoute(null);
-
- assertThat(mAudioPoliciesBluetoothRouteController.getSelectedRoute())
- .isNull();
- }
-
- @Test
- public void getTransferableRoutes_noSelectedRoute_returnsAllBluetoothDevices() {
- String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
- DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- // Force route controller to update bluetooth devices list.
- sendBluetoothDevicesChangedBroadcast();
-
- Set<String> transferableDevices = extractAddressesListFrom(
- mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
- assertThat(transferableDevices).containsExactlyElementsIn(addresses);
- }
-
- @Test
- public void getTransferableRoutes_hasSelectedRoute_returnsRoutesWithoutSelectedDevice() {
- String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
- DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- // Force route controller to update bluetooth devices list.
- sendBluetoothDevicesChangedBroadcast();
- mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
- Set<String> transferableDevices = extractAddressesListFrom(
- mAudioPoliciesBluetoothRouteController.getTransferableRoutes());
- assertThat(transferableDevices).containsExactly(DEVICE_ADDRESS_SAMPLE_1,
- DEVICE_ADDRESS_SAMPLE_2);
- }
-
- @Test
- public void getAllBluetoothRoutes_hasSelectedRoute_returnsAllRoutes() {
- String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
- DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- // Force route controller to update bluetooth devices list.
- sendBluetoothDevicesChangedBroadcast();
- mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
- Set<String> bluetoothDevices = extractAddressesListFrom(
- mAudioPoliciesBluetoothRouteController.getAllBluetoothRoutes());
- assertThat(bluetoothDevices).containsExactlyElementsIn(addresses);
- }
-
- @Test
- public void updateVolumeForDevice_setVolumeForA2DPTo25_selectedRouteVolumeIsUpdated() {
- String[] addresses = new String[] { DEVICE_ADDRESS_SAMPLE_1,
- DEVICE_ADDRESS_SAMPLE_2, DEVICE_ADDRESS_SAMPLE_4 };
- Set<BluetoothDevice> devices = generateFakeBluetoothDevicesSet(addresses);
- mShadowBluetoothAdapter.setBondedDevices(devices);
-
- // Force route controller to update bluetooth devices list.
- sendBluetoothDevicesChangedBroadcast();
- mAudioPoliciesBluetoothRouteController.selectRoute(DEVICE_ADDRESS_SAMPLE_4);
-
- mAudioPoliciesBluetoothRouteController.updateVolumeForDevices(
- AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, 25);
-
- MediaRoute2Info selectedRoute = mAudioPoliciesBluetoothRouteController.getSelectedRoute();
- assertThat(selectedRoute.getVolume()).isEqualTo(25);
- }
-
- private void sendBluetoothDevicesChangedBroadcast() {
- Intent intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
- mContext.sendBroadcast(intent);
- }
-
- private static Set<String> extractAddressesListFrom(Collection<MediaRoute2Info> routes) {
- Set<String> addresses = new HashSet<>();
-
- for (MediaRoute2Info route: routes) {
- addresses.add(route.getAddress());
- }
-
- return addresses;
- }
-
- private static Set<BluetoothDevice> generateFakeBluetoothDevicesSet(String... addresses) {
- Set<BluetoothDevice> devices = new HashSet<>();
-
- for (String address: addresses) {
- devices.add(ShadowBluetoothDevice.newInstance(address));
- }
-
- return devices;
- }
-}
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/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/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
deleted file mode 100644
index 5aef7a3..0000000
--- a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
+++ /dev/null
@@ -1,247 +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.
- */
-
-package com.android.server.media;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
-import android.media.IAudioRoutesObserver;
-import android.media.MediaRoute2Info;
-import android.os.RemoteException;
-
-import com.android.internal.R;
-import com.android.server.audio.AudioService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(JUnit4.class)
-public class AudioPoliciesDeviceRouteControllerTest {
-
- private static final String ROUTE_NAME_DEFAULT = "default";
- private static final String ROUTE_NAME_DOCK = "dock";
- private static final String ROUTE_NAME_HEADPHONES = "headphones";
-
- private static final int VOLUME_SAMPLE_1 = 25;
-
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private AudioManager mAudioManager;
- @Mock
- private AudioService mAudioService;
- @Mock
- private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
-
- @Captor
- private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
-
- private AudioPoliciesDeviceRouteController mController;
-
- private IAudioRoutesObserver.Stub mAudioRoutesObserver;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT);
-
- // Setting built-in speaker as default speaker.
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
- when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
- .thenReturn(audioRoutesInfo);
-
- mController = new AudioPoliciesDeviceRouteController(
- mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener);
-
- mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
- }
-
- @Test
- public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
- MediaRoute2Info route2Info = mController.getSelectedRoute();
-
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
- }
-
- @Test
- public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
- }
-
- @Test
- public void getDeviceRoute_selectDevice_returnsSelectedRoute() {
- when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
- .thenReturn(ROUTE_NAME_DOCK);
-
- mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
- }
-
- @Test
- public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
- when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
- .thenReturn(ROUTE_NAME_DOCK);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
- }
-
- @Test
- public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
- when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
- .thenReturn(ROUTE_NAME_DOCK);
-
- mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- mController.selectRoute(null);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
- }
-
- @Test
- public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
- }
-
- @Test
- public void selectRoute_selectWiredRoute_returnsTrue() {
- assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue();
- }
-
- @Test
- public void selectRoute_selectBluetoothRoute_returnsFalse() {
- assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse();
- }
-
- @Test
- public void selectRoute_unselectRoute_returnsTrue() {
- assertThat(mController.selectRoute(null)).isTrue();
- }
-
- @Test
- public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- mController.updateVolume(VOLUME_SAMPLE_1);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
- assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
- }
-
- @Test
- public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() {
- when(mResources.getText(R.string.default_audio_route_name_headphones))
- .thenReturn(ROUTE_NAME_HEADPHONES);
- when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
- .thenReturn(ROUTE_NAME_DOCK);
-
- AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
- audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
- callAudioRoutesObserver(audioRoutesInfo);
-
- mController.updateVolume(VOLUME_SAMPLE_1);
-
- mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
-
- MediaRoute2Info route2Info = mController.getSelectedRoute();
- assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
- assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
- }
-
- /**
- * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
- * from {@link AudioService}. This happens when there is a wired route change,
- * like a wired headset being connected.
- *
- * @param audioRoutesInfo updated state of connected wired device
- */
- private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
- try {
- // this is a captured observer implementation
- // from WiredRoutesController's AudioService#startWatchingRoutes call
- mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
- } catch (RemoteException exception) {
- // Should not happen since the object is mocked.
- assertWithMessage("An unexpected RemoteException happened.").fail();
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
index 14b121d..0961b7d 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -19,6 +19,7 @@
import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
import android.content.Context;
+import android.os.Looper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -56,7 +57,8 @@
@RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
DeviceRouteController deviceRouteController =
- DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+ DeviceRouteController.createInstance(
+ mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class);
}
@@ -65,7 +67,8 @@
@RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
DeviceRouteController deviceRouteController =
- DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+ DeviceRouteController.createInstance(
+ mContext, Looper.getMainLooper(), mOnDeviceRouteChangedListener);
Truth.assertThat(deviceRouteController)
.isInstanceOf(AudioPoliciesDeviceRouteController.class);
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 2d7f044..6853c4c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -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;
@@ -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/aapt2/Android.bp b/tools/aapt2/Android.bp
index 275a0e2..412aa9b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -92,6 +92,10 @@
srcs: [
"compile/IdAssigner.cpp",
"compile/InlineXmlFormatParser.cpp",
+ "compile/NinePatch.cpp",
+ "compile/Png.cpp",
+ "compile/PngChunkFilter.cpp",
+ "compile/PngCrunch.cpp",
"compile/PseudolocaleGenerator.cpp",
"compile/Pseudolocalizer.cpp",
"compile/XmlIdCollector.cpp",
@@ -108,7 +112,9 @@
"format/binary/XmlFlattener.cpp",
"format/proto/ProtoDeserialize.cpp",
"format/proto/ProtoSerialize.cpp",
+ "io/BigBufferStream.cpp",
"io/File.cpp",
+ "io/FileStream.cpp",
"io/FileSystem.cpp",
"io/StringStream.cpp",
"io/Util.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index d6502d8..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -18,12 +18,12 @@
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "androidfw/BigBufferStream.h"
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"
@@ -48,7 +48,7 @@
}
// First try in proto format.
- std::unique_ptr<android::InputStream> manifest_in = manifest_file->OpenInputStream();
+ std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
if (manifest_in != nullptr) {
pb::XmlNode pb_node;
io::ProtoInputStreamReader proto_reader(manifest_in.get());
@@ -102,7 +102,7 @@
io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
if (table_file != nullptr) {
pb::ResourceTable pb_table;
- std::unique_ptr<android::InputStream> in = table_file->OpenInputStream();
+ std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
if (in == nullptr) {
diag->Error(android::DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
return {};
@@ -129,7 +129,7 @@
return {};
}
- std::unique_ptr<android::InputStream> manifest_in = manifest_file->OpenInputStream();
+ std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
if (manifest_in == nullptr) {
diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath);
return {};
@@ -262,7 +262,7 @@
return false;
}
- android::BigBufferInputStream input_stream(&buffer);
+ io::BigBufferInputStream input_stream(&buffer);
if (!io::CopyInputStreamToArchive(context,
&input_stream,
path,
@@ -296,7 +296,7 @@
}
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
- android::BigBufferInputStream manifest_buffer_in(&buffer);
+ io::BigBufferInputStream manifest_buffer_in(&buffer);
if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
writer)) {
return false;
@@ -321,7 +321,7 @@
std::unique_ptr<xml::XmlResource> doc;
if (format_ == ApkFormat::kProto) {
- std::unique_ptr<android::InputStream> in = file->OpenInputStream();
+ std::unique_ptr<io::InputStream> in = file->OpenInputStream();
if (!in) {
diag->Error(android::DiagMessage() << "failed to open file");
return nullptr;
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index b351d6e..a0b4dab 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -27,7 +27,6 @@
#include "Diagnostics.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
-#include "androidfw/FileStream.h"
#include "androidfw/IDiagnostics.h"
#include "androidfw/StringPiece.h"
#include "cmd/ApkInfo.h"
@@ -38,6 +37,7 @@
#include "cmd/Dump.h"
#include "cmd/Link.h"
#include "cmd/Optimize.h"
+#include "io/FileStream.h"
#include "trace/TraceBuffer.h"
#include "util/Files.h"
#include "util/Util.h"
@@ -99,7 +99,7 @@
*/
class DaemonCommand : public Command {
public:
- explicit DaemonCommand(android::FileOutputStream* out, android::IDiagnostics* diagnostics)
+ explicit DaemonCommand(io::FileOutputStream* out, android::IDiagnostics* diagnostics)
: Command("daemon", "m"), out_(out), diagnostics_(diagnostics) {
SetDescription("Runs aapt in daemon mode. Each subsequent line is a single parameter to the\n"
"command. The end of an invocation is signaled by providing an empty line.");
@@ -147,7 +147,7 @@
}
private:
- android::FileOutputStream* out_;
+ io::FileOutputStream* out_;
android::IDiagnostics* diagnostics_;
std::optional<std::string> trace_folder_;
};
@@ -167,7 +167,7 @@
// Use a smaller buffer so that there is less latency for printing to stdout.
constexpr size_t kStdOutBufferSize = 1024u;
- android::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ aapt::io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
aapt::text::Printer printer(&fout);
aapt::StdErrDiagnostics diagnostics;
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 031dd5b..728ba8a 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -25,16 +25,13 @@
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/utf8.h"
-#include "androidfw/BigBufferStream.h"
#include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
#include "androidfw/IDiagnostics.h"
-#include "androidfw/Image.h"
-#include "androidfw/Png.h"
#include "androidfw/StringPiece.h"
#include "cmd/Util.h"
#include "compile/IdAssigner.h"
#include "compile/InlineXmlFormatParser.h"
+#include "compile/Png.h"
#include "compile/PseudolocaleGenerator.h"
#include "compile/XmlIdCollector.h"
#include "format/Archive.h"
@@ -42,6 +39,8 @@
#include "format/proto/ProtoSerialize.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
#include "io/FileSystem.h"
#include "io/StringStream.h"
#include "io/Util.h"
@@ -53,9 +52,9 @@
#include "xml/XmlDom.h"
#include "xml/XmlPullParser.h"
+using ::aapt::io::FileInputStream;
using ::aapt::text::Printer;
using ::android::ConfigDescription;
-using ::android::FileInputStream;
using ::android::StringPiece;
using ::android::base::SystemErrorCodeToString;
using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -242,7 +241,7 @@
}
if (options.generate_text_symbols_path) {
- android::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
if (fout_text.HadError()) {
context->GetDiagnostics()->Error(android::DiagMessage()
@@ -308,7 +307,7 @@
}
static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
- android::KnownSizeInputStream* in, IArchiveWriter* writer,
+ io::KnownSizeInputStream* in, IArchiveWriter* writer,
android::IDiagnostics* diag) {
TRACE_CALL();
// Start the entry so we can write the header.
@@ -449,7 +448,7 @@
}
if (options.generate_text_symbols_path) {
- android::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+ io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
if (fout_text.HadError()) {
context->GetDiagnostics()->Error(android::DiagMessage()
@@ -499,22 +498,21 @@
}
android::BigBuffer crunched_png_buffer(4096);
- android::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
+ io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
// Ensure that we only keep the chunks we care about if we end up
// using the original PNG instead of the crunched one.
const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
- android::PngChunkFilter png_chunk_filter(content);
- android::SourcePathDiagnostics source_diag(path_data.source, context->GetDiagnostics());
- auto image = android::ReadPng(&png_chunk_filter, &source_diag);
+ PngChunkFilter png_chunk_filter(content);
+ std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
if (!image) {
return false;
}
- std::unique_ptr<android::NinePatch> nine_patch;
+ std::unique_ptr<NinePatch> nine_patch;
if (path_data.extension == "9.png") {
std::string err;
- nine_patch = android::NinePatch::Create(image->rows.get(), image->width, image->height, &err);
+ nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
if (!nine_patch) {
context->GetDiagnostics()->Error(android::DiagMessage() << err);
return false;
@@ -539,8 +537,7 @@
}
// Write the crunched PNG.
- if (!android::WritePng(image.get(), nine_patch.get(), &crunched_png_buffer_out, {},
- &source_diag, context->IsVerbose())) {
+ if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
return false;
}
@@ -560,7 +557,7 @@
png_chunk_filter.Rewind();
android::BigBuffer filtered_png_buffer(4096);
- android::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
+ io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
buffer.AppendBuffer(std::move(filtered_png_buffer));
}
@@ -570,7 +567,7 @@
// This will help catch exotic cases where the new code may generate larger PNGs.
std::stringstream legacy_stream{std::string(content)};
android::BigBuffer legacy_buffer(4096);
- android::Png png(context->GetDiagnostics());
+ Png png(context->GetDiagnostics());
if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
return false;
}
@@ -581,7 +578,7 @@
}
}
- android::BigBufferInputStream buffer_in(&buffer);
+ io::BigBufferInputStream buffer_in(&buffer);
return WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
context->GetDiagnostics());
}
diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp
index 9337cb9..8880089 100644
--- a/tools/aapt2/cmd/Compile_test.cpp
+++ b/tools/aapt2/cmd/Compile_test.cpp
@@ -341,7 +341,7 @@
// Check resources.pb contains relative sources.
io::IFile* proto_file =
apk.get()->GetFileCollection()->FindFile("resources.pb");
- std::unique_ptr<android::InputStream> proto_stream = proto_file->OpenInputStream();
+ std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream();
io::ProtoInputStreamReader proto_reader(proto_stream.get());
pb::ResourceTable pb_table;
proto_reader.ReadMessage(&pb_table);
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index c132792..387dcfe 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -24,13 +24,13 @@
#include "android-base/file.h"
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
#include "androidfw/StringPiece.h"
#include "cmd/Util.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
@@ -80,7 +80,7 @@
return false;
}
- android::BigBufferInputStream input_stream(&buffer);
+ io::BigBufferInputStream input_stream(&buffer);
return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
}
@@ -91,14 +91,14 @@
return false;
}
- android::BigBufferInputStream input_stream(&buffer);
+ io::BigBufferInputStream input_stream(&buffer);
return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
ArchiveEntry::kAlign, writer);
}
bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
if (file->type == ResourceFile::Type::kProtoXml) {
- unique_ptr<android::InputStream> in = file->file->OpenInputStream();
+ unique_ptr<io::InputStream> in = file->file->OpenInputStream();
if (in == nullptr) {
context_->GetDiagnostics()->Error(android::DiagMessage(source_)
<< "failed to open file " << *file->path);
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 6fa9ecb..864af06 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -19,18 +19,19 @@
#include <cinttypes>
#include <vector>
+#include "android-base/stringprintf.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
+
#include "Debug.h"
#include "Diagnostics.h"
#include "LoadedApk.h"
#include "Util.h"
-#include "android-base/stringprintf.h"
-#include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
-#include "androidfw/StringPiece.h"
#include "format/Container.h"
#include "format/binary/BinaryResourceParser.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
+#include "io/FileStream.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "text/Printer.h"
@@ -144,7 +145,7 @@
bool error = false;
for (auto container : args) {
- android::FileInputStream input(container);
+ io::FileInputStream input(container);
if (input.HadError()) {
context.GetDiagnostics()->Error(android::DiagMessage(container)
<< "failed to open file: " << input.GetError());
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 119a59b..76d33d7 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -17,7 +17,7 @@
#ifndef AAPT2_DUMP_H
#define AAPT2_DUMP_H
-#include <androidfw/FileStream.h>
+#include <io/FileStream.h>
#include <io/ZipArchive.h>
#include "Command.h"
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 9ca546f..c638873 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -35,8 +35,6 @@
#include "android-base/expected.h"
#include "android-base/file.h"
#include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
-#include "androidfw/FileStream.h"
#include "androidfw/IDiagnostics.h"
#include "androidfw/Locale.h"
#include "androidfw/StringPiece.h"
@@ -50,6 +48,8 @@
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
#include "io/FileSystem.h"
#include "io/Util.h"
#include "io/ZipArchive.h"
@@ -73,8 +73,8 @@
#include "util/Files.h"
#include "xml/XmlDom.h"
+using ::aapt::io::FileInputStream;
using ::android::ConfigDescription;
-using ::android::FileInputStream;
using ::android::StringPiece;
using ::android::base::expected;
using ::android::base::StringPrintf;
@@ -263,7 +263,7 @@
return false;
}
- android::BigBufferInputStream input_stream(&buffer);
+ io::BigBufferInputStream input_stream(&buffer);
return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
writer);
} break;
@@ -284,7 +284,7 @@
static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path,
android::IDiagnostics* diag) {
TRACE_CALL();
- android::FileInputStream fin(path);
+ FileInputStream fin(path);
if (fin.HadError()) {
diag->Error(android::DiagMessage(path) << "failed to load XML file: " << fin.GetError());
return {};
@@ -687,7 +687,7 @@
static bool WriteStableIdMapToPath(android::IDiagnostics* diag,
const std::unordered_map<ResourceName, ResourceId>& id_map,
const std::string& id_map_path) {
- android::FileOutputStream fout(id_map_path);
+ io::FileOutputStream fout(id_map_path);
if (fout.HadError()) {
diag->Error(android::DiagMessage(id_map_path) << "failed to open: " << fout.GetError());
return false;
@@ -1197,7 +1197,7 @@
return false;
}
- android::BigBufferInputStream input_stream(&buffer);
+ io::BigBufferInputStream input_stream(&buffer);
return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
ArchiveEntry::kAlign, writer);
} break;
@@ -1221,7 +1221,7 @@
}
std::string out_path;
- std::unique_ptr<android::FileOutputStream> fout;
+ std::unique_ptr<io::FileOutputStream> fout;
if (options_.generate_java_class_path) {
out_path = options_.generate_java_class_path.value();
file::AppendPath(&out_path, file::PackageToPath(out_package));
@@ -1233,7 +1233,7 @@
file::AppendPath(&out_path, "R.java");
- fout = util::make_unique<android::FileOutputStream>(out_path);
+ fout = util::make_unique<io::FileOutputStream>(out_path);
if (fout->HadError()) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed writing to '" << out_path
@@ -1242,9 +1242,9 @@
}
}
- std::unique_ptr<android::FileOutputStream> fout_text;
+ std::unique_ptr<io::FileOutputStream> fout_text;
if (out_text_symbols_path) {
- fout_text = util::make_unique<android::FileOutputStream>(out_text_symbols_path.value());
+ fout_text = util::make_unique<io::FileOutputStream>(out_text_symbols_path.value());
if (fout_text->HadError()) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed writing to '" << out_text_symbols_path.value()
@@ -1386,7 +1386,7 @@
file::AppendPath(&out_path, "Manifest.java");
- android::FileOutputStream fout(out_path);
+ io::FileOutputStream fout(out_path);
if (fout.HadError()) {
context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path
<< "': " << fout.GetError());
@@ -1412,7 +1412,7 @@
}
const std::string& out_path = out.value();
- android::FileOutputStream fout(out_path);
+ io::FileOutputStream fout(out_path);
if (fout.HadError()) {
context_->GetDiagnostics()->Error(android::DiagMessage() << "failed to open '" << out_path
<< "': " << fout.GetError());
@@ -1601,7 +1601,7 @@
}
}
- std::unique_ptr<android::InputStream> input_stream = file->OpenInputStream();
+ std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream();
if (input_stream == nullptr) {
context_->GetDiagnostics()->Error(android::DiagMessage(src) << "failed to open file");
return false;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 762441e..f045dad 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -30,7 +30,6 @@
#include "ValueVisitor.h"
#include "android-base/file.h"
#include "android-base/stringprintf.h"
-#include "androidfw/BigBufferStream.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/IDiagnostics.h"
#include "androidfw/ResourceTypes.h"
@@ -40,6 +39,7 @@
#include "filter/AbiFilter.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
+#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "optimize/MultiApkGenerator.h"
#include "optimize/Obfuscator.h"
@@ -249,7 +249,7 @@
return false;
}
- android::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
+ io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
ArchiveEntry::kCompress, writer)) {
return false;
@@ -297,7 +297,7 @@
return false;
}
- android::BigBufferInputStream table_buffer_in(&table_buffer);
+ io::BigBufferInputStream table_buffer_in(&table_buffer);
return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
ArchiveEntry::kAlign, writer);
}
diff --git a/libs/androidfw/include/androidfw/Image.h b/tools/aapt2/compile/Image.h
similarity index 92%
rename from libs/androidfw/include/androidfw/Image.h
rename to tools/aapt2/compile/Image.h
index c18c34c..db0b945 100644
--- a/libs/androidfw/include/androidfw/Image.h
+++ b/tools/aapt2/compile/Image.h
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#pragma once
+#ifndef AAPT_COMPILE_IMAGE_H
+#define AAPT_COMPILE_IMAGE_H
#include <cstdint>
#include <memory>
@@ -23,7 +24,7 @@
#include "android-base/macros.h"
-namespace android {
+namespace aapt {
/**
* An in-memory image, loaded from disk, with pixels in RGBA_8888 format.
@@ -36,7 +37,7 @@
* A `height` sized array of pointers, where each element points to a
* `width` sized row of RGBA_8888 pixels.
*/
- std::unique_ptr<uint8_t*[]> rows;
+ std::unique_ptr<uint8_t* []> rows;
/**
* The width of the image in RGBA_8888 pixels. This is int32_t because of
@@ -71,8 +72,7 @@
int32_t end = 0;
explicit Range() = default;
- inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {
- }
+ inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {}
};
inline bool operator==(const Range& left, const Range& right) {
@@ -93,8 +93,7 @@
explicit Bounds() = default;
inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b)
- : left(l), top(t), right(r), bottom(b) {
- }
+ : left(l), top(t), right(r), bottom(b) {}
bool nonZero() const;
};
@@ -104,8 +103,8 @@
}
inline bool operator==(const Bounds& left, const Bounds& right) {
- return left.left == right.left && left.top == right.top && left.right == right.right &&
- left.bottom == right.bottom;
+ return left.left == right.left && left.top == right.top &&
+ left.right == right.right && left.bottom == right.bottom;
}
/**
@@ -116,7 +115,8 @@
class NinePatch {
public:
static std::unique_ptr<NinePatch> Create(uint8_t** rows, const int32_t width,
- const int32_t height, std::string* err_out);
+ const int32_t height,
+ std::string* err_out);
/**
* Packs the RGBA_8888 data pointed to by pixel into a uint32_t
@@ -204,4 +204,6 @@
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds);
::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch);
-} // namespace android
\ No newline at end of file
+} // namespace aapt
+
+#endif /* AAPT_COMPILE_IMAGE_H */
diff --git a/libs/androidfw/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
similarity index 80%
rename from libs/androidfw/NinePatch.cpp
rename to tools/aapt2/compile/NinePatch.cpp
index 1fdbebf..4538ecc 100644
--- a/libs/androidfw/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -14,17 +14,20 @@
* limitations under the License.
*/
+#include "compile/Image.h"
+
#include <sstream>
#include <string>
#include <vector>
-#include "androidfw/Image.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
+#include "util/Util.h"
+
using android::StringPiece;
-namespace android {
+namespace aapt {
// Colors in the format 0xAARRGGBB (the way 9-patch expects it).
constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu;
@@ -87,8 +90,10 @@
// };
//
template <typename ImageLine>
-static bool FillRanges(const ImageLine* image_line, const ColorValidator* color_validator,
- std::vector<Range>* primary_ranges, std::vector<Range>* secondary_ranges,
+static bool FillRanges(const ImageLine* image_line,
+ const ColorValidator* color_validator,
+ std::vector<Range>* primary_ranges,
+ std::vector<Range>* secondary_ranges,
std::string* out_err) {
const int32_t length = image_line->GetLength();
@@ -128,13 +133,11 @@
*/
class HorizontalImageLine {
public:
- explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
- : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
- }
+ explicit HorizontalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
- inline int32_t GetLength() const {
- return length_;
- }
+ inline int32_t GetLength() const { return length_; }
inline uint32_t GetColor(int32_t idx) const {
return NinePatch::PackRGBA(rows_[yoffset_] + (idx + xoffset_) * 4);
@@ -153,13 +156,11 @@
*/
class VerticalImageLine {
public:
- explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t length)
- : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {
- }
+ explicit VerticalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t length)
+ : rows_(rows), xoffset_(xoffset), yoffset_(yoffset), length_(length) {}
- inline int32_t GetLength() const {
- return length_;
- }
+ inline int32_t GetLength() const { return length_; }
inline uint32_t GetColor(int32_t idx) const {
return NinePatch::PackRGBA(rows_[yoffset_ + idx] + (xoffset_ * 4));
@@ -174,22 +175,20 @@
class DiagonalImageLine {
public:
- explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset, int32_t xstep,
- int32_t ystep, int32_t length)
+ explicit DiagonalImageLine(uint8_t** rows, int32_t xoffset, int32_t yoffset,
+ int32_t xstep, int32_t ystep, int32_t length)
: rows_(rows),
xoffset_(xoffset),
yoffset_(yoffset),
xstep_(xstep),
ystep_(ystep),
- length_(length) {
- }
+ length_(length) {}
- inline int32_t GetLength() const {
- return length_;
- }
+ inline int32_t GetLength() const { return length_; }
inline uint32_t GetColor(int32_t idx) const {
- return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] + ((idx + xoffset_) * xstep_) * 4);
+ return NinePatch::PackRGBA(rows_[yoffset_ + (idx * ystep_)] +
+ ((idx + xoffset_) * xstep_) * 4);
}
private:
@@ -244,7 +243,8 @@
if (layout_bounds.size() > 2) {
std::stringstream err_stream;
- err_stream << "too many layout bounds sections on " << edge_name << " border";
+ err_stream << "too many layout bounds sections on " << edge_name
+ << " border";
*out_err = err_stream.str();
return false;
}
@@ -258,7 +258,8 @@
// end at length.
if (range.start != 0 && range.end != length) {
std::stringstream err_stream;
- err_stream << "layout bounds on " << edge_name << " border must start at edge";
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
*out_err = err_stream.str();
return false;
}
@@ -268,7 +269,8 @@
const Range& range = layout_bounds.back();
if (range.end != length) {
std::stringstream err_stream;
- err_stream << "layout bounds on " << edge_name << " border must start at edge";
+ err_stream << "layout bounds on " << edge_name
+ << " border must start at edge";
*out_err = err_stream.str();
return false;
}
@@ -278,7 +280,8 @@
return true;
}
-static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions, int32_t length) {
+static int32_t CalculateSegmentCount(const std::vector<Range>& stretch_regions,
+ int32_t length) {
if (stretch_regions.size() == 0) {
return 0;
}
@@ -296,7 +299,8 @@
static uint32_t GetRegionColor(uint8_t** rows, const Bounds& region) {
// Sample the first pixel to compare against.
- const uint32_t expected_color = NinePatch::PackRGBA(rows[region.top] + region.left * 4);
+ const uint32_t expected_color =
+ NinePatch::PackRGBA(rows[region.top] + region.left * 4);
for (int32_t y = region.top; y < region.bottom; y++) {
const uint8_t* row = rows[y];
for (int32_t x = region.left; x < region.right; x++) {
@@ -332,11 +336,10 @@
// the indices must be offset by 1.
//
// width and height also include the 9-patch 1px border.
-static void CalculateRegionColors(uint8_t** rows,
- const std::vector<Range>& horizontal_stretch_regions,
- const std::vector<Range>& vertical_stretch_regions,
- const int32_t width, const int32_t height,
- std::vector<uint32_t>* out_colors) {
+static void CalculateRegionColors(
+ uint8_t** rows, const std::vector<Range>& horizontal_stretch_regions,
+ const std::vector<Range>& vertical_stretch_regions, const int32_t width,
+ const int32_t height, std::vector<uint32_t>* out_colors) {
int32_t next_top = 0;
Bounds bounds;
auto row_iter = vertical_stretch_regions.begin();
@@ -398,7 +401,8 @@
// alpha value begins
// (on both sides).
template <typename ImageLine>
-static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start, int32_t* out_end) {
+static void FindOutlineInsets(const ImageLine* image_line, int32_t* out_start,
+ int32_t* out_end) {
*out_start = 0;
*out_end = 0;
@@ -451,8 +455,10 @@
return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2];
}
-std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows, const int32_t width,
- const int32_t height, std::string* out_err) {
+std::unique_ptr<NinePatch> NinePatch::Create(uint8_t** rows,
+ const int32_t width,
+ const int32_t height,
+ std::string* out_err) {
if (width < 3 || height < 3) {
*out_err = "image must be at least 3x3 (1x1 image with 1 pixel border)";
return {};
@@ -466,11 +472,12 @@
std::unique_ptr<ColorValidator> color_validator;
if (rows[0][3] == 0) {
- color_validator = std::make_unique<TransparentNeutralColorValidator>();
+ color_validator = util::make_unique<TransparentNeutralColorValidator>();
} else if (PackRGBA(rows[0]) == kColorOpaqueWhite) {
- color_validator = std::make_unique<WhiteNeutralColorValidator>();
+ color_validator = util::make_unique<WhiteNeutralColorValidator>();
} else {
- *out_err = "top-left corner pixel must be either opaque white or transparent";
+ *out_err =
+ "top-left corner pixel must be either opaque white or transparent";
return {};
}
@@ -478,8 +485,9 @@
auto nine_patch = std::unique_ptr<NinePatch>(new NinePatch());
HorizontalImageLine top_row(rows, 0, 0, width);
- if (!FillRanges(&top_row, color_validator.get(), &nine_patch->horizontal_stretch_regions,
- &unexpected_ranges, out_err)) {
+ if (!FillRanges(&top_row, color_validator.get(),
+ &nine_patch->horizontal_stretch_regions, &unexpected_ranges,
+ out_err)) {
return {};
}
@@ -493,8 +501,9 @@
}
VerticalImageLine left_col(rows, 0, 0, height);
- if (!FillRanges(&left_col, color_validator.get(), &nine_patch->vertical_stretch_regions,
- &unexpected_ranges, out_err)) {
+ if (!FillRanges(&left_col, color_validator.get(),
+ &nine_patch->vertical_stretch_regions, &unexpected_ranges,
+ out_err)) {
return {};
}
@@ -513,28 +522,32 @@
}
if (!PopulateBounds(horizontal_padding, horizontal_layout_bounds,
- nine_patch->horizontal_stretch_regions, width - 2, &nine_patch->padding.left,
- &nine_patch->padding.right, &nine_patch->layout_bounds.left,
+ nine_patch->horizontal_stretch_regions, width - 2,
+ &nine_patch->padding.left, &nine_patch->padding.right,
+ &nine_patch->layout_bounds.left,
&nine_patch->layout_bounds.right, "bottom", out_err)) {
return {};
}
VerticalImageLine right_col(rows, width - 1, 0, height);
- if (!FillRanges(&right_col, color_validator.get(), &vertical_padding, &vertical_layout_bounds,
- out_err)) {
+ if (!FillRanges(&right_col, color_validator.get(), &vertical_padding,
+ &vertical_layout_bounds, out_err)) {
return {};
}
if (!PopulateBounds(vertical_padding, vertical_layout_bounds,
- nine_patch->vertical_stretch_regions, height - 2, &nine_patch->padding.top,
- &nine_patch->padding.bottom, &nine_patch->layout_bounds.top,
+ nine_patch->vertical_stretch_regions, height - 2,
+ &nine_patch->padding.top, &nine_patch->padding.bottom,
+ &nine_patch->layout_bounds.top,
&nine_patch->layout_bounds.bottom, "right", out_err)) {
return {};
}
// Fill the region colors of the 9-patch.
- const int32_t num_rows = CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
- const int32_t num_cols = CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
+ const int32_t num_rows =
+ CalculateSegmentCount(nine_patch->horizontal_stretch_regions, width - 2);
+ const int32_t num_cols =
+ CalculateSegmentCount(nine_patch->vertical_stretch_regions, height - 2);
if ((int64_t)num_rows * (int64_t)num_cols > 0x7f) {
*out_err = "too many regions in 9-patch";
return {};
@@ -542,35 +555,40 @@
nine_patch->region_colors.reserve(num_rows * num_cols);
CalculateRegionColors(rows, nine_patch->horizontal_stretch_regions,
- nine_patch->vertical_stretch_regions, width - 2, height - 2,
- &nine_patch->region_colors);
+ nine_patch->vertical_stretch_regions, width - 2,
+ height - 2, &nine_patch->region_colors);
// Compute the outline based on opacity.
// Find left and right extent of 9-patch content on center row.
HorizontalImageLine mid_row(rows, 1, height / 2, width - 2);
- FindOutlineInsets(&mid_row, &nine_patch->outline.left, &nine_patch->outline.right);
+ FindOutlineInsets(&mid_row, &nine_patch->outline.left,
+ &nine_patch->outline.right);
// Find top and bottom extent of 9-patch content on center column.
VerticalImageLine mid_col(rows, width / 2, 1, height - 2);
- FindOutlineInsets(&mid_col, &nine_patch->outline.top, &nine_patch->outline.bottom);
+ FindOutlineInsets(&mid_col, &nine_patch->outline.top,
+ &nine_patch->outline.bottom);
- const int32_t outline_width = (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
+ const int32_t outline_width =
+ (width - 2) - nine_patch->outline.left - nine_patch->outline.right;
const int32_t outline_height =
(height - 2) - nine_patch->outline.top - nine_patch->outline.bottom;
// Find the largest alpha value within the outline area.
- HorizontalImageLine outline_mid_row(rows, 1 + nine_patch->outline.left,
- 1 + nine_patch->outline.top + (outline_height / 2),
- outline_width);
- VerticalImageLine outline_mid_col(rows, 1 + nine_patch->outline.left + (outline_width / 2),
- 1 + nine_patch->outline.top, outline_height);
+ HorizontalImageLine outline_mid_row(
+ rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top + (outline_height / 2), outline_width);
+ VerticalImageLine outline_mid_col(
+ rows, 1 + nine_patch->outline.left + (outline_width / 2),
+ 1 + nine_patch->outline.top, outline_height);
nine_patch->outline_alpha =
std::max(FindMaxAlpha(&outline_mid_row), FindMaxAlpha(&outline_mid_col));
// Assuming the image is a round rect, compute the radius by marching
// diagonally from the top left corner towards the center.
- DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left, 1 + nine_patch->outline.top, 1, 1,
+ DiagonalImageLine diagonal(rows, 1 + nine_patch->outline.left,
+ 1 + nine_patch->outline.top, 1, 1,
std::min(outline_width, outline_height));
int32_t top_left, bottom_right;
FindOutlineInsets(&diagonal, &top_left, &bottom_right);
@@ -596,9 +614,10 @@
data.paddingBottom = padding.bottom;
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]);
- android::Res_png_9patch::serialize(data, (const int32_t*)horizontal_stretch_regions.data(),
- (const int32_t*)vertical_stretch_regions.data(),
- region_colors.data(), buffer.get());
+ android::Res_png_9patch::serialize(
+ data, (const int32_t*)horizontal_stretch_regions.data(),
+ (const int32_t*)vertical_stretch_regions.data(), region_colors.data(),
+ buffer.get());
// Convert to file endianness.
reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile();
@@ -606,7 +625,8 @@
return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(size_t* out_len) const {
+std::unique_ptr<uint8_t[]> NinePatch::SerializeLayoutBounds(
+ size_t* out_len) const {
size_t chunk_len = sizeof(uint32_t) * 4;
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
uint8_t* cursor = buffer.get();
@@ -627,7 +647,8 @@
return buffer;
}
-std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(size_t* out_len) const {
+std::unique_ptr<uint8_t[]> NinePatch::SerializeRoundedRectOutline(
+ size_t* out_len) const {
size_t chunk_len = sizeof(uint32_t) * 6;
auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunk_len]);
uint8_t* cursor = buffer.get();
@@ -658,25 +679,20 @@
}
::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) {
- return out << "l=" << bounds.left << " t=" << bounds.top << " r=" << bounds.right
- << " b=" << bounds.bottom;
-}
-
-template <typename T>
-std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
- for (int i = 0; i < v.size(); ++i) {
- os << v[i];
- if (i != v.size() - 1) os << " ";
- }
- return os;
+ return out << "l=" << bounds.left << " t=" << bounds.top
+ << " r=" << bounds.right << " b=" << bounds.bottom;
}
::std::ostream& operator<<(::std::ostream& out, const NinePatch& nine_patch) {
- return out << "horizontalStretch:" << nine_patch.horizontal_stretch_regions
- << " verticalStretch:" << nine_patch.vertical_stretch_regions
- << " padding: " << nine_patch.padding << ", bounds: " << nine_patch.layout_bounds
- << ", outline: " << nine_patch.outline << " rad=" << nine_patch.outline_radius
+ return out << "horizontalStretch:"
+ << util::Joiner(nine_patch.horizontal_stretch_regions, " ")
+ << " verticalStretch:"
+ << util::Joiner(nine_patch.vertical_stretch_regions, " ")
+ << " padding: " << nine_patch.padding
+ << ", bounds: " << nine_patch.layout_bounds
+ << ", outline: " << nine_patch.outline
+ << " rad=" << nine_patch.outline_radius
<< " alpha=" << nine_patch.outline_alpha;
}
-} // namespace android
+} // namespace aapt
diff --git a/libs/androidfw/tests/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp
similarity index 69%
rename from libs/androidfw/tests/NinePatch_test.cpp
rename to tools/aapt2/compile/NinePatch_test.cpp
index 7ee8e9e..f54bb2e 100644
--- a/libs/androidfw/tests/NinePatch_test.cpp
+++ b/tools/aapt2/compile/NinePatch_test.cpp
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#include "androidfw/Image.h"
-#include "androidfw/ResourceTypes.h"
-#include "gtest/gtest.h"
+#include "compile/Image.h"
-namespace android {
+#include "test/Test.h"
+
+namespace aapt {
// Pixels are in RGBA_8888 packing.
@@ -33,19 +33,16 @@
#define TRANS "\x00\x00\x00\x00"
static uint8_t* k2x2[] = {
- (uint8_t*)WHITE WHITE,
- (uint8_t*)WHITE WHITE,
+ (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE,
};
static uint8_t* kMixedNeutralColor3x3[] = {
- (uint8_t*)WHITE BLACK TRANS,
- (uint8_t*)TRANS RED TRANS,
+ (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS,
(uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kTransparentNeutralColor3x3[] = {
- (uint8_t*)TRANS BLACK TRANS,
- (uint8_t*)BLACK RED BLACK,
+ (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK,
(uint8_t*)TRANS BLACK TRANS,
};
@@ -69,44 +66,55 @@
};
static uint8_t* kPadding6x5[] = {
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE,
(uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE,
};
static uint8_t* kLayoutBoundsWrongEdge3x3[] = {
- (uint8_t*)WHITE RED WHITE,
- (uint8_t*)RED WHITE WHITE,
+ (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE,
(uint8_t*)WHITE WHITE WHITE,
};
static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = {
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
- (uint8_t*)WHITE WHITE WHITE WHITE RED, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
(uint8_t*)WHITE WHITE RED WHITE WHITE,
};
static uint8_t* kLayoutBounds5x5[] = {
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
(uint8_t*)WHITE RED WHITE RED WHITE,
};
static uint8_t* kAsymmetricLayoutBounds5x5[] = {
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
(uint8_t*)WHITE RED WHITE WHITE WHITE,
};
static uint8_t* kPaddingAndLayoutBounds5x5[] = {
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE, (uint8_t*)WHITE WHITE WHITE WHITE RED,
- (uint8_t*)WHITE WHITE WHITE WHITE BLACK, (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
+ (uint8_t*)WHITE WHITE WHITE WHITE BLACK,
+ (uint8_t*)WHITE WHITE WHITE WHITE RED,
(uint8_t*)WHITE RED BLACK RED WHITE,
};
static uint8_t* kColorfulImage5x5[] = {
- (uint8_t*)WHITE BLACK WHITE BLACK WHITE, (uint8_t*)BLACK RED BLUE GREEN WHITE,
- (uint8_t*)BLACK RED GREEN GREEN WHITE, (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
+ (uint8_t*)WHITE BLACK WHITE BLACK WHITE,
+ (uint8_t*)BLACK RED BLUE GREEN WHITE,
+ (uint8_t*)BLACK RED GREEN GREEN WHITE,
+ (uint8_t*)WHITE TRANS BLUE GREEN WHITE,
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
@@ -137,21 +145,33 @@
};
static uint8_t* kOutlineOffsetTranslucent12x10[] = {
- (uint8_t*)WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
- (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
- (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE,
+ (uint8_t*)
+ WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE,
};
static uint8_t* kOutlineRadius5x5[] = {
- (uint8_t*)WHITE BLACK BLACK BLACK WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
- (uint8_t*)BLACK GREEN GREEN GREEN WHITE, (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)WHITE BLACK BLACK BLACK WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
+ (uint8_t*)BLACK GREEN GREEN GREEN WHITE,
+ (uint8_t*)BLACK TRANS GREEN TRANS WHITE,
(uint8_t*)WHITE WHITE WHITE WHITE WHITE,
};
@@ -175,12 +195,14 @@
TEST(NinePatchTest, TransparentNeutralColor) {
std::string err;
- EXPECT_NE(nullptr, NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
+ EXPECT_NE(nullptr,
+ NinePatch::Create(kTransparentNeutralColor3x3, 3, 3, &err));
}
TEST(NinePatchTest, SingleStretchRegion) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kSingleStretch7x6, 7, 6, &err);
ASSERT_NE(nullptr, nine_patch);
ASSERT_EQ(1u, nine_patch->horizontal_stretch_regions.size());
@@ -192,7 +214,8 @@
TEST(NinePatchTest, MultipleStretchRegions) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
ASSERT_NE(nullptr, nine_patch);
ASSERT_EQ(3u, nine_patch->horizontal_stretch_regions.size());
@@ -208,14 +231,16 @@
TEST(NinePatchTest, InferPaddingFromStretchRegions) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kMultipleStretch10x7, 10, 7, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(1, 0, 1, 0), nine_patch->padding);
}
TEST(NinePatchTest, Padding) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPadding6x5, 6, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPadding6x5, 6, 5, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
}
@@ -228,13 +253,15 @@
TEST(NinePatchTest, LayoutBoundsMustTouchEdges) {
std::string err;
- EXPECT_EQ(nullptr, NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
+ EXPECT_EQ(nullptr,
+ NinePatch::Create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err));
EXPECT_FALSE(err.empty());
}
TEST(NinePatchTest, LayoutBounds) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kLayoutBounds5x5, 5, 5, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
@@ -245,7 +272,8 @@
TEST(NinePatchTest, PaddingAndLayoutBounds) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kPaddingAndLayoutBounds5x5, 5, 5, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->padding);
EXPECT_EQ(Bounds(1, 1, 1, 1), nine_patch->layout_bounds);
@@ -253,20 +281,25 @@
TEST(NinePatchTest, RegionColorsAreCorrect) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kColorfulImage5x5, 5, 5, &err);
ASSERT_NE(nullptr, nine_patch);
std::vector<uint32_t> expected_colors = {
- NinePatch::PackRGBA((uint8_t*)RED), (uint32_t)android::Res_png_9patch::NO_COLOR,
- NinePatch::PackRGBA((uint8_t*)GREEN), (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
- NinePatch::PackRGBA((uint8_t*)BLUE), NinePatch::PackRGBA((uint8_t*)GREEN),
+ NinePatch::PackRGBA((uint8_t*)RED),
+ (uint32_t)android::Res_png_9patch::NO_COLOR,
+ NinePatch::PackRGBA((uint8_t*)GREEN),
+ (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR,
+ NinePatch::PackRGBA((uint8_t*)BLUE),
+ NinePatch::PackRGBA((uint8_t*)GREEN),
};
EXPECT_EQ(expected_colors, nine_patch->region_colors);
}
TEST(NinePatchTest, OutlineFromOpaqueImage) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineOpaque10x10, 10, 10, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(2, 2, 2, 2), nine_patch->outline);
EXPECT_EQ(0x000000ffu, nine_patch->outline_alpha);
@@ -275,7 +308,8 @@
TEST(NinePatchTest, OutlineFromTranslucentImage) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineTranslucent10x10, 10, 10, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(3, 3, 3, 3), nine_patch->outline);
EXPECT_EQ(0x000000b3u, nine_patch->outline_alpha);
@@ -303,7 +337,8 @@
TEST(NinePatchTest, OutlineRadius) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kOutlineRadius5x5, 5, 5, &err);
ASSERT_NE(nullptr, nine_patch);
EXPECT_EQ(Bounds(0, 0, 0, 0), nine_patch->outline);
EXPECT_EQ(3.4142f, nine_patch->outline_radius);
@@ -318,7 +353,8 @@
TEST(NinePatchTest, SerializePngEndianness) {
std::string err;
- std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
+ std::unique_ptr<NinePatch> nine_patch =
+ NinePatch::Create(kStretchAndPadding5x5, 5, 5, &err);
ASSERT_NE(nullptr, nine_patch);
size_t len;
@@ -338,4 +374,4 @@
EXPECT_TRUE(BigEndianOne(cursor + 12));
}
-} // namespace android
+} // namespace aapt
diff --git a/libs/androidfw/Png.cpp b/tools/aapt2/compile/Png.cpp
similarity index 77%
rename from libs/androidfw/Png.cpp
rename to tools/aapt2/compile/Png.cpp
index fb45cd9..76db815 100644
--- a/libs/androidfw/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "androidfw/Png.h"
+#include "Png.h"
#include <png.h>
#include <zlib.h>
@@ -24,12 +24,13 @@
#include <string>
#include <vector>
-#include "android-base/strings.h"
#include "androidfw/BigBuffer.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Source.h"
+#include "trace/TraceBuffer.h"
+#include "util/Util.h"
-namespace android {
+namespace aapt {
constexpr bool kDebug = false;
@@ -46,8 +47,9 @@
}
void* serialize9Patch() {
- void* serialized = Res_png_9patch::serialize(info9Patch, xDivs, yDivs, colors.data());
- reinterpret_cast<Res_png_9patch*>(serialized)->deviceToFile();
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs,
+ yDivs, colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
return serialized;
}
@@ -56,7 +58,7 @@
std::vector<png_bytep> rows;
bool is9Patch = false;
- Res_png_9patch info9Patch;
+ android::Res_png_9patch info9Patch;
int32_t* xDivs = nullptr;
int32_t* yDivs = nullptr;
std::vector<uint32_t> colors;
@@ -77,30 +79,34 @@
uint8_t outlineAlpha;
};
-static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
- std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+static void readDataFromStream(png_structp readPtr, png_bytep data,
+ png_size_t length) {
+ std::istream* input =
+ reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
if (!input->read(reinterpret_cast<char*>(data), length)) {
png_error(readPtr, strerror(errno));
}
}
-static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+static void writeDataToStream(png_structp writePtr, png_bytep data,
+ png_size_t length) {
+ android::BigBuffer* outBuffer = reinterpret_cast<android::BigBuffer*>(png_get_io_ptr(writePtr));
png_bytep buf = outBuffer->NextBlock<png_byte>(length);
memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp /*writePtr*/) {
-}
+static void flushDataToStream(png_structp /*writePtr*/) {}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
- IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr));
- diag->Warn(DiagMessage() << warningMessage);
+ android::IDiagnostics* diag =
+ reinterpret_cast<android::IDiagnostics*>(png_get_error_ptr(readPtr));
+ diag->Warn(android::DiagMessage() << warningMessage);
}
-static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) {
+static bool readPng(android::IDiagnostics* diag, png_structp readPtr, png_infop infoPtr,
+ PngInfo* outInfo) {
if (setjmp(png_jmpbuf(readPtr))) {
- diag->Error(DiagMessage() << "failed reading png");
+ diag->Error(android::DiagMessage() << "failed reading png");
return false;
}
@@ -108,8 +114,8 @@
png_read_info(readPtr, infoPtr);
int colorType, bitDepth, interlaceType, compressionType;
- png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
- &interlaceType, &compressionType, nullptr);
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth,
+ &colorType, &interlaceType, &compressionType, nullptr);
if (colorType == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(readPtr);
@@ -131,7 +137,8 @@
png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
}
- if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(readPtr);
}
@@ -149,11 +156,12 @@
return true;
}
-static void checkNinePatchSerialization(Res_png_9patch* inPatch, void* data) {
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,
+ void* data) {
size_t patchSize = inPatch->serializedSize();
void* newData = malloc(patchSize);
memcpy(newData, data, patchSize);
- Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
outPatch->fileToDevice();
// deserialization is done in place, so outPatch == newData
assert(outPatch == newData);
@@ -236,9 +244,10 @@
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(a) ((a) < 0 ? -(a) : (a))
-static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance,
- png_colorp rgbPalette, png_bytep alphaPalette, int* paletteEntries,
- bool* hasTransparency, int* colorType, png_bytepp outRows) {
+static void analyze_image(android::IDiagnostics* diag, const PngInfo& imageInfo,
+ int grayscaleTolerance, png_colorp rgbPalette, png_bytep alphaPalette,
+ int* paletteEntries, bool* hasTransparency, int* colorType,
+ png_bytepp outRows) {
int w = imageInfo.width;
int h = imageInfo.height;
int i, j, rr, gg, bb, aa, idx;
@@ -275,8 +284,8 @@
maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
if (maxGrayDeviation > odev) {
if (kDebug) {
- printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", maxGrayDeviation, i, j,
- rr, gg, bb, aa);
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
}
}
@@ -284,7 +293,8 @@
if (isGrayscale) {
if (rr != gg || rr != bb) {
if (kDebug) {
- printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
}
isGrayscale = false;
}
@@ -294,7 +304,8 @@
if (isOpaque) {
if (aa != 0xff) {
if (kDebug) {
- printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, rr, gg, bb, aa);
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j,
+ rr, gg, bb, aa);
}
isOpaque = false;
}
@@ -338,9 +349,10 @@
printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
printf("isOpaque = %s\n", isOpaque ? "true" : "false");
printf("isPalette = %s\n", isPalette ? "true" : "false");
- printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, 2 * w * h,
- bpp * w * h);
- printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize,
+ 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation,
+ grayscaleTolerance);
}
// Choose the best color type for the image.
@@ -369,8 +381,8 @@
*colorType = PNG_COLOR_TYPE_PALETTE;
} else {
if (maxGrayDeviation <= grayscaleTolerance) {
- diag->Note(DiagMessage() << "forcing image to gray (max deviation = " << maxGrayDeviation
- << ")");
+ diag->Note(android::DiagMessage()
+ << "forcing image to gray (max deviation = " << maxGrayDeviation << ")");
*colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
} else {
*colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
@@ -392,7 +404,8 @@
rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff);
alphaPalette[idx] = (png_byte)(col & 0xff);
}
- } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY ||
+ *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
// If the image is gray or gray + alpha, compact the pixels into outRows
for (j = 0; j < h; j++) {
const png_byte* row = imageInfo.rows[j];
@@ -416,10 +429,10 @@
}
}
-static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info,
- int grayScaleTolerance) {
+static bool writePng(android::IDiagnostics* diag, png_structp writePtr, png_infop infoPtr,
+ PngInfo* info, int grayScaleTolerance) {
if (setjmp(png_jmpbuf(writePtr))) {
- diag->Error(DiagMessage() << "failed to write png");
+ diag->Error(android::DiagMessage() << "failed to write png");
return false;
}
@@ -431,7 +444,8 @@
unknowns[1].data = nullptr;
unknowns[2].data = nullptr;
- png_bytepp outRows = (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
+ png_bytepp outRows =
+ (png_bytepp)malloc((int)info->height * sizeof(png_bytep));
if (outRows == (png_bytepp)0) {
printf("Can't allocate output buffer!\n");
exit(1);
@@ -447,7 +461,8 @@
png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
if (kDebug) {
- diag->Note(DiagMessage() << "writing image: w = " << info->width << ", h = " << info->height);
+ diag->Note(android::DiagMessage()
+ << "writing image: w = " << info->width << ", h = " << info->height);
}
png_color rgbPalette[256];
@@ -455,45 +470,48 @@
bool hasTransparency;
int paletteEntries;
- analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, &paletteEntries,
- &hasTransparency, &colorType, outRows);
+ analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &colorType, outRows);
// If the image is a 9-patch, we need to preserve it as a ARGB file to make
// sure the pixels will not be pre-dithered/clamped until we decide they are
- if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
- colorType == PNG_COLOR_TYPE_PALETTE)) {
+ if (info->is9Patch &&
+ (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_PALETTE)) {
colorType = PNG_COLOR_TYPE_RGB_ALPHA;
}
if (kDebug) {
switch (colorType) {
case PNG_COLOR_TYPE_PALETTE:
- diag->Note(DiagMessage() << "has " << paletteEntries << " colors"
- << (hasTransparency ? " (with alpha)" : "")
- << ", using PNG_COLOR_TYPE_PALLETTE");
+ diag->Note(android::DiagMessage() << "has " << paletteEntries << " colors"
+ << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE");
break;
case PNG_COLOR_TYPE_GRAY:
- diag->Note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
+ diag->Note(android::DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY");
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
- diag->Note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
+ diag->Note(android::DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA");
break;
case PNG_COLOR_TYPE_RGB:
- diag->Note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
+ diag->Note(android::DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB");
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
- diag->Note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
+ diag->Note(android::DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA");
break;
}
}
- png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, PNG_INTERLACE_NONE,
- PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
if (colorType == PNG_COLOR_TYPE_PALETTE) {
png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
if (hasTransparency) {
- png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p)0);
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries,
+ (png_color_16p)0);
}
png_set_filter(writePtr, 0, PNG_NO_FILTERS);
} else {
@@ -508,12 +526,13 @@
// Chunks ordered thusly because older platforms depend on the base 9 patch
// data being last
- png_bytep chunkNames =
- info->haveLayoutBounds ? (png_bytep) "npOl\0npLb\0npTc\0" : (png_bytep) "npOl\0npTc";
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep) "npOl\0npLb\0npTc\0"
+ : (png_bytep) "npOl\0npTc";
// base 9 patch data
if (kDebug) {
- diag->Note(DiagMessage() << "adding 9-patch info..");
+ diag->Note(android::DiagMessage() << "adding 9-patch info..");
}
memcpy((char*)unknowns[pIndex].name, "npTc", 5);
unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
@@ -544,7 +563,8 @@
for (int i = 0; i < chunkCount; i++) {
unknowns[i].location = PNG_HAVE_PLTE;
}
- png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, chunkCount);
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames,
+ chunkCount);
png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
#if PNG_LIBPNG_VER < 10600
@@ -559,7 +579,8 @@
png_write_info(writePtr, infoPtr);
png_bytepp rows;
- if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
if (colorType == PNG_COLOR_TYPE_RGB) {
png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
}
@@ -584,13 +605,14 @@
free(unknowns[1].data);
free(unknowns[2].data);
- png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
- &compressionType, nullptr);
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
if (kDebug) {
- diag->Note(DiagMessage() << "image written: w = " << width << ", h = " << height
- << ", d = " << bitDepth << ", colors = " << colorType
- << ", inter = " << interlaceType << ", comp = " << compressionType);
+ diag->Note(android::DiagMessage()
+ << "image written: w = " << width << ", h = " << height << ", d = " << bitDepth
+ << ", colors = " << colorType << ", inter = " << interlaceType
+ << ", comp = " << compressionType);
}
return true;
}
@@ -651,8 +673,9 @@
enum class TickState { kStart, kInside1, kOutside1 };
-static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
- int32_t* outLeft, int32_t* outRight, const char** outError,
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent,
+ bool required, int32_t* outLeft,
+ int32_t* outRight, const char** outError,
uint8_t* outDivs, bool multipleAllowed) {
*outLeft = *outRight = -1;
TickState state = TickState::kStart;
@@ -660,7 +683,8 @@
for (int i = 1; i < width - 1; i++) {
if (tickType(row + i * 4, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
*outLeft = i - 1;
*outRight = width - 2;
found = true;
@@ -695,16 +719,18 @@
return true;
}
-static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool required, int32_t* outTop, int32_t* outBottom,
- const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+static bool getVerticalTicks(png_bytepp rows, int offset, int height,
+ bool transparent, bool required, int32_t* outTop,
+ int32_t* outBottom, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
*outTop = *outBottom = -1;
TickState state = TickState::kStart;
bool found = false;
for (int i = 1; i < height - 1; i++) {
if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) {
- if (state == TickState::kStart || (state == TickState::kOutside1 && multipleAllowed)) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
*outTop = i - 1;
*outBottom = height - 2;
found = true;
@@ -739,8 +765,10 @@
return true;
}
-static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
- bool /* required */, int32_t* outLeft, int32_t* outRight,
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width,
+ bool transparent,
+ bool /* required */,
+ int32_t* outLeft, int32_t* outRight,
const char** outError) {
*outLeft = *outRight = 0;
@@ -751,20 +779,23 @@
while (i < width - 1) {
(*outLeft)++;
i++;
- if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
break;
}
}
}
// Look for right tick
- if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+ if (tickType(row + (width - 2) * 4, transparent, outError) ==
+ TickType::kLayoutBounds) {
// Ending with a layout padding tick
int i = width - 2;
while (i > 1) {
(*outRight)++;
i--;
- if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ if (tickType(row + i * 4, transparent, outError) !=
+ TickType::kLayoutBounds) {
break;
}
}
@@ -772,32 +803,38 @@
return true;
}
-static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
- bool /* required */, int32_t* outTop, int32_t* outBottom,
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset,
+ int height, bool transparent,
+ bool /* required */, int32_t* outTop,
+ int32_t* outBottom,
const char** outError) {
*outTop = *outBottom = 0;
// Look for top tick
- if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ if (tickType(rows[1] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
// Starting with a layout padding tick
int i = 1;
while (i < height - 1) {
(*outTop)++;
i++;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
break;
}
}
}
// Look for bottom tick
- if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ if (tickType(rows[height - 2] + offset, transparent, outError) ==
+ TickType::kLayoutBounds) {
// Ending with a layout padding tick
int i = height - 2;
while (i > 1) {
(*outBottom)++;
i--;
- if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ if (tickType(rows[i] + offset, transparent, outError) !=
+ TickType::kLayoutBounds) {
break;
}
}
@@ -805,12 +842,13 @@
return true;
}
-static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, int dX,
- int dY, int* outInset) {
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX,
+ int endY, int dX, int dY, int* outInset) {
uint8_t maxOpacity = 0;
int inset = 0;
*outInset = 0;
- for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ for (int x = startX, y = startY; x != endX && y != endY;
+ x += dX, y += dY, inset++) {
png_byte* color = rows[y] + x * 4;
uint8_t opacity = color[3];
if (opacity > maxOpacity) {
@@ -830,7 +868,8 @@
return maxAlpha;
}
-static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY,
+ int endY) {
uint8_t maxAlpha = 0;
for (int y = startY; y < endY; y++) {
uint8_t alpha = (rows[y] + offsetX * 4)[3];
@@ -847,8 +886,10 @@
// find left and right extent of nine patch content on center row
if (image->width > 4) {
- findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
- findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, &image->outlineInsetsRight);
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0,
+ &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
} else {
image->outlineInsetsLeft = 0;
image->outlineInsetsRight = 0;
@@ -856,8 +897,10 @@
// find top and bottom extent of nine patch content on center column
if (image->height > 4) {
- findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
- findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, &image->outlineInsetsBottom);
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1,
+ &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
} else {
image->outlineInsetsTop = 0;
image->outlineInsetsBottom = 0;
@@ -872,13 +915,13 @@
// assuming the image is a round rect, compute the radius by marching
// diagonally from the top left corner towards the center
- image->outlineAlpha =
- std::max(maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
- maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
int diagonalInset = 0;
- findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
- &diagonalInset);
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX,
+ innerMidY, 1, 1, &diagonalInset);
/* Determine source radius based upon inset:
* sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
@@ -889,17 +932,19 @@
image->outlineRadius = 3.4142f * diagonalInset;
if (kDebug) {
- printf("outline insets %d %d %d %d, rad %f, alpha %x\n", image->outlineInsetsLeft,
- image->outlineInsetsTop, image->outlineInsetsRight, image->outlineInsetsBottom,
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft, image->outlineInsetsTop,
+ image->outlineInsetsRight, image->outlineInsetsBottom,
image->outlineRadius, image->outlineAlpha);
}
}
-static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+static uint32_t getColor(png_bytepp rows, int left, int top, int right,
+ int bottom) {
png_bytep color = rows[top] + left * 4;
if (left > right || top > bottom) {
- return Res_png_9patch::TRANSPARENT_COLOR;
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
}
while (top <= bottom) {
@@ -907,17 +952,18 @@
png_bytep p = rows[top] + i * 4;
if (color[3] == 0) {
if (p[3] != 0) {
- return Res_png_9patch::NO_COLOR;
+ return android::Res_png_9patch::NO_COLOR;
}
- } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || p[3] != color[3]) {
- return Res_png_9patch::NO_COLOR;
+ } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] ||
+ p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
}
}
top++;
}
if (color[3] == 0) {
- return Res_png_9patch::TRANSPARENT_COLOR;
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
}
return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2];
}
@@ -968,22 +1014,23 @@
}
// Validate frame...
- if (!transparent && (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
errorMsg = "Must have one-pixel frame that is either transparent or white";
goto getout;
}
// Find left and right of sizing areas...
- if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
- true)) {
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1],
+ &errorMsg, &numXDivs, true)) {
errorPixel = xDivs[0];
errorEdge = "top";
goto getout;
}
// Find top and bottom of sizing areas...
- if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
- &errorMsg, &numYDivs, true)) {
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0],
+ &yDivs[1], &errorMsg, &numYDivs, true)) {
errorPixel = yDivs[0];
errorEdge = "left";
goto getout;
@@ -994,8 +1041,10 @@
image->info9Patch.numYDivs = numYDivs;
// Find left and right of padding area...
- if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, &image->info9Patch.paddingLeft,
- &image->info9Patch.paddingRight, &errorMsg, nullptr, false)) {
+ if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false,
+ &image->info9Patch.paddingLeft,
+ &image->info9Patch.paddingRight, &errorMsg, nullptr,
+ false)) {
errorPixel = image->info9Patch.paddingLeft;
errorEdge = "bottom";
goto getout;
@@ -1003,8 +1052,9 @@
// Find top and bottom of padding area...
if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
- &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, &errorMsg,
- nullptr, false)) {
+ &image->info9Patch.paddingTop,
+ &image->info9Patch.paddingBottom, &errorMsg, nullptr,
+ false)) {
errorPixel = image->info9Patch.paddingTop;
errorEdge = "right";
goto getout;
@@ -1012,18 +1062,22 @@
// Find left and right of layout padding...
getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false,
- &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+ &image->layoutBoundsLeft,
+ &image->layoutBoundsRight, &errorMsg);
- getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, false,
- &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent,
+ false, &image->layoutBoundsTop,
+ &image->layoutBoundsBottom, &errorMsg);
- image->haveLayoutBounds = image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
- image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
+ image->haveLayoutBounds =
+ image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 ||
+ image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0;
if (image->haveLayoutBounds) {
if (kDebug) {
- printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
- image->layoutBoundsRight, image->layoutBoundsBottom);
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft,
+ image->layoutBoundsTop, image->layoutBoundsRight,
+ image->layoutBoundsBottom);
}
}
@@ -1135,7 +1189,7 @@
c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
image->colors[colorIndex++] = c;
if (kDebug) {
- if (c != Res_png_9patch::NO_COLOR) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
hasColor = true;
}
}
@@ -1160,7 +1214,8 @@
if (errorEdge) {
err << "." << std::endl;
if (errorPixel >= 0) {
- err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge
+ << " edge";
} else {
err << "Found along " << errorEdge << " edge";
}
@@ -1171,19 +1226,20 @@
return true;
}
-bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+bool Png::process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer,
const PngOptions& options) {
+ TRACE_CALL();
png_byte signature[kPngSignatureSize];
// Read the PNG signature first.
if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
- mDiag->Error(DiagMessage() << strerror(errno));
+ mDiag->Error(android::DiagMessage() << strerror(errno));
return false;
}
// If the PNG signature doesn't match, bail early.
if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- mDiag->Error(DiagMessage() << "not a valid png file");
+ mDiag->Error(android::DiagMessage() << "not a valid png file");
return false;
}
@@ -1196,17 +1252,18 @@
readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
if (!readPtr) {
- mDiag->Error(DiagMessage() << "failed to allocate read ptr");
+ mDiag->Error(android::DiagMessage() << "failed to allocate read ptr");
goto bail;
}
infoPtr = png_create_info_struct(readPtr);
if (!infoPtr) {
- mDiag->Error(DiagMessage() << "failed to allocate info ptr");
+ mDiag->Error(android::DiagMessage() << "failed to allocate info ptr");
goto bail;
}
- png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning);
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr,
+ logWarning);
// Set the read function to read from std::istream.
png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream);
@@ -1215,32 +1272,35 @@
goto bail;
}
- if (android::base::EndsWith(source.path, ".9.png")) {
+ if (util::EndsWith(source.path, ".9.png")) {
std::string errorMsg;
if (!do9Patch(&pngInfo, &errorMsg)) {
- mDiag->Error(DiagMessage() << errorMsg);
+ mDiag->Error(android::DiagMessage() << errorMsg);
goto bail;
}
}
- writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ writePtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
if (!writePtr) {
- mDiag->Error(DiagMessage() << "failed to allocate write ptr");
+ mDiag->Error(android::DiagMessage() << "failed to allocate write ptr");
goto bail;
}
writeInfoPtr = png_create_info_struct(writePtr);
if (!writeInfoPtr) {
- mDiag->Error(DiagMessage() << "failed to allocate write info ptr");
+ mDiag->Error(android::DiagMessage() << "failed to allocate write info ptr");
goto bail;
}
png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
// Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream,
+ flushDataToStream);
- if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayscale_tolerance)) {
+ if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo,
+ options.grayscale_tolerance)) {
goto bail;
}
@@ -1256,4 +1316,4 @@
return result;
}
-} // namespace android
+} // namespace aapt
diff --git a/libs/androidfw/include/androidfw/Png.h b/tools/aapt2/compile/Png.h
similarity index 62%
rename from libs/androidfw/include/androidfw/Png.h
rename to tools/aapt2/compile/Png.h
index 2ece43e..a8b7dd1 100644
--- a/libs/androidfw/include/androidfw/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -14,18 +14,22 @@
* limitations under the License.
*/
-#pragma once
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
+#include <iostream>
#include <string>
-#include "BigBuffer.h"
-#include "IDiagnostics.h"
-#include "Image.h"
-#include "Source.h"
-#include "Streams.h"
#include "android-base/macros.h"
+#include "androidfw/BigBuffer.h"
+#include "androidfw/IDiagnostics.h"
+#include "androidfw/Source.h"
+#include "compile/Image.h"
+#include "io/Io.h"
+#include "process/IResourceTableConsumer.h"
-namespace android {
+namespace aapt {
+
// Size in bytes of the PNG signature.
constexpr size_t kPngSignatureSize = 8u;
@@ -38,36 +42,32 @@
*/
class Png {
public:
- explicit Png(IDiagnostics* diag) : mDiag(diag) {
+ explicit Png(android::IDiagnostics* diag) : mDiag(diag) {
}
- bool process(const Source& source, std::istream* input, BigBuffer* outBuffer,
+ bool process(const android::Source& source, std::istream* input, android::BigBuffer* outBuffer,
const PngOptions& options);
private:
DISALLOW_COPY_AND_ASSIGN(Png);
- IDiagnostics* mDiag;
+ android::IDiagnostics* mDiag;
};
/**
* An InputStream that filters out unimportant PNG chunks.
*/
-class PngChunkFilter : public InputStream {
+class PngChunkFilter : public io::InputStream {
public:
- explicit PngChunkFilter(StringPiece data);
+ explicit PngChunkFilter(android::StringPiece data);
virtual ~PngChunkFilter() = default;
bool Next(const void** buffer, size_t* len) override;
void BackUp(size_t count) override;
- bool CanRewind() const override {
- return true;
- }
+ bool CanRewind() const override { return true; }
bool Rewind() override;
- size_t ByteCount() const override {
- return window_start_;
- }
+ size_t ByteCount() const override { return window_start_; }
bool HadError() const override {
return !error_msg_.empty();
@@ -81,20 +81,26 @@
bool ConsumeWindow(const void** buffer, size_t* len);
- StringPiece data_;
+ android::StringPiece data_;
size_t window_start_ = 0;
size_t window_end_ = 0;
std::string error_msg_;
};
+
/**
* Reads a PNG from the InputStream into memory as an RGBA Image.
*/
-std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag);
+std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source,
+ io::InputStream* in);
/**
* Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream
* as a PNG.
*/
-bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
- const PngOptions& options, IDiagnostics* diag, bool verbose);
-} // namespace android
\ No newline at end of file
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, io::OutputStream* out,
+ const PngOptions& options);
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/libs/androidfw/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
similarity index 95%
rename from libs/androidfw/PngChunkFilter.cpp
rename to tools/aapt2/compile/PngChunkFilter.cpp
index 331b948..2e55d0c 100644
--- a/libs/androidfw/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -14,22 +14,25 @@
* limitations under the License.
*/
+#include "compile/Png.h"
+
#include "android-base/stringprintf.h"
-#include "android-base/strings.h"
-#include "androidfw/Png.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
+#include "io/Io.h"
+
+using ::android::StringPiece;
using ::android::base::StringPrintf;
-namespace android {
+namespace aapt {
static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
// Useful helper function that encodes individual bytes into a uint32
// at compile time.
constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
- return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | ((uint32_t)d);
+ return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
+ ((uint32_t)d);
}
// Allow list of PNG chunk types that we want to keep in the resulting PNG.
@@ -68,7 +71,7 @@
}
PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
- if (android::base::StartsWith(data_, kPngSignature)) {
+ if (util::StartsWith(data_, kPngSignature)) {
window_start_ = 0;
window_end_ = kPngSignatureSize;
} else {
@@ -173,4 +176,5 @@
window_end_ = kPngSignatureSize;
return true;
}
-} // namespace android
+
+} // namespace aapt
diff --git a/libs/androidfw/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp
similarity index 87%
rename from libs/androidfw/PngCrunch.cpp
rename to tools/aapt2/compile/PngCrunch.cpp
index cf3c0ee..4ef87ba 100644
--- a/libs/androidfw/PngCrunch.cpp
+++ b/tools/aapt2/compile/PngCrunch.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "compile/Png.h"
+
#include <png.h>
#include <zlib.h>
@@ -24,16 +26,16 @@
#include "android-base/errors.h"
#include "android-base/logging.h"
#include "android-base/macros.h"
-#include "androidfw/Png.h"
-namespace android {
+#include "trace/TraceBuffer.h"
+
+namespace aapt {
// Custom deleter that destroys libpng read and info structs.
class PngReadStructDeleter {
public:
PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr)
- : read_ptr_(read_ptr), info_ptr_(info_ptr) {
- }
+ : read_ptr_(read_ptr), info_ptr_(info_ptr) {}
~PngReadStructDeleter() {
png_destroy_read_struct(&read_ptr_, &info_ptr_, nullptr);
@@ -50,8 +52,7 @@
class PngWriteStructDeleter {
public:
PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr)
- : write_ptr_(write_ptr), info_ptr_(info_ptr) {
- }
+ : write_ptr_(write_ptr), info_ptr_(info_ptr) {}
~PngWriteStructDeleter() {
png_destroy_write_struct(&write_ptr_, &info_ptr_);
@@ -82,7 +83,7 @@
}
static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
- InputStream* in = (InputStream*)png_get_io_ptr(png_ptr);
+ io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr);
const void* in_buffer;
size_t in_len;
@@ -107,7 +108,7 @@
}
static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) {
- OutputStream* out = (OutputStream*)png_get_io_ptr(png_ptr);
+ io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr);
void* out_buffer;
size_t out_len;
@@ -142,22 +143,28 @@
}
}
-std::unique_ptr<Image> ReadPng(InputStream* in, IDiagnostics* diag) {
+std::unique_ptr<Image> ReadPng(IAaptContext* context, const android::Source& source,
+ io::InputStream* in) {
+ TRACE_CALL();
+ // Create a diagnostics that has the source information encoded.
+ android::SourcePathDiagnostics source_diag(source, context->GetDiagnostics());
+
// Read the first 8 bytes of the file looking for the PNG signature.
// Bail early if it does not match.
const png_byte* signature;
size_t buffer_size;
if (!in->Next((const void**)&signature, &buffer_size)) {
if (in->HadError()) {
- diag->Error(android::DiagMessage() << "failed to read PNG signature: " << in->GetError());
+ source_diag.Error(android::DiagMessage()
+ << "failed to read PNG signature: " << in->GetError());
} else {
- diag->Error(android::DiagMessage() << "not enough data for PNG signature");
+ source_diag.Error(android::DiagMessage() << "not enough data for PNG signature");
}
return {};
}
if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
- diag->Error(android::DiagMessage() << "file signature does not match PNG signature");
+ source_diag.Error(android::DiagMessage() << "file signature does not match PNG signature");
return {};
}
@@ -169,14 +176,14 @@
// version of libpng.
png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (read_ptr == nullptr) {
- diag->Error(android::DiagMessage() << "failed to create libpng read png_struct");
+ source_diag.Error(android::DiagMessage() << "failed to create libpng read png_struct");
return {};
}
// Create and initialize the memory for image header and data.
png_infop info_ptr = png_create_info_struct(read_ptr);
if (info_ptr == nullptr) {
- diag->Error(android::DiagMessage() << "failed to create libpng read png_info");
+ source_diag.Error(android::DiagMessage() << "failed to create libpng read png_info");
png_destroy_read_struct(&read_ptr, nullptr, nullptr);
return {};
}
@@ -192,7 +199,7 @@
}
// Handle warnings ourselves via IDiagnostics.
- png_set_error_fn(read_ptr, (png_voidp)&diag, LogError, LogWarning);
+ png_set_error_fn(read_ptr, (png_voidp)&source_diag, LogError, LogWarning);
// Set up the read functions which read from our custom data sources.
png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream);
@@ -206,8 +213,8 @@
// Extract image meta-data from the various chunk headers.
uint32_t width, height;
int bit_depth, color_type, interlace_method, compression_method, filter_method;
- png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method,
- &compression_method, &filter_method);
+ png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
+ &interlace_method, &compression_method, &filter_method);
// When the image is read, expand it so that it is in RGBA 8888 format
// so that image handling is uniform.
@@ -232,7 +239,8 @@
png_set_add_alpha(read_ptr, 0xFF, PNG_FILLER_AFTER);
}
- if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(read_ptr);
}
@@ -248,12 +256,12 @@
// something
// that can always be represented by 9-patch.
if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) {
- diag->Error(android::DiagMessage()
- << "PNG image dimensions are too large: " << width << "x" << height);
+ source_diag.Error(android::DiagMessage()
+ << "PNG image dimensions are too large: " << width << "x" << height);
return {};
}
- std::unique_ptr<Image> output_image = std::make_unique<Image>();
+ std::unique_ptr<Image> output_image = util::make_unique<Image>();
output_image->width = static_cast<int32_t>(width);
output_image->height = static_cast<int32_t>(height);
@@ -264,7 +272,7 @@
output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]);
// Create an array of rows that index into the data block.
- output_image->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]);
+ output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]);
for (uint32_t h = 0; h < height; h++) {
output_image->rows[h] = output_image->data.get() + (h * row_bytes);
}
@@ -324,7 +332,8 @@
// This grayscale has alpha and can fit within a palette.
// See if it is worth fitting into a palette.
const size_t palette_threshold = palette_chunk_size + alpha_chunk_size +
- palette_data_chunk_size + kPaletteOverheadConstant;
+ palette_data_chunk_size +
+ kPaletteOverheadConstant;
if (grayscale_alpha_data_chunk_size > palette_threshold) {
return PNG_COLOR_TYPE_PALETTE;
}
@@ -334,14 +343,16 @@
if (color_palette_size <= 256 && !has_nine_patch) {
// This image can fit inside a palette. Let's see if it is worth it.
- size_t total_size_with_palette = palette_data_chunk_size + palette_chunk_size;
+ size_t total_size_with_palette =
+ palette_data_chunk_size + palette_chunk_size;
size_t total_size_without_palette = color_data_chunk_size;
if (alpha_palette_size > 0) {
total_size_with_palette += alpha_palette_size;
total_size_without_palette = color_alpha_data_chunk_size;
}
- if (total_size_without_palette > total_size_with_palette + kPaletteOverheadConstant) {
+ if (total_size_without_palette >
+ total_size_with_palette + kPaletteOverheadConstant) {
return PNG_COLOR_TYPE_PALETTE;
}
}
@@ -471,22 +482,26 @@
png_set_unknown_chunks(write_ptr, write_info_ptr, unknown_chunks, index);
}
-bool WritePng(const Image* image, const NinePatch* nine_patch, OutputStream* out,
- const PngOptions& options, IDiagnostics* diag, bool verbose) {
+bool WritePng(IAaptContext* context, const Image* image,
+ const NinePatch* nine_patch, io::OutputStream* out,
+ const PngOptions& options) {
+ TRACE_CALL();
// Create and initialize the write png_struct with the default error and
// warning handlers.
// The header version is also passed in to ensure that this was built against the same
// version of libpng.
png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (write_ptr == nullptr) {
- diag->Error(android::DiagMessage() << "failed to create libpng write png_struct");
+ context->GetDiagnostics()->Error(android::DiagMessage()
+ << "failed to create libpng write png_struct");
return false;
}
// Allocate memory to store image header data.
png_infop write_info_ptr = png_create_info_struct(write_ptr);
if (write_info_ptr == nullptr) {
- diag->Error(android::DiagMessage() << "failed to create libpng write png_info");
+ context->GetDiagnostics()->Error(android::DiagMessage()
+ << "failed to create libpng write png_info");
png_destroy_write_struct(&write_ptr, nullptr);
return false;
}
@@ -501,7 +516,7 @@
}
// Handle warnings with our IDiagnostics.
- png_set_error_fn(write_ptr, (png_voidp)&diag, LogError, LogWarning);
+ png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning);
// Set up the write functions which write to our custom data sources.
png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr);
@@ -563,21 +578,22 @@
}
}
- if (verbose) {
+ if (context->IsVerbose()) {
android::DiagMessage msg;
- msg << " paletteSize=" << color_palette.size() << " alphaPaletteSize=" << alpha_palette.size()
+ msg << " paletteSize=" << color_palette.size()
+ << " alphaPaletteSize=" << alpha_palette.size()
<< " maxGrayDeviation=" << max_gray_deviation
<< " grayScale=" << (grayscale ? "true" : "false");
- diag->Note(msg);
+ context->GetDiagnostics()->Note(msg);
}
const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance;
- const int new_color_type =
- PickColorType(image->width, image->height, grayscale, convertible_to_grayscale,
- nine_patch != nullptr, color_palette.size(), alpha_palette.size());
+ const int new_color_type = PickColorType(
+ image->width, image->height, grayscale, convertible_to_grayscale,
+ nine_patch != nullptr, color_palette.size(), alpha_palette.size());
- if (verbose) {
+ if (context->IsVerbose()) {
android::DiagMessage msg;
msg << "encoding PNG ";
if (nine_patch) {
@@ -603,11 +619,12 @@
msg << "unknown type " << new_color_type;
break;
}
- diag->Note(msg);
+ context->GetDiagnostics()->Note(msg);
}
- png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8, new_color_type,
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_set_IHDR(write_ptr, write_info_ptr, image->width, image->height, 8,
+ new_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
if (new_color_type & PNG_COLOR_MASK_PALETTE) {
// Assigns indices to the palette, and writes the encoded palette to the
@@ -649,9 +666,11 @@
}
png_write_row(write_ptr, out_row.get());
}
- } else if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ } else if (new_color_type == PNG_COLOR_TYPE_GRAY ||
+ new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
const size_t bpp = new_color_type == PNG_COLOR_TYPE_GRAY ? 1 : 2;
- auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
+ auto out_row =
+ std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]);
for (int32_t y = 0; y < image->height; y++) {
png_const_bytep in_row = image->rows[y];
@@ -672,7 +691,8 @@
// The image is convertible to grayscale, use linear-luminance of
// sRGB colorspace:
// https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
- out_row[x * bpp] = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ out_row[x * bpp] =
+ (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
}
if (bpp == 2) {
@@ -727,4 +747,4 @@
return true;
}
-} // namespace android
+} // namespace aapt
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index a596229..a2b4818 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -29,8 +29,8 @@
#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "androidfw/ConfigDescription.h"
-#include "androidfw/FileStream.h"
#include "io/File.h"
+#include "io/FileStream.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 91768a0..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -91,7 +91,7 @@
return true;
}
- bool WriteFile(StringPiece path, uint32_t flags, android::InputStream* in) override {
+ bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
if (!StartEntry(path, flags)) {
return false;
}
@@ -182,7 +182,7 @@
return true;
}
- bool WriteFile(StringPiece path, uint32_t flags, android::InputStream* in) override {
+ bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
while (true) {
if (!StartEntry(path, flags)) {
return false;
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 3c3d0ab..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -24,9 +24,9 @@
#include "androidfw/BigBuffer.h"
#include "androidfw/IDiagnostics.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+#include "io/Io.h"
#include "util/Files.h"
namespace aapt {
@@ -46,7 +46,7 @@
public:
virtual ~IArchiveWriter() = default;
- virtual bool WriteFile(android::StringPiece path, uint32_t flags, android::InputStream* in) = 0;
+ virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
// Starts a new entry and allows caller to write bytes to it sequentially.
// Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index df105f8..fd50af9 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -95,7 +95,7 @@
void VerifyZipFile(const std::string& output_path, const std::string& file, const uint8_t array[]) {
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
- std::unique_ptr<android::InputStream> stream = zip->FindFile(file)->OpenInputStream();
+ std::unique_ptr<io::InputStream> stream = zip->FindFile(file)->OpenInputStream();
std::vector<uint8_t> buffer;
const void* data;
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
index cb4a225..1ff6c49 100644
--- a/tools/aapt2/format/Container.cpp
+++ b/tools/aapt2/format/Container.cpp
@@ -94,7 +94,7 @@
}
bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
- android::KnownSizeInputStream* in) {
+ io::KnownSizeInputStream* in) {
if (current_entry_count_ >= total_entry_count_) {
error_ = "too many entries being serialized";
return false;
@@ -264,7 +264,7 @@
return reader_->GetError();
}
-ContainerReader::ContainerReader(android::InputStream* in)
+ContainerReader::ContainerReader(io::InputStream* in)
: in_(in),
adaptor_(in),
coded_in_(&adaptor_),
diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h
index c5d5676..121c592 100644
--- a/tools/aapt2/format/Container.h
+++ b/tools/aapt2/format/Container.h
@@ -22,9 +22,9 @@
#include "Resources.pb.h"
#include "ResourcesInternal.pb.h"
#include "androidfw/BigBuffer.h"
-#include "androidfw/Streams.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream.h"
+#include "io/Io.h"
#include "io/Util.h"
namespace aapt {
@@ -39,7 +39,7 @@
explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count);
bool AddResTableEntry(const pb::ResourceTable& table);
- bool AddResFileEntry(const pb::internal::CompiledFile& file, android::KnownSizeInputStream* in);
+ bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in);
bool HadError() const;
std::string GetError() const;
@@ -79,7 +79,7 @@
class ContainerReader {
public:
- explicit ContainerReader(android::InputStream* in);
+ explicit ContainerReader(io::InputStream* in);
ContainerReaderEntry* Next();
@@ -91,7 +91,7 @@
friend class ContainerReaderEntry;
- android::InputStream* in_;
+ io::InputStream* in_;
io::ZeroCopyInputAdaptor adaptor_;
::google::protobuf::io::CodedInputStream coded_in_;
size_t total_entry_count_;
diff --git a/libs/androidfw/BigBufferStream.cpp b/tools/aapt2/io/BigBufferStream.cpp
similarity index 69%
rename from libs/androidfw/BigBufferStream.cpp
rename to tools/aapt2/io/BigBufferStream.cpp
index f18199c..9704caa 100644
--- a/libs/androidfw/BigBufferStream.cpp
+++ b/tools/aapt2/io/BigBufferStream.cpp
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#include "androidfw/BigBufferStream.h"
+#include "io/BigBufferStream.h"
-#include <algorithm>
-
-namespace android {
+namespace aapt {
+namespace io {
//
// BigBufferInputStream
@@ -77,34 +76,6 @@
return buffer_->size();
}
-bool BigBufferInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
- if (byte_count == 0) {
- return true;
- }
- if (offset < 0) {
- return false;
- }
- if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
- return false;
- }
- if (offset + byte_count > buffer_->size()) {
- return false;
- }
- auto p = reinterpret_cast<uint8_t*>(data);
- for (auto iter = buffer_->begin(); iter != buffer_->end() && byte_count > 0; ++iter) {
- if (offset < iter->size) {
- size_t to_read = std::min(byte_count, (size_t)(iter->size - offset));
- memcpy(p, iter->buffer.get() + offset, to_read);
- byte_count -= to_read;
- p += to_read;
- offset = 0;
- } else {
- offset -= iter->size;
- }
- }
- return byte_count == 0;
-}
-
//
// BigBufferOutputStream
//
@@ -126,4 +97,5 @@
return false;
}
-} // namespace android
+} // namespace io
+} // namespace aapt
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/tools/aapt2/io/BigBufferStream.h
similarity index 74%
rename from libs/androidfw/include/androidfw/BigBufferStream.h
rename to tools/aapt2/io/BigBufferStream.h
index e55fe0d..63a5e57 100644
--- a/libs/androidfw/include/androidfw/BigBufferStream.h
+++ b/tools/aapt2/io/BigBufferStream.h
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-#pragma once
+#ifndef AAPT_IO_BIGBUFFERSTREAM_H
+#define AAPT_IO_BIGBUFFERSTREAM_H
-#include "BigBuffer.h"
-#include "Streams.h"
+#include "androidfw/BigBuffer.h"
+#include "io/Io.h"
-namespace android {
+namespace aapt {
+namespace io {
class BigBufferInputStream : public KnownSizeInputStream {
public:
- inline explicit BigBufferInputStream(const BigBuffer* buffer)
+ inline explicit BigBufferInputStream(const android::BigBuffer* buffer)
: buffer_(buffer), iter_(buffer->begin()) {
}
virtual ~BigBufferInputStream() = default;
@@ -42,20 +44,18 @@
size_t TotalSize() const override;
- bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
private:
DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
- const BigBuffer* buffer_;
- BigBuffer::const_iterator iter_;
+ const android::BigBuffer* buffer_;
+ android::BigBuffer::const_iterator iter_;
size_t offset_ = 0;
size_t bytes_read_ = 0;
};
class BigBufferOutputStream : public OutputStream {
public:
- inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+ inline explicit BigBufferOutputStream(android::BigBuffer* buffer) : buffer_(buffer) {
}
virtual ~BigBufferOutputStream() = default;
@@ -70,7 +70,10 @@
private:
DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
- BigBuffer* buffer_;
+ android::BigBuffer* buffer_;
};
-} // namespace android
\ No newline at end of file
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_BIGBUFFERSTREAM_H
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 29f523a..db91a77 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -20,14 +20,15 @@
#include <memory>
#include "android-base/macros.h"
-#include "androidfw/Streams.h"
#include "utils/FileMap.h"
+#include "io/Io.h"
+
namespace aapt {
namespace io {
// Interface for a block of contiguous memory. An instance of this interface owns the data.
-class IData : public android::KnownSizeInputStream {
+class IData : public KnownSizeInputStream {
public:
virtual ~IData() = default;
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
index 4dfdb5b..b4f1ff3 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -39,7 +39,7 @@
return {};
}
-std::unique_ptr<android::InputStream> FileSegment::OpenInputStream() {
+std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() {
return OpenAsData();
}
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 248756b..673d1b7 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -43,7 +43,7 @@
// Returns nullptr on failure.
virtual std::unique_ptr<IData> OpenAsData() = 0;
- virtual std::unique_ptr<android::InputStream> OpenInputStream() = 0;
+ virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0;
// Returns the source of this file. This is for presentation to the user and
// may not be a valid file system path (for example, it may contain a '@' sign to separate
@@ -78,7 +78,7 @@
: file_(file), offset_(offset), len_(len) {}
std::unique_ptr<IData> OpenAsData() override;
- std::unique_ptr<android::InputStream> OpenInputStream() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override {
return file_->GetSource();
diff --git a/libs/androidfw/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
similarity index 95%
rename from libs/androidfw/FileStream.cpp
rename to tools/aapt2/io/FileStream.cpp
index b86c9cb..27529bc 100644
--- a/libs/androidfw/FileStream.cpp
+++ b/tools/aapt2/io/FileStream.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "androidfw/FileStream.h"
+#include "io/FileStream.h"
#include <errno.h> // for errno
#include <fcntl.h> // for O_RDONLY
@@ -34,7 +34,8 @@
using ::android::base::SystemErrorCodeToString;
using ::android::base::unique_fd;
-namespace android {
+namespace aapt {
+namespace io {
FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
: buffer_capacity_(buffer_capacity) {
@@ -107,10 +108,6 @@
return error_;
}
-bool FileInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
- return base::ReadFullyAtOffset(fd_, data, byte_count, offset);
-}
-
FileOutputStream::FileOutputStream(const std::string& path, size_t buffer_capacity)
: buffer_capacity_(buffer_capacity) {
int mode = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY;
@@ -202,4 +199,5 @@
return error_;
}
-} // namespace android
+} // namespace io
+} // namespace aapt
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/tools/aapt2/io/FileStream.h
similarity index 93%
rename from libs/androidfw/include/androidfw/FileStream.h
rename to tools/aapt2/io/FileStream.h
index fb84a91..62d910f 100644
--- a/libs/androidfw/include/androidfw/FileStream.h
+++ b/tools/aapt2/io/FileStream.h
@@ -14,16 +14,19 @@
* limitations under the License.
*/
-#pragma once
+#ifndef AAPT_IO_FILESTREAM_H
+#define AAPT_IO_FILESTREAM_H
+
+#include "io/Io.h"
#include <memory>
#include <string>
-#include "Streams.h"
#include "android-base/macros.h"
#include "android-base/unique_fd.h"
-namespace android {
+namespace aapt {
+namespace io {
constexpr size_t kDefaultBufferCapacity = 4096u;
@@ -45,8 +48,6 @@
std::string GetError() const override;
- bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
private:
DISALLOW_COPY_AND_ASSIGN(FileInputStream);
@@ -100,4 +101,7 @@
size_t total_byte_count_ = 0u;
};
-} // namespace android
\ No newline at end of file
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESTREAM_H
diff --git a/libs/androidfw/tests/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
similarity index 95%
rename from libs/androidfw/tests/FileStream_test.cpp
rename to tools/aapt2/io/FileStream_test.cpp
index 9785975..cc9cd28 100644
--- a/libs/androidfw/tests/FileStream_test.cpp
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -14,21 +14,20 @@
* limitations under the License.
*/
-#include "androidfw/FileStream.h"
-#include "androidfw/StringPiece.h"
+#include "io/FileStream.h"
#include "android-base/file.h"
#include "android-base/macros.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include "test/Test.h"
using ::android::StringPiece;
using ::testing::Eq;
using ::testing::NotNull;
using ::testing::StrEq;
-namespace android {
+namespace aapt {
+namespace io {
TEST(FileInputStreamTest, NextAndBackup) {
std::string input = "this is a cool string";
@@ -124,4 +123,5 @@
EXPECT_THAT(actual, StrEq(input));
}
-} // namespace android
+} // namespace io
+} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 03fabcc..6a692e4 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -22,9 +22,9 @@
#include <sys/stat.h>
#include "android-base/errors.h"
-#include "androidfw/FileStream.h"
#include "androidfw/Source.h"
#include "androidfw/StringPiece.h"
+#include "io/FileStream.h"
#include "util/Files.h"
#include "util/Util.h"
#include "utils/FileMap.h"
@@ -49,8 +49,8 @@
return {};
}
-std::unique_ptr<android::InputStream> RegularFile::OpenInputStream() {
- return util::make_unique<android::FileInputStream>(source_.path);
+std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() {
+ return util::make_unique<FileInputStream>(source_.path);
}
const android::Source& RegularFile::GetSource() const {
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index d6ecfeb..f975196 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -30,7 +30,7 @@
explicit RegularFile(const android::Source& source);
std::unique_ptr<IData> OpenAsData() override;
- std::unique_ptr<android::InputStream> OpenInputStream() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
bool GetModificationTime(struct tm* buf) const override;
diff --git a/libs/androidfw/include/androidfw/Streams.h b/tools/aapt2/io/Io.h
similarity index 87%
rename from libs/androidfw/include/androidfw/Streams.h
rename to tools/aapt2/io/Io.h
index 89f1620..e1df23a6 100644
--- a/libs/androidfw/include/androidfw/Streams.h
+++ b/tools/aapt2/io/Io.h
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-#pragma once
+#ifndef AAPT_IO_IO_H
+#define AAPT_IO_IO_H
#include <string>
-namespace android {
+namespace aapt {
+namespace io {
// InputStream interface that mimics protobuf's ZeroCopyInputStream,
// with added error handling methods to better report issues.
@@ -39,34 +41,21 @@
virtual void BackUp(size_t count) = 0;
// Returns true if this InputStream can rewind. If so, Rewind() can be called.
- virtual bool CanRewind() const {
- return false;
- };
+ virtual bool CanRewind() const { return false; };
// Rewinds the stream to the beginning so it can be read again.
// Returns true if the rewind succeeded.
// This does nothing if CanRewind() returns false.
- virtual bool Rewind() {
- return false;
- }
+ virtual bool Rewind() { return false; }
// Returns the number of bytes that have been read from the stream.
virtual size_t ByteCount() const = 0;
// Returns an error message if HadError() returned true.
- virtual std::string GetError() const {
- return {};
- }
+ virtual std::string GetError() const { return {}; }
// Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
-
- virtual bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
- (void)data;
- (void)byte_count;
- (void)offset;
- return false;
- }
};
// A sub-InputStream interface that knows the total size of its stream.
@@ -98,12 +87,13 @@
virtual size_t ByteCount() const = 0;
// Returns an error message if HadError() returned true.
- virtual std::string GetError() const {
- return {};
- }
+ virtual std::string GetError() const { return {}; }
// Returns true if an error occurred. Errors are permanent.
virtual bool HadError() const = 0;
};
-} // namespace android
\ No newline at end of file
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_IO_H */
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index bb3911b..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -51,23 +51,6 @@
return str_.size();
}
-bool StringInputStream::ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) {
- if (byte_count == 0) {
- return true;
- }
- if (offset < 0) {
- return false;
- }
- if (offset > std::numeric_limits<off64_t>::max() - byte_count) {
- return false;
- }
- if (offset + byte_count > str_.size()) {
- return false;
- }
- memcpy(data, str_.data() + offset, byte_count);
- return true;
-}
-
StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity)
: str_(str),
buffer_capacity_(buffer_capacity),
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index 7e1abe5..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -17,16 +17,17 @@
#ifndef AAPT_IO_STRINGSTREAM_H
#define AAPT_IO_STRINGSTREAM_H
+#include "io/Io.h"
+
#include <memory>
#include "android-base/macros.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
namespace aapt {
namespace io {
-class StringInputStream : public android::KnownSizeInputStream {
+class StringInputStream : public KnownSizeInputStream {
public:
explicit StringInputStream(android::StringPiece str);
@@ -46,8 +47,6 @@
size_t TotalSize() const override;
- bool ReadFullyAtOffset(void* data, size_t byte_count, off64_t offset) override;
-
private:
DISALLOW_COPY_AND_ASSIGN(StringInputStream);
@@ -55,7 +54,7 @@
size_t offset_;
};
-class StringOutputStream : public android::OutputStream {
+class StringOutputStream : public OutputStream {
public:
explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u);
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 9616e47..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,9 +26,8 @@
namespace aapt {
namespace io {
-bool CopyInputStreamToArchive(IAaptContext* context, android::InputStream* in,
- std::string_view out_path, uint32_t compression_flags,
- IArchiveWriter* writer) {
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
+ uint32_t compression_flags, IArchiveWriter* writer) {
TRACE_CALL();
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(android::DiagMessage()
@@ -92,7 +91,7 @@
return false;
}
-bool Copy(android::OutputStream* out, android::InputStream* in) {
+bool Copy(OutputStream* out, InputStream* in) {
TRACE_CALL();
const void* in_buffer;
size_t in_len;
@@ -111,7 +110,7 @@
return !in->HadError();
}
-bool Copy(android::OutputStream* out, StringPiece in) {
+bool Copy(OutputStream* out, StringPiece in) {
const char* in_buffer = in.data();
size_t in_len = in.size();
while (in_len != 0) {
@@ -130,7 +129,7 @@
return true;
}
-bool Copy(ZeroCopyOutputStream* out, android::InputStream* in) {
+bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
OutputStreamAdaptor adaptor(out);
return Copy(&adaptor, in);
}
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 25aa8f8..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -19,19 +19,18 @@
#include <string_view>
-#include "androidfw/Streams.h"
#include "format/Archive.h"
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/message.h"
#include "io/File.h"
+#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
namespace aapt {
namespace io {
-bool CopyInputStreamToArchive(IAaptContext* context, android::InputStream* in,
- std::string_view out_path, uint32_t compression_flags,
- IArchiveWriter* writer);
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
+ uint32_t compression_flags, IArchiveWriter* writer);
bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
uint32_t compression_flags, IArchiveWriter* writer);
@@ -45,11 +44,11 @@
// Copies the data from in to out. Returns false if there was an error.
// If there was an error, check the individual streams' HadError/GetError methods.
-bool Copy(android::OutputStream* out, android::InputStream* in);
-bool Copy(android::OutputStream* out, android::StringPiece in);
-bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, android::InputStream* in);
+bool Copy(OutputStream* out, InputStream* in);
+bool Copy(OutputStream* out, android::StringPiece in);
+bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
-class OutputStreamAdaptor : public android::OutputStream {
+class OutputStreamAdaptor : public io::OutputStream {
public:
explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) {
}
@@ -85,7 +84,7 @@
class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream {
public:
- explicit ZeroCopyInputAdaptor(android::InputStream* in) : in_(in) {
+ explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) {
}
bool Next(const void** data, int* size) override {
@@ -120,13 +119,12 @@
private:
DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor);
- android::InputStream* in_;
+ io::InputStream* in_;
};
class ProtoInputStreamReader {
public:
- explicit ProtoInputStreamReader(android::InputStream* in) : in_(in) {
- }
+ explicit ProtoInputStreamReader(io::InputStream* in) : in_(in) { }
/** Deserializes a Message proto from the current position in the input stream.*/
template <typename T> bool ReadMessage(T *message) {
@@ -137,7 +135,7 @@
}
private:
- android::InputStream* in_;
+ io::InputStream* in_;
};
} // namespace io
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index e44d61e..cb5bbe9 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -63,7 +63,7 @@
}
}
-std::unique_ptr<android::InputStream> ZipFile::OpenInputStream() {
+std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() {
return OpenAsData();
}
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index a53c4a2..ac125d0 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -35,7 +35,7 @@
ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const android::Source& source);
std::unique_ptr<IData> OpenAsData() override;
- std::unique_ptr<android::InputStream> OpenInputStream() override;
+ std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
bool WasCompressed() override;
bool GetModificationTime(struct tm* buf) const override;
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index db7aa35..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -111,7 +111,7 @@
" */\n\n";
void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
- bool strip_api_annotations, android::OutputStream* out) {
+ bool strip_api_annotations, io::OutputStream* out) {
Printer printer(out);
printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 84e3f33..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -241,7 +241,7 @@
class ClassDefinition : public ClassMember {
public:
static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
- bool strip_api_annotations, android::OutputStream* out);
+ bool strip_api_annotations, io::OutputStream* out);
ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
: name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 58f6564..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -37,8 +37,8 @@
#include "java/ClassDefinition.h"
#include "process/SymbolTable.h"
+using ::aapt::io::OutputStream;
using ::aapt::text::Printer;
-using ::android::OutputStream;
using ::android::StringPiece;
using ::android::base::StringPrintf;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 9909eec..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -19,10 +19,11 @@
#include <string>
+#include "androidfw/StringPiece.h"
+
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "androidfw/Streams.h"
-#include "androidfw/StringPiece.h"
+#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "text/Printer.h"
@@ -69,12 +70,12 @@
// All symbols technically belong to a single package, but linked libraries will
// have their names mangled, denoting that they came from a different package.
// We need to generate these symbols in a separate file. Returns true on success.
- bool Generate(android::StringPiece package_name_to_generate, android::OutputStream* out,
- android::OutputStream* out_r_txt = nullptr);
+ bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
+ io::OutputStream* out_r_txt = nullptr);
bool Generate(android::StringPiece package_name_to_generate,
- android::StringPiece output_package_name, android::OutputStream* out,
- android::OutputStream* out_r_txt = nullptr);
+ android::StringPiece output_package_name, io::OutputStream* out,
+ io::OutputStream* out_r_txt = nullptr);
const std::string& GetError() const;
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index aef48fc..80a46d5 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -29,8 +29,8 @@
#include "util/Util.h"
#include "xml/XmlDom.h"
+using ::aapt::io::OutputStream;
using ::aapt::text::Printer;
-using ::android::OutputStream;
namespace aapt {
namespace proguard {
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 876ef48..267f7ed 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -26,8 +26,8 @@
#include "ResourceTable.h"
#include "ValueVisitor.h"
#include "androidfw/Source.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
+#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
@@ -69,7 +69,7 @@
}
private:
- friend void WriteKeepSet(const KeepSet& keep_set, android::OutputStream* out, bool minimal_keep,
+ friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
bool no_location_reference);
friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
@@ -89,7 +89,7 @@
bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set);
-void WriteKeepSet(const KeepSet& keep_set, android::OutputStream* out, bool minimal_keep,
+void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep,
bool no_location_reference);
bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 0437980..e48668c 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -90,7 +90,7 @@
return {};
}
- std::unique_ptr<android::InputStream> OpenInputStream() override {
+ std::unique_ptr<io::InputStream> OpenInputStream() override {
return OpenAsData();
}
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index b91abe5..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -20,7 +20,6 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/utf8.h>
-#include <androidfw/FileStream.h>
#include <androidfw/StringPiece.h>
#include <dirent.h>
#include <gmock/gmock.h>
@@ -29,6 +28,7 @@
#include "Diagnostics.h"
#include "cmd/Compile.h"
#include "cmd/Link.h"
+#include "io/FileStream.h"
#include "util/Files.h"
using testing::Eq;
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index c2fa8cc..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -20,7 +20,7 @@
#include "io/Util.h"
-using ::android::OutputStream;
+using ::aapt::io::OutputStream;
using ::android::StringPiece;
namespace aapt {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index 44f0fc5..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -18,16 +18,17 @@
#define AAPT_TEXT_PRINTER_H
#include "android-base/macros.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
+#include "io/Io.h"
+
namespace aapt {
namespace text {
// An indenting Printer that helps write formatted text to the OutputStream.
class Printer {
public:
- explicit Printer(android::OutputStream* out) : out_(out) {
+ explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
}
Printer& Print(android::StringPiece str);
@@ -40,7 +41,7 @@
private:
DISALLOW_COPY_AND_ASSIGN(Printer);
- android::OutputStream* out_;
+ ::aapt::io::OutputStream* out_;
int indent_level_ = 0;
bool needs_indent_ = false;
bool error_ = false;
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 49807db..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -30,7 +30,7 @@
#include "XmlPullParser.h"
#include "util/Util.h"
-using ::android::InputStream;
+using ::aapt::io::InputStream;
using ::android::StringPiece;
using ::android::StringPiece16;
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 9668b6a6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -24,8 +24,8 @@
#include "Resource.h"
#include "ResourceValues.h"
#include "androidfw/IDiagnostics.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
+#include "io/Io.h"
#include "util/Util.h"
#include "xml/XmlUtil.h"
@@ -152,7 +152,7 @@
};
// Inflates an XML DOM from an InputStream, logging errors to the logger.
-std::unique_ptr<XmlResource> Inflate(android::InputStream* in, android::IDiagnostics* diag,
+std::unique_ptr<XmlResource> Inflate(io::InputStream* in, android::IDiagnostics* diag,
const android::Source& source);
// Inflates an XML DOM from a binary ResXMLTree.
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 203832d..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -21,7 +21,7 @@
#include "xml/XmlPullParser.h"
#include "xml/XmlUtil.h"
-using ::android::InputStream;
+using ::aapt::io::InputStream;
using ::android::StringPiece;
namespace aapt {
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 655e6dc..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -27,10 +27,11 @@
#include <string>
#include <vector>
-#include "Resource.h"
#include "android-base/macros.h"
-#include "androidfw/Streams.h"
#include "androidfw/StringPiece.h"
+
+#include "Resource.h"
+#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlUtil.h"
@@ -65,7 +66,7 @@
static bool SkipCurrentElement(XmlPullParser* parser);
static bool IsGoodEvent(Event event);
- explicit XmlPullParser(android::InputStream* in);
+ explicit XmlPullParser(io::InputStream* in);
~XmlPullParser();
/**
@@ -178,7 +179,7 @@
std::vector<Attribute> attributes;
};
- android::InputStream* in_;
+ io::InputStream* in_;
XML_Parser parser_;
std::queue<EventData> event_queue_;
std::string error_;
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 5949bca..d415dc5 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -274,6 +274,21 @@
],
}
+java_library_host {
+ name: "hoststubgen-helper-libcore-runtime",
+ defaults: ["hoststubgen-for-prototype-only-java"],
+ srcs: [
+ "helper-framework-runtime-src/libcore-fake/**/*.java",
+ ],
+}
+
+java_host_for_device {
+ name: "hoststubgen-helper-libcore-runtime.ravenwood",
+ libs: [
+ "hoststubgen-helper-libcore-runtime",
+ ],
+}
+
java_library {
name: "hoststubgen-helper-framework-runtime.ravenwood",
defaults: ["hoststubgen-for-prototype-only-java"],
@@ -286,6 +301,7 @@
],
static_libs: [
"core-xml-for-device",
+ "hoststubgen-helper-libcore-runtime.ravenwood",
],
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java
new file mode 100644
index 0000000..388156a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.system;
+
+import java.io.IOException;
+import java.net.SocketException;
+
+/**
+ * A checked exception thrown when {@link Os} methods fail. This exception contains the native
+ * errno value, for comparison against the constants in {@link OsConstants}, should sophisticated
+ * callers need to adjust their behavior based on the exact failure.
+ */
+public final class ErrnoException extends Exception {
+ private final String functionName;
+
+ /**
+ * The errno value, for comparison with the {@code E} constants in {@link OsConstants}.
+ */
+ public final int errno;
+
+ /**
+ * Constructs an instance with the given function name and errno value.
+ */
+ public ErrnoException(String functionName, int errno) {
+ this.functionName = functionName;
+ this.errno = errno;
+ }
+
+ /**
+ * Constructs an instance with the given function name, errno value, and cause.
+ */
+ public ErrnoException(String functionName, int errno, Throwable cause) {
+ super(cause);
+ this.functionName = functionName;
+ this.errno = errno;
+ }
+
+ /**
+ * Converts the stashed function name and errno value to a human-readable string.
+ * We do this here rather than in the constructor so that callers only pay for
+ * this if they need it.
+ */
+ @Override public String getMessage() {
+ return functionName + " failed: " + errno;
+ }
+
+ /**
+ * Throws an {@link IOException} with a message based on {@link #getMessage()} and with this
+ * instance as the cause.
+ *
+ * <p>This method always terminates by throwing the exception. Callers can write
+ * {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
+ */
+ public IOException rethrowAsIOException() throws IOException {
+ IOException newException = new IOException(getMessage());
+ newException.initCause(this);
+ throw newException;
+ }
+
+ /**
+ * Throws a {@link SocketException} with a message based on {@link #getMessage()} and with this
+ * instance as the cause.
+ *
+ * <p>This method always terminates by throwing the exception. Callers can write
+ * {@code throw e.rethrowAsIOException()} to make that clear to the compiler.
+ */
+ public SocketException rethrowAsSocketException() throws SocketException {
+ throw new SocketException(getMessage());
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java
new file mode 100644
index 0000000..65c285e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+
+/** @hide */
+public final class IoUtils {
+ private IoUtils() {
+ }
+
+ public static void closeQuietly(AutoCloseable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ public static void closeQuietly(Socket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ public static void deleteContents(File dir) throws IOException {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ deleteContents(file);
+ }
+ file.delete();
+ }
+ }
+ }
+}