diff options
160 files changed, 3165 insertions, 1081 deletions
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 3dbe41395024..0617eb6c0e66 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -116,7 +116,6 @@ cc_defaults { "libcutils", "libgtest_prod", "libprotoutil", - "libstatsmetadata", "libstatslog_statsd", "libsysutils", "libutils", @@ -129,51 +128,6 @@ cc_defaults { ], } -// ================ -// libstatsmetadata -// ================ - -genrule { - name: "atoms_info.h", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h", - out: [ - "atoms_info.h", - ], -} - -genrule { - name: "atoms_info.cpp", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp", - out: [ - "atoms_info.cpp", - ], -} - -cc_library_static { - name: "libstatsmetadata", - host_supported: true, - generated_sources: [ - "atoms_info.cpp", - ], - generated_headers: [ - "atoms_info.h", - ], - cflags: [ - "-Wall", - "-Werror", - ], - export_generated_headers: [ - "atoms_info.h", - ], - apex_available: [ - //TODO(b/149782403): Remove this once statsd no longer links against libstatsmetadata - "com.android.os.statsd", - "test_com.android.os.statsd", - ], -} - genrule { name: "statslog_statsd.h", tools: ["stats-log-api-gen"], diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 8527185d3891..ff5717e4fa78 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -110,8 +110,6 @@ extend google.protobuf.FieldOptions { optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC]; - optional bool allow_from_any_uid = 50003 [default = false]; - repeated string module = 50004; optional bool truncate_timestamp = 50005 [default = false]; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index dc20a02156cb..a5f0ac97cebc 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -145,8 +145,7 @@ message Atom { PacketWakeupOccurred packet_wakeup_occurred = 44 [(module) = "framework"]; WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; AnomalyDetected anomaly_detected = 46 [(module) = "statsd"]; - AppBreadcrumbReported app_breadcrumb_reported = - 47 [(allow_from_any_uid) = true, (module) = "statsd"]; + AppBreadcrumbReported app_breadcrumb_reported = 47 [(module) = "statsd"]; AppStartOccurred app_start_occurred = 48 [(module) = "framework", (module) = "statsdtest"]; AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; @@ -157,7 +156,7 @@ message Atom { AppStartMemoryStateCaptured app_start_memory_state_captured = 55 [(module) = "framework"]; ShutdownSequenceReported shutdown_sequence_reported = 56 [(module) = "framework"]; BootSequenceReported boot_sequence_reported = 57; - DaveyOccurred davey_occurred = 58 [(allow_from_any_uid) = true, (module) = "statsd"]; + DaveyOccurred davey_occurred = 58 [(module) = "statsd"]; OverlayStateChanged overlay_state_changed = 59 [(module) = "framework", (module) = "statsdtest"]; ForegroundServiceStateChanged foreground_service_state_changed @@ -186,8 +185,7 @@ message Atom { WTFOccurred wtf_occurred = 80 [(module) = "framework"]; LowMemReported low_mem_reported = 81 [(module) = "framework"]; GenericAtom generic_atom = 82; - KeyValuePairsAtom key_value_pairs_atom = - 83 [(allow_from_any_uid) = true, (module) = "framework", (module) = "statsd"]; + KeyValuePairsAtom key_value_pairs_atom = 83 [(module) = "framework", (module) = "statsd"]; VibratorStateChanged vibrator_state_changed = 84 [(module) = "framework"]; DeferredJobStatsReported deferred_job_stats_reported = 85 [(module) = "framework"]; ThermalThrottlingStateChanged thermal_throttling = 86 [deprecated=true]; @@ -317,7 +315,7 @@ message Atom { AssistGestureFeedbackReported assist_gesture_feedback_reported = 175 [(module) = "sysui"]; AssistGestureProgressReported assist_gesture_progress_reported = 176 [(module) = "sysui"]; TouchGestureClassified touch_gesture_classified = 177 [(module) = "framework"]; - HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true, (module) = "framework"]; + HiddenApiUsed hidden_api_used = 178 [(module) = "framework"]; StyleUIChanged style_ui_changed = 179 [(module) = "sysui"]; PrivacyIndicatorsInteracted privacy_indicators_interacted = 180 [(module) = "permissioncontroller"]; @@ -383,7 +381,7 @@ message Atom { UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226; CameraActionEvent camera_action_event = 227 [(module) = "framework"]; AppCompatibilityChangeReported app_compatibility_change_reported = - 228 [(allow_from_any_uid) = true, (module) = "framework"]; + 228 [(module) = "framework"]; PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; VmsClientConnectionStateChanged vms_client_connection_state_changed = 230 [(module) = "car"]; @@ -486,6 +484,7 @@ message Atom { KeystoreKeyEventReported keystore_key_event_reported = 302; NetworkTetheringReported network_tethering_reported = 303 [(module) = "network_tethering"]; + ImeTouchReported ime_touch_reported = 304 [(module) = "sysui"]; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -3061,6 +3060,18 @@ message ExclusionRectStateChanged { } /** + * Logs when IME is on. + * + * Logged from: /packages/SystemUI/src/com/android/systemui/ + statusbar/phone/NavigationBarView.java + * + */ +message ImeTouchReported { + optional int32 x_coordinate = 1; // X coordinate for ACTION_DOWN event. + optional int32 y_coordinate = 2; // Y coordinate for ACTION_DOWN event. +} + +/** * Logs when Launcher (HomeScreen) UI has changed or was interacted. * * Logged from: diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index 7e825efddb75..60de1a24cce5 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -21,7 +21,6 @@ #include <private/android_filesystem_config.h> #include "CountMetricProducer.h" -#include "atoms_info.h" #include "condition/CombinationConditionTracker.h" #include "condition/SimpleConditionTracker.h" #include "guardrail/StatsdStats.h" @@ -372,13 +371,6 @@ void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, bool MetricsManager::checkLogCredentials(const LogEvent& event) { - // TODO(b/154856835): Remove this check once we get whitelist from the config. - if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) != - android::util::AtomsInfo::kWhitelistedAtoms.end()) - { - return true; - } - if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) { return true; } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 22a63d9b486b..3772755beca1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2816,6 +2816,11 @@ public class Activity extends ContextThemeWrapper * The system may disallow entering picture-in-picture in various cases, including when the * activity is not visible, if the screen is locked or if the user has an activity pinned. * + * <p>By default, system calculates the dimension of picture-in-picture window based on the + * given {@param params}. + * See <a href="{@docRoot}guide/topics/ui/picture-in-picture">Picture-in-picture Support</a> + * on how to override this behavior.</p> + * * @see android.R.attr#supportsPictureInPicture * @see PictureInPictureParams * diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index b749c3504811..98a23f2b0075 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -33,7 +33,10 @@ import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.UserHandle; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; import android.view.IWindow; import android.view.IWindowManager; import android.view.KeyEvent; @@ -407,6 +410,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd } private class SurfaceCallback implements SurfaceHolder.Callback { + private final DisplayInfo mTempDisplayInfo = new DisplayInfo(); + private final DisplayMetrics mTempMetrics = new DisplayMetrics(); + @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { if (!mTaskEmbedder.isInitialized()) { @@ -415,13 +421,21 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(), mSurfaceView.getSurfaceControl()).apply(); } + mTaskEmbedder.resizeTask(getWidth(), getHeight()); mTaskEmbedder.start(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { - mTaskEmbedder.resizeTask(width, height); - mTaskEmbedder.notifyBoundsChanged(); + final Display display = getVirtualDisplay().getDisplay(); + if (!display.getDisplayInfo(mTempDisplayInfo)) { + return; + } + mTempDisplayInfo.getAppMetrics(mTempMetrics); + if (width != mTempMetrics.widthPixels || height != mTempMetrics.heightPixels) { + mTaskEmbedder.resizeTask(width, height); + mTaskEmbedder.notifyBoundsChanged(); + } } @Override diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 1278ff6817fd..0719422632d1 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ClipDescription; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -50,11 +51,13 @@ import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.LongSparseArray; import android.util.Pair; +import android.webkit.MimeTypeMap; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * The download manager is a system service that handles long-running HTTP downloads. Clients may @@ -1554,6 +1557,7 @@ public class DownloadManager { values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); values.put(Downloads.Impl._DATA, path); + values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path))); values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, @@ -1569,6 +1573,58 @@ public class DownloadManager { return Long.parseLong(downloadUri.getLastPathSegment()); } + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/MimeUtils.java} + * + * @hide + */ + private static @NonNull String resolveMimeType(@NonNull File file) { + final String extension = extractFileExtension(file.getPath()); + if (extension == null) return ClipDescription.MIMETYPE_UNKNOWN; + + final String mimeType = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT)); + if (mimeType == null) return ClipDescription.MIMETYPE_UNKNOWN; + + return mimeType; + } + + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} + * + * @hide + */ + private static @Nullable String extractDisplayName(@Nullable String data) { + if (data == null) return null; + if (data.indexOf('/') == -1) { + return data; + } + if (data.endsWith("/")) { + data = data.substring(0, data.length() - 1); + } + return data.substring(data.lastIndexOf('/') + 1); + } + + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} + * + * @hide + */ + private static @Nullable String extractFileExtension(@Nullable String data) { + if (data == null) return null; + data = extractDisplayName(data); + + final int lastDot = data.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } else { + return data.substring(lastDot + 1); + } + } + private static final String NON_DOWNLOADMANAGER_DOWNLOAD = "non-dwnldmngr-download-dont-retry2download"; diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index d6e77624a967..fc8248e1012a 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -41,6 +41,7 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.telephony.TelephonyManager; import android.util.DataUnit; import android.util.Log; @@ -198,6 +199,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -231,6 +238,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -268,6 +281,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -301,7 +320,7 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid. * - * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ public NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid) throws SecurityException { @@ -319,7 +338,7 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid and tag. * - * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag) throws SecurityException { @@ -344,6 +363,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -398,6 +423,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -455,7 +486,7 @@ public class NetworkStatsManager { /** * Registers to receive notifications about data usage on specified networks. * - * #see registerUsageCallback(int, String[], long, UsageCallback, Handler) + * @see #registerUsageCallback(int, String, long, UsageCallback, Handler) */ public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback) { @@ -472,6 +503,12 @@ public class NetworkStatsManager { * @param networkType Type of network to monitor. Either {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}. * @param subscriberId If applicable, the subscriber id of the network interface. + * <p>Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when registering for the mobile network type to receive + * notifications for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param thresholdBytes Threshold in bytes to be notified on. * @param callback The {@link UsageCallback} that the system will call when data usage * has exceeded the specified threshold. diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 2ce6a86753a6..8a6cc95319fb 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -154,6 +154,7 @@ public final class UsageStatsManager { * been misbehaving in some manner. * Apps in this bucket will have the most restrictions, including network restrictions and * additional restrictions on jobs. + * <p> Note: this bucket is not enabled in {@link Build.VERSION_CODES#R}. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_RESTRICTED = 45; diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java index d2a15357aa1f..e07bc0215a6b 100644 --- a/core/java/android/bluetooth/BluetoothCodecConfig.java +++ b/core/java/android/bluetooth/BluetoothCodecConfig.java @@ -614,8 +614,9 @@ public final class BluetoothCodecConfig implements Parcelable { if (other == null && mCodecType != other.mCodecType) { return false; } - // Currently we only care about the LDAC Playback Quality at CodecSpecific1 + // Currently we only care about the AAC VBR and LDAC Playback Quality at CodecSpecific1 switch (mCodecType) { + case SOURCE_CODEC_TYPE_AAC: case SOURCE_CODEC_TYPE_LDAC: if (mCodecSpecific1 != other.mCodecSpecific1) { return false; diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 772845d4e683..8d65c92db52e 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -84,6 +84,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -189,6 +191,9 @@ public final class StrictMode { // Only show an annoying dialog at most every 30 seconds private static final long MIN_DIALOG_INTERVAL_MS = 30000; + // Only log a dropbox entry at most every 30 seconds + private static final long MIN_DROPBOX_INTERVAL_MS = 3000; + // How many Span tags (e.g. animations) to report. private static final int MAX_SPAN_TAGS = 20; @@ -1752,16 +1757,20 @@ public final class StrictMode { // Not perfect, but fast and good enough for dup suppression. Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; - if (mLastViolationTime != null) { - Long vtime = mLastViolationTime.get(crashFingerprint); - if (vtime != null) { - lastViolationTime = vtime; + long now = SystemClock.uptimeMillis(); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + if (mLastViolationTime != null) { + Long vtime = mLastViolationTime.get(crashFingerprint); + if (vtime != null) { + lastViolationTime = vtime; + } + clampViolationTimeMap(mLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS, + Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS))); + } else { + mLastViolationTime = new ArrayMap<>(1); } - } else { - mLastViolationTime = new ArrayMap<>(1); + mLastViolationTime.put(crashFingerprint, now); } - long now = SystemClock.uptimeMillis(); - mLastViolationTime.put(crashFingerprint, now); long timeSinceLastViolationMillis = lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime); @@ -1780,7 +1789,8 @@ public final class StrictMode { penaltyMask |= PENALTY_DIALOG; } - if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) { + if (info.penaltyEnabled(PENALTY_DROPBOX) + && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) { penaltyMask |= PENALTY_DROPBOX; } @@ -2215,6 +2225,23 @@ public final class StrictMode { @UnsupportedAppUsage private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>(); + /** + * Clamp the given map by removing elements with timestamp older than the given retainSince. + */ + private static void clampViolationTimeMap(final @NonNull Map<Integer, Long> violationTime, + final long retainSince) { + final Iterator<Map.Entry<Integer, Long>> iterator = violationTime.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<Integer, Long> e = iterator.next(); + if (e.getValue() < retainSince) { + // Remove stale entries + iterator.remove(); + } + } + // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK, + // seems not worth it (saving some space immediately but they will be obsoleted soon anyway) + } + /** @hide */ public static void onVmPolicyViolation(Violation originStack) { onVmPolicyViolation(originStack, false); @@ -2238,13 +2265,17 @@ public final class StrictMode { final long now = SystemClock.uptimeMillis(); long lastViolationTime; long timeSinceLastViolationMillis = Long.MAX_VALUE; - synchronized (sLastVmViolationTime) { - if (sLastVmViolationTime.containsKey(fingerprint)) { - lastViolationTime = sLastVmViolationTime.get(fingerprint); - timeSinceLastViolationMillis = now - lastViolationTime; - } - if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { - sLastVmViolationTime.put(fingerprint, now); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + synchronized (sLastVmViolationTime) { + if (sLastVmViolationTime.containsKey(fingerprint)) { + lastViolationTime = sLastVmViolationTime.get(fingerprint); + timeSinceLastViolationMillis = now - lastViolationTime; + } + if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { + sLastVmViolationTime.put(fingerprint, now); + } + clampViolationTimeMap(sLastVmViolationTime, + now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS)); } } if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a415dc57e160..2465b0e41876 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2699,15 +2699,12 @@ public class UserManager { * @param name the user's name * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. * @param flags UserInfo flags that specify user properties. - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. - * @hide * @see UserInfo + * + * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) @@ -2716,8 +2713,7 @@ public class UserManager { try { return mService.createUserWithThrow(name, userType, flags); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2743,25 +2739,19 @@ public class UserManager { * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}. * * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). - * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. + * @return the {@link UserInfo} object for the created user. * + * @throws UserOperationException if the user could not be created. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public @Nullable UserInfo preCreateUser(@NonNull String userType) + public @NonNull UserInfo preCreateUser(@NonNull String userType) throws UserOperationException { try { return mService.preCreateUserWithThrow(userType); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + throw UserOperationException.from(e); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2771,18 +2761,14 @@ public class UserManager { * Creates a guest user and configures it. * @param context an application context * @param name the name to set for the user - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createGuest(Context context, String name) throws UserOperationException { + public UserInfo createGuest(Context context, String name) { UserInfo guest = null; try { guest = mService.createUserWithThrow(name, USER_TYPE_FULL_GUEST, 0); @@ -2791,8 +2777,7 @@ public class UserManager { Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); } } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2902,26 +2887,20 @@ public class UserManager { * @param userId new user will be a profile of this user. * @param disallowedPackages packages that will not be installed in the profile being created. * - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user could + * not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) - throws UserOperationException { + @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { try { return mService.createProfileForUserWithThrow(name, userType, flags, userId, disallowedPackages); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2938,13 +2917,12 @@ public class UserManager { Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, - String[] disallowedPackages) throws UserOperationException { + String[] disallowedPackages) { try { return mService.createProfileForUserEvenWhenDisallowedWithThrow(name, userType, flags, userId, disallowedPackages); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2955,18 +2933,14 @@ public class UserManager { * restrictions and adds shared accounts. * * @param name profile's name - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createRestrictedProfile(String name) throws UserOperationException { + public UserInfo createRestrictedProfile(String name) { try { UserHandle parentUserHandle = Process.myUserHandle(); UserInfo user = mService.createRestrictedProfileWithThrow(name, @@ -2977,8 +2951,7 @@ public class UserManager { } return user; } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -4009,15 +3982,15 @@ public class UserManager { * Sets the user's photo. * @param userId the user for whom to change the photo. * @param icon the bitmap to set as the photo. + * * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) - throws UserOperationException { + public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) { try { mService.setUserIcon(userId, icon); } catch (ServiceSpecificException e) { - throw UserOperationException.from(e); + return; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -4027,6 +4000,10 @@ public class UserManager { * Sets the context user's photo. * * @param icon the bitmap to set as the photo. + * + * @throws UserOperationException according to the function signature, but may not actually + * throw it in practice. Catch RuntimeException instead. + * * @hide */ @SystemApi diff --git a/core/java/android/os/strictmode/Violation.java b/core/java/android/os/strictmode/Violation.java index 31c7d584fd65..0edb78a64243 100644 --- a/core/java/android/os/strictmode/Violation.java +++ b/core/java/android/os/strictmode/Violation.java @@ -18,7 +18,58 @@ package android.os.strictmode; /** Root class for all StrictMode violations. */ public abstract class Violation extends Throwable { + private int mHashCode; + private boolean mHashCodeValid; + Violation(String message) { super(message); } + + @Override + public int hashCode() { + synchronized (this) { + if (mHashCodeValid) { + return mHashCode; + } + final String message = getMessage(); + final Throwable cause = getCause(); + int hashCode = message != null ? message.hashCode() : getClass().hashCode(); + hashCode = hashCode * 37 + calcStackTraceHashCode(getStackTrace()); + hashCode = hashCode * 37 + (cause != null ? cause.toString().hashCode() : 0); + mHashCodeValid = true; + return mHashCode = hashCode; + } + } + + @Override + public synchronized Throwable initCause(Throwable cause) { + mHashCodeValid = false; + return super.initCause(cause); + } + + @Override + public void setStackTrace(StackTraceElement[] stackTrace) { + super.setStackTrace(stackTrace); + synchronized (this) { + mHashCodeValid = false; + } + } + + @Override + public synchronized Throwable fillInStackTrace() { + mHashCodeValid = false; + return super.fillInStackTrace(); + } + + private static int calcStackTraceHashCode(final StackTraceElement[] stackTrace) { + int hashCode = 17; + if (stackTrace != null) { + for (int i = 0; i < stackTrace.length; i++) { + if (stackTrace[i] != null) { + hashCode = hashCode * 37 + stackTrace[i].hashCode(); + } + } + } + return hashCode; + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c0a4635c33b4..9ee88982f598 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11998,7 +11998,7 @@ public final class Settings { * @see #ENABLE_RESTRICTED_BUCKET * @hide */ - public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1; + public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 0; /** * Whether or not app auto restriction is enabled. When it is enabled, settings app will diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 8b5af29517cb..ef9d990168d2 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -21,6 +21,7 @@ import static android.view.InsetsState.ITYPE_IME; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; import android.view.SurfaceControl.Transaction; @@ -153,6 +154,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); } + @Override + public void onPerceptible(boolean perceptible) { + super.onPerceptible(perceptible); + final IBinder window = mController.getHost().getWindowToken(); + if (window != null) { + getImm().reportPerceptible(window, perceptible); + } + } + private boolean isDummyOrEmptyEditor(EditorInfo info) { // TODO(b/123044812): Handle dummy input gracefully in IME Insets API return info == null || (info.fieldId <= 0 && info.inputType <= 0); diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 74c186948b2f..3431c3ecc310 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,6 +16,7 @@ package android.view; +import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; /** @@ -64,4 +65,15 @@ public interface InsetsAnimationControlCallbacks { * previous calls to applySurfaceParams. */ void releaseSurfaceControlFromRt(SurfaceControl sc); + + /** + * Reports that the perceptibility of the given types has changed to the given value. + * + * A type is perceptible if it is not (almost) entirely off-screen and not (almost) entirely + * transparent. + * + * @param types the (public) types whose perceptibility has changed + * @param perceptible true, if the types are now perceptible, false if they are not perceptible + */ + void reportPerceptible(@InsetsType int types, boolean perceptible); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index baeae2fd2cdd..31da83ad5137 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -87,6 +87,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private float mPendingAlpha = 1.0f; @VisibleForTesting(visibility = PACKAGE) public boolean mReadyDispatched; + private Boolean mPerceptible; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, @@ -121,6 +122,14 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll new Bounds(mHiddenInsets, mShownInsets)); } + private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { + return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left) + && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top) + && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right) + && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom) + && currentAlpha >= 0.5f; + } + @Override public boolean hasZeroInsetsIme() { return mHasZeroInsetsIme; @@ -175,6 +184,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mPendingInsets = sanitize(insets); mPendingAlpha = sanitize(alpha); mController.scheduleApplyChangeInsets(this); + boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); + if (mPerceptible == null || perceptible != mPerceptible) { + mController.reportPerceptible(mTypes, perceptible); + mPerceptible = perceptible; + } } @VisibleForTesting diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 0e71b7643b7d..123604489da4 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -90,6 +90,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro // Since we don't push the SurfaceParams to the RT we can release directly sc.release(); } + + @Override + public void reportPerceptible(int types, boolean perceptible) { + mMainThreadHandler.post(() -> mOuterCallbacks.reportPerceptible(types, perceptible)); + } }; @UiThread diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5f99bfe432a4..a3c88bacb627 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -35,6 +35,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; +import android.os.IBinder; import android.os.Trace; import android.util.ArraySet; import android.util.Log; @@ -165,6 +166,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** @see ViewRootImpl#dipToPx */ int dipToPx(int dips); + + /** + * @return token associated with the host, if it has one. + */ + @Nullable + IBinder getWindowToken(); } private static final String TAG = "InsetsController"; @@ -1353,6 +1360,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mHost.releaseSurfaceControlFromRt(sc); } + @Override + public void reportPerceptible(int types, boolean perceptible) { + final ArraySet<Integer> internalTypes = toInternalType(types); + final int size = mSourceConsumers.size(); + for (int i = 0; i < size; i++) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (internalTypes.contains(consumer.getType())) { + consumer.onPerceptible(perceptible); + } + } + } + Host getHost() { return mHost; } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 565846638acc..b62e67c8f9e1 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -261,6 +261,15 @@ public class InsetsSourceConsumer { } /** + * Reports that this source's perceptibility has changed + * + * @param perceptible true if the source is perceptible, false otherwise. + * @see InsetsAnimationControlCallbacks#reportPerceptible + */ + public void onPerceptible(boolean perceptible) { + } + + /** * Notify listeners that window is now hidden. */ void notifyHidden() { @@ -339,5 +348,6 @@ public class InsetsSourceConsumer { t.hide(mSourceControl.getLeash()); } t.apply(); + onPerceptible(mRequestedVisible); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ed40483be337..b860bac0d001 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -21,7 +21,6 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.LAST_TYPE; import static android.view.InsetsState.SIZE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -4617,6 +4616,9 @@ public final class ViewRootImpl implements ViewParent, } void dispatchDetachedFromWindow() { + // Make sure we free-up insets resources if view never received onWindowFocusLost() + // because of a die-signal + mInsetsController.onWindowFocusLost(); mFirstInputStage.onDetachedFromWindow(); if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index f7ca3c2b7ddf..90a80cefc54d 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import android.annotation.NonNull; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -236,4 +237,16 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } return 0; } + + @Override + public IBinder getWindowToken() { + if (mViewRoot == null) { + return null; + } + final View view = mViewRoot.getView(); + if (view == null) { + return null; + } + return view.getWindowToken(); + } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 821dd742db9a..aedb59bfee42 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -541,6 +541,19 @@ public final class InputMethodManager { return servedView.hasWindowFocus() || isAutofillUIShowing(servedView); } + /** + * Reports whether the IME is currently perceptible or not, according to the leash applied by + * {@link android.view.WindowInsetsController}. + * @hide + */ + public void reportPerceptible(IBinder windowToken, boolean perceptible) { + try { + mService.reportPerceptible(windowToken, perceptible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class DelegateImpl implements ImeFocusController.InputMethodManagerDelegate { /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index e08dd9e34517..47edc497c79f 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -691,8 +691,14 @@ public class ChooserActivity extends ResolverActivity implements mPinnedSharedPrefs = getPinnedSharedPrefs(this); pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); + + + // Exclude out Nearby from main list if chip is present, to avoid duplication + ComponentName nearbySharingComponent = getNearbySharingComponent(); + boolean hasNearby = nearbySharingComponent != null; + if (pa != null) { - ComponentName[] names = new ComponentName[pa.length]; + ComponentName[] names = new ComponentName[pa.length + (hasNearby ? 1 : 0)]; for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof ComponentName)) { Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]); @@ -701,7 +707,14 @@ public class ChooserActivity extends ResolverActivity implements } names[i] = (ComponentName) pa[i]; } + if (hasNearby) { + names[names.length - 1] = nearbySharingComponent; + } + mFilteredComponentNames = names; + } else if (hasNearby) { + mFilteredComponentNames = new ComponentName[1]; + mFilteredComponentNames[0] = nearbySharingComponent; } pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 6f7695ce8c34..ca0856238b90 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -26,6 +26,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -97,7 +99,10 @@ public class UnlaunchableAppActivity extends Activity @Override public void onClick(DialogInterface dialog, int which) { if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) { - UserManager.get(this).requestQuietModeEnabled(false, UserHandle.of(mUserId), mTarget); + UserManager userManager = UserManager.get(this); + new Handler(Looper.getMainLooper()).post( + () -> userManager.requestQuietModeEnabled( + /* enableQuietMode= */ false, UserHandle.of(mUserId), mTarget)); } } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index d22f94213338..8ec51b89d240 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -72,5 +72,6 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); + oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); void removeImeSurface(); } diff --git a/core/proto/android/stats/connectivity/tethering.proto b/core/proto/android/stats/connectivity/tethering.proto index 6303b7d1870b..13f0b8c44fb5 100644 --- a/core/proto/android/stats/connectivity/tethering.proto +++ b/core/proto/android/stats/connectivity/tethering.proto @@ -87,7 +87,7 @@ enum UpstreamType { enum UserType { // Unknown. - USER_UNKOWNN = 0; + USER_UNKNOWN = 0; // Settings. USER_SETTINGS = 1; // System UI. diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml index a434c0b9b6a9..0dd9e9c7cd98 100644 --- a/core/res/res/drawable/chooser_action_button_bg.xml +++ b/core/res/res/drawable/chooser_action_button_bg.xml @@ -25,8 +25,8 @@ <shape android:shape="rectangle"> <corners android:radius="16dp"></corners> <stroke android:width="1dp" - android:color="?attr/textColorSecondary" /> - <solid android:color="?attr/colorBackground" /> + android:color="?attr/opacityListDivider" /> + <solid android:color="?attr/colorBackgroundFloating" /> </shape> </inset> </item> diff --git a/core/res/res/layout/chooser_action_button.xml b/core/res/res/layout/chooser_action_button.xml index 119b2e90292d..6af7937960f0 100644 --- a/core/res/res/layout/chooser_action_button.xml +++ b/core/res/res/layout/chooser_action_button.xml @@ -19,12 +19,12 @@ android:paddingStart="12dp" android:paddingEnd="12dp" android:drawablePadding="8dp" - android:textColor="?android:textColorSecondary" + android:textColor="?android:textColorPrimary" android:textSize="12sp" android:maxWidth="192dp" android:singleLine="true" android:clickable="true" android:background="@drawable/chooser_action_button_bg" - android:drawableTint="?android:attr/colorControlNormal" + android:drawableTint="@color/chooser_chip_icon" android:drawableTintMode="src_in" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index c413f8b036d7..1242c6dc8217 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -230,6 +230,7 @@ <color name="resolver_text_color_secondary_dark">#ffC4C6C6</color> <color name="resolver_empty_state_text">#FF202124</color> <color name="resolver_empty_state_icon">#FF5F6368</color> + <color name="chooser_chip_icon">#FF1A73E8</color> <!-- Blue 600 --> <!-- Color for personal app suspension notification button text and icon tint. --> <color name="personal_apps_suspension_notification_color">#1A73E8</color> diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 8eca650398bf..a2b1e3d69cd2 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -234,6 +234,24 @@ public class InsetsAnimationControlImplTest { verify(mMockListener).onFinished(mController); } + @Test + public void testPerceptible_insets() { + mController.setInsetsAndAlpha(mController.getHiddenStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), false); + + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), true); + } + + @Test + public void testPerceptible_alpha() { + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 0f, 1f); + verify(mMockController).reportPerceptible(systemBars(), false); + + mController.setInsetsAndAlpha(mController.getShownStateInsets(), 1f, 1f); + verify(mMockController).reportPerceptible(systemBars(), true); + } + private void assertPosition(Matrix m, Rect original, Rect transformed) { RectF rect = new RectF(original); rect.offsetTo(0, 0); diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index eb6ee9525bb9..5f231ffe4786 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -157,7 +157,7 @@ IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const { table_value->dataType = entry->type; table_value->data = entry->value; - return Result(ResTable_entry_handle::managed(table_entry)); + return Result(ResTable_entry_handle::managed(table_entry, [](auto p) { free(p); })); } static bool is_word_aligned(const void* data) { diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 21be81cb85bd..e351a46d633a 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1601,8 +1601,8 @@ class ResTable_entry_handle { entry_ = handle.entry_; } - inline static ResTable_entry_handle managed(ResTable_entry* entry) { - return ResTable_entry_handle(std::shared_ptr<const ResTable_entry>(entry)); + inline static ResTable_entry_handle managed(ResTable_entry* entry, void (*deleter)(void *)) { + return ResTable_entry_handle(std::shared_ptr<const ResTable_entry>(entry, deleter)); } inline static ResTable_entry_handle unmanaged(const ResTable_entry* entry) { diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 2cca669a592b..00a4c7e19f34 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -448,8 +448,7 @@ public final class AudioDeviceInfo { * Returns an array of supported encapsulation modes for the device. * * The array can include any of the {@code AudioTrack} encapsulation modes, - * e.g. {@link AudioTrack#ENCAPSULATION_MODE_NONE}, - * or {@link AudioTrack#ENCAPSULATION_MODE_ELEMENTARY_STREAM}. + * e.g. {@link AudioTrack#ENCAPSULATION_MODE_ELEMENTARY_STREAM}. * * @return An array of supported encapsulation modes for the device. This * may be an empty array if no encapsulation modes are supported. diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ea7a556f835b..3ac71b2cff1d 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4599,6 +4599,12 @@ public class AudioManager { /** * @hide + * Volume behavior for an audio device that has no particular volume behavior set. Invalid as + * an argument to {@link #setDeviceVolumeBehavior(int, String, int)}. + */ + public static final int DEVICE_VOLUME_BEHAVIOR_UNSET = -1; + /** + * @hide * Volume behavior for an audio device where a software attenuation is applied * @see #setDeviceVolumeBehavior(int, String, int) */ @@ -4647,6 +4653,18 @@ public class AudioManager { @Retention(RetentionPolicy.SOURCE) public @interface DeviceVolumeBehavior {} + /** @hide */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_UNSET, + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceVolumeBehaviorState {} + /** * @hide * Throws IAE on an invalid volume behavior value @@ -4713,7 +4731,7 @@ public class AudioManager { * @return the volume behavior for the device */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @DeviceVolumeBehavior int getDeviceVolumeBehavior(int deviceType, + public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress) { // verify arguments AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType); @@ -4728,8 +4746,8 @@ public class AudioManager { * @return the volume behavior for the device */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) - { + public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior( + @NonNull AudioDeviceAttributes device) { // verify arguments (validity of device type is enforced in server) Objects.requireNonNull(device); // communicate with service diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 9f3fc5d94a98..1c0a526f536c 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -280,7 +280,7 @@ public class AudioTrack extends PlayerBase /** * Encapsulation metadata type for framework tuner information. * - * TODO(b/147778408) Link: Fill in Tuner API info. + * Refer to the Android Media TV Tuner API for details. */ public static final int ENCAPSULATION_METADATA_TYPE_FRAMEWORK_TUNER = 1; diff --git a/packages/CtsShim/Android.bp b/packages/CtsShim/Android.bp index 34878035051d..49608b3856e0 100644 --- a/packages/CtsShim/Android.bp +++ b/packages/CtsShim/Android.bp @@ -45,6 +45,7 @@ android_app_import { presigned: true, apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v1", "com.android.apex.cts.shim.v2", "com.android.apex.cts.shim.v2_legacy", @@ -82,6 +83,7 @@ android_app_import { presigned: true, apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v1", "com.android.apex.cts.shim.v2", "com.android.apex.cts.shim.v2_legacy", diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index bcff63471302..9d52098f37d5 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -30,7 +30,7 @@ import java.io.PrintWriter; */ @ProvidesInterface(version = FalsingManager.VERSION) public interface FalsingManager { - int VERSION = 3; + int VERSION = 4; void onSuccessfulUnlock(); @@ -88,11 +88,11 @@ public interface FalsingManager { void onScreenOff(); - void onNotificatonStopDismissing(); + void onNotificationStopDismissing(); void onNotificationDismissed(); - void onNotificatonStartDismissing(); + void onNotificationStartDismissing(); void onNotificationDoubleTap(boolean accepted, float dx, float dy); diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_seamless_background.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_seamless_background.xml new file mode 100644 index 000000000000..3790378ff3ae --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/qs_media_seamless_background.xml @@ -0,0 +1,26 @@ +<?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 + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/media_seamless_border"> + <item android:id="@android:id/background"> + <shape + android:color="@android:color/transparent"> + <stroke android:width="1dp" android:color="@color/media_seamless_border"/> + <corners android:radius="24dp"/> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/qs_media_divider.xml b/packages/SystemUI/res-keyguard/layout/qs_media_divider.xml new file mode 100644 index 000000000000..1be489cdc700 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/qs_media_divider.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginBottom="16dp" + android:background="@color/media_divider"> +</View>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml index dc917316bef1..ee1173be0db9 100644 --- a/packages/SystemUI/res/layout/media_carousel.xml +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -23,7 +23,7 @@ android:clipChildren="false" android:clipToPadding="false" > - <com.android.systemui.media.UnboundHorizontalScrollView + <com.android.systemui.media.MediaScrollView android:id="@+id/media_carousel_scroller" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -41,14 +41,12 @@ > <!-- QSMediaPlayers will be added here dynamically --> </LinearLayout> - </com.android.systemui.media.UnboundHorizontalScrollView> + </com.android.systemui.media.MediaScrollView> <com.android.systemui.qs.PageIndicator android:id="@+id/media_page_indicator" android:layout_width="wrap_content" android:layout_height="48dp" android:layout_marginBottom="4dp" - android:layout_gravity="center_horizontal|bottom" - android:gravity="center" android:tint="@color/media_primary_text" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/media_carousel_settings_button.xml b/packages/SystemUI/res/layout/media_carousel_settings_button.xml new file mode 100644 index 000000000000..4570cb1d1d10 --- /dev/null +++ b/packages/SystemUI/res/layout/media_carousel_settings_button.xml @@ -0,0 +1,29 @@ +<?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. + --> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/settings_cog" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/controls_media_settings_button" + android:paddingStart="30dp" + android:paddingEnd="30dp" + android:paddingBottom="20dp" + android:paddingTop="20dp" + android:src="@drawable/ic_settings" + android:tint="@color/notification_gear_color" + android:visibility="invisible" + android:forceHasOverlappingRendering="false"/> diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index c17b0c8580ca..07bbb8f40eb8 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -26,6 +26,7 @@ android:gravity="center_horizontal|fill_vertical" android:background="@drawable/qs_media_background"> + <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> <FrameLayout android:id="@+id/notification_media_progress_time" android:layout_width="0dp" @@ -36,7 +37,7 @@ android:id="@+id/media_elapsed_time" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textColor="@color/media_primary_text" android:gravity="start" @@ -46,13 +47,36 @@ android:id="@+id/media_total_time" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" android:fontFamily="@*android:string/config_bodyFontFamily" android:textColor="@color/media_primary_text" android:gravity="end" android:textSize="14sp" /> </FrameLayout> + <!-- Actions must be ordered left-to-right even in RTL layout. However, they appear in a chain + with the album art and the title, and must as a group appear at the end of that chain. This is + accomplished by having the guidebox (an invisible view that is positioned around all 5 actions) + in the chain with the album art and the title. The actions are in a LTR chain bounded by that + guidebox, and the ambiguity of how wide the guidebox should be is resolved by using a barrier + which forces it's starting edge to be as far to the end as possible while fitting the actions. + --> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/media_action_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:barrierDirection="start" + /> + + <View + android:id="@+id/media_action_guidebox" + android:layout_width="0dp" + android:layout_height="48dp" + android:layout_marginTop="16dp" + android:visibility="invisible" + /> + <ImageButton android:id="@+id/action0" style="@style/MediaPlayer.Button" @@ -94,20 +118,20 @@ android:id="@+id/media_seamless" android:layout_width="0dp" android:layout_height="wrap_content" - android:foreground="@*android:drawable/media_seamless_background" + android:foreground="@drawable/qs_media_seamless_background" android:background="@drawable/qs_media_light_source" android:orientation="horizontal" android:forceHasOverlappingRendering="false" - android:paddingLeft="12dp" + android:paddingStart="12dp" android:paddingTop="6dp" - android:paddingRight="12dp" + android:paddingEnd="12dp" android:paddingBottom="6dp"> <ImageView android:id="@+id/media_seamless_image" android:layout_width="@dimen/qs_seamless_icon_size" android:layout_height="@dimen/qs_seamless_icon_size" - android:layout_marginRight="8dp" + android:layout_marginEnd="8dp" android:tint="@color/media_primary_text" android:src="@*android:drawable/ic_media_seamless" /> @@ -115,10 +139,11 @@ android:id="@+id/media_seamless_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_bodyFontFamily" + android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" android:text="@*android:string/ext_media_seamless_action" android:textColor="@color/media_primary_text" + android:textDirection="locale" android:textSize="14sp" /> </LinearLayout> @@ -140,13 +165,14 @@ /> <!-- Seek Bar --> + <!-- As per Material Design on Biderectionality, this is forced to LTR in code --> <SeekBar android:id="@+id/media_progress_bar" style="@android:style/Widget.ProgressBar.Horizontal" android:layout_width="0dp" android:layout_height="wrap_content" android:clickable="true" - android:maxHeight="3dp" + android:maxHeight="@dimen/qs_media_enabled_seekbar_height" android:paddingTop="16dp" android:paddingBottom="16dp" android:thumbTint="@color/media_primary_text" @@ -161,6 +187,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:singleLine="true" + android:fontFamily="@*android:string/config_headlineFontFamily" + android:textDirection="locale" android:textSize="14sp" /> <!-- Song name --> @@ -171,6 +199,7 @@ android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:singleLine="true" android:textColor="@color/media_primary_text" + android:textDirection="locale" android:textSize="16sp" /> <!-- Artist name --> @@ -178,16 +207,17 @@ android:id="@+id/header_artist" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@*android:string/config_bodyFontFamily" + android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" android:textColor="@color/media_secondary_text" + android:textDirection="locale" android:textSize="14sp" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" android:tint="@color/media_primary_text" - android:layout_width="16dp" - android:layout_height="16dp" /> + android:layout_width="20dp" + android:layout_height="20dp" /> <!-- Buttons to remove this view when no longer needed --> <include diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 761ab03ee87e..597644bf3295 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -62,6 +62,8 @@ android:focusable="true" android:accessibilityTraversalBefore="@android:id/edit"> <include layout="@layout/qs_footer_impl" /> + <include layout="@layout/qs_media_divider" + android:id="@+id/divider"/> </com.android.systemui.qs.QSPanel> </com.android.systemui.qs.NonInterceptingScrollView> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 196357c4794e..cb9e178de243 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -81,6 +81,8 @@ <color name="global_screenshot_dismiss_foreground">#FFFFFF</color> <color name="global_screenshot_background_protection_start">#80000000</color> <!-- 50% black --> + <!-- Media --> + <color name="media_divider">#85ffffff</color> <!-- Biometric dialog colors --> <color name="biometric_dialog_gray">#ff888888</color> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index d8a3c8c19eae..994a18110260 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -248,6 +248,8 @@ <color name="media_secondary_text">#99ffffff</color> <!-- 60% --> <color name="media_seekbar_progress">#c0ffffff</color> <color name="media_disabled">#80ffffff</color> + <color name="media_seamless_border">#26ffffff</color> <!-- 15% --> + <color name="media_divider">#1d000000</color> <!-- controls --> <color name="control_primary_text">#E6FFFFFF</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index df531a9c3e47..29a2ad4cba9f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1274,6 +1274,8 @@ <dimen name="qs_seamless_fallback_end_margin">16dp</dimen> <dimen name="qqs_media_spacing">16dp</dimen> <dimen name="qs_footer_horizontal_margin">22dp</dimen> + <dimen name="qs_media_disabled_seekbar_height">1dp</dimen> + <dimen name="qs_media_enabled_seekbar_height">3dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml index 0e886d6fa0b8..811e0e351bd3 100644 --- a/packages/SystemUI/res/xml/media_collapsed.xml +++ b/packages/SystemUI/res/xml/media_collapsed.xml @@ -85,7 +85,7 @@ app:layout_constraintTop_toBottomOf="@id/app_name" app:layout_constraintBottom_toTopOf="@id/header_artist" app:layout_constraintStart_toEndOf="@id/album_art" - app:layout_constraintEnd_toStartOf="@id/action0" + app:layout_constraintEnd_toStartOf="@id/media_action_barrier" app:layout_constraintHorizontal_bias="0"/> <!-- Artist name --> @@ -97,7 +97,7 @@ android:layout_marginBottom="24dp" app:layout_constraintTop_toBottomOf="@id/header_title" app:layout_constraintStart_toStartOf="@id/header_title" - app:layout_constraintEnd_toStartOf="@id/action0" + app:layout_constraintEnd_toStartOf="@id/media_action_barrier" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0"/> @@ -128,15 +128,37 @@ /> <Constraint + android:id="@+id/media_action_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintTop_toTopOf="parent" + app:barrierDirection="start" + app:constraint_referenced_ids="media_action_guidebox,action0,action1,action2,action3,action4" + /> + + <Constraint + android:id="@+id/media_action_guidebox" + android:layout_width="0dp" + android:layout_height="48dp" + android:layout_marginTop="18dp" + android:visibility="invisible" + app:layout_constraintTop_toBottomOf="@id/app_name" + app:layout_constraintStart_toEndOf="@id/header_title" + app:layout_constraintEnd_toEndOf="parent" + /> + + <Constraint android:id="@+id/action0" android:layout_width="48dp" android:layout_height="48dp" android:layout_marginStart="4dp" - android:layout_marginTop="16dp" + android:layout_marginEnd="4dp" + android:layout_marginTop="18dp" android:visibility="gone" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/app_name" - app:layout_constraintLeft_toRightOf="@id/header_title" + app:layout_constraintLeft_toLeftOf="@id/media_action_guidebox" app:layout_constraintRight_toLeftOf="@id/action1" > </Constraint> @@ -188,9 +210,10 @@ android:layout_marginEnd="4dp" android:visibility="gone" android:layout_marginTop="18dp" + app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/app_name" app:layout_constraintLeft_toRightOf="@id/action3" - app:layout_constraintRight_toRightOf="parent" + app:layout_constraintRight_toRightOf="@id/media_action_guidebox" > </Constraint> </ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml index 9b4caa430185..8432abcc16cb 100644 --- a/packages/SystemUI/res/xml/media_expanded.xml +++ b/packages/SystemUI/res/xml/media_expanded.xml @@ -182,6 +182,7 @@ android:layout_marginStart="4dp" android:layout_marginEnd="4dp" android:layout_marginBottom="@dimen/qs_media_panel_outer_padding" + app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintLeft_toRightOf="@id/action3" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="@id/action0" diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index c985b083f9a9..50828e878154 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -1504,6 +1504,7 @@ public class BubbleStackView extends FrameLayout && ((BadgedImageView) v).getKey().equals(bubble.getKey())) { mBubbleContainer.removeViewAt(i); bubble.cleanupViews(); + updatePointerPosition(); logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); return; } @@ -1554,6 +1555,14 @@ public class BubbleStackView extends FrameLayout mBubbleData.setShowingOverflow(true); } + if (mIsExpanded && mIsExpansionAnimating) { + // If the bubble selection changed during the expansion animation, the expanding bubble + // probably crashed or immediately removed itself (or, we just got unlucky with a new + // auto-expanding bubble showing up at just the right time). Cancel the animations so we + // can start fresh. + cancelAllExpandCollapseSwitchAnimations(); + } + // If we're expanded, screenshot the currently expanded bubble (before expanding the newly // selected bubble) so we can animate it out. if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { @@ -1879,35 +1888,37 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); } - mDelayedAnimationHandler.postDelayed(() -> - PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) - .spring(AnimatableScaleMatrix.SCALE_X, - AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), - mScaleInSpringConfig) - .spring(AnimatableScaleMatrix.SCALE_Y, - AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), - mScaleInSpringConfig) - .addUpdateListener((target, values) -> { - if (mExpandedBubble.getIconView() == null) { - return; - } - mExpandedViewContainerMatrix.postTranslate( - mExpandedBubble.getIconView().getTranslationX() - - bubbleWillBeAtX, - 0); - mExpandedViewContainer.setAnimationMatrix( - mExpandedViewContainerMatrix); - }) - .withEndActions(() -> { - if (mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView() - .setContentVisibility(true); - mExpandedBubble.getExpandedView() - .setSurfaceZOrderedOnTop(false); - } - }) - .start(), startDelay); + mDelayedAnimationHandler.postDelayed(() -> { + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + if (mExpandedBubble.getIconView() == null) { + return; + } + mExpandedViewContainerMatrix.postTranslate( + mExpandedBubble.getIconView().getTranslationX() + - bubbleWillBeAtX, + 0); + mExpandedViewContainer.setAnimationMatrix( + mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView() + .setContentVisibility(true); + mExpandedBubble.getExpandedView() + .setSurfaceZOrderedOnTop(false); + } + }) + .start(); + }, startDelay); } private void animateCollapse() { @@ -2035,6 +2046,7 @@ public class BubbleStackView extends FrameLayout return; } + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) .spring(AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), @@ -2068,6 +2080,15 @@ public class BubbleStackView extends FrameLayout mIsBubbleSwitchAnimating = false; } + private void cancelAllExpandCollapseSwitchAnimations() { + cancelDelayedExpandCollapseSwitchAnimations(); + + PhysicsAnimator.getInstance(mAnimatingOutSurfaceView).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + + mExpandedViewContainer.setAnimationMatrix(null); + } + private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) { if (mExpandListener != null && bubble != null) { mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey()); @@ -2587,6 +2608,7 @@ public class BubbleStackView extends FrameLayout bev.setContentVisibility(false); mExpandedViewContainerMatrix.setScaleX(0f); mExpandedViewContainerMatrix.setScaleY(0f); + mExpandedViewContainerMatrix.setTranslate(0f, 0f); mExpandedViewContainer.setVisibility(View.INVISIBLE); mExpandedViewContainer.setAlpha(0f); mExpandedViewContainer.addView(bev); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 8e232520a196..f2a4f159f959 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -237,6 +237,10 @@ public class ExpandedAnimationController } mAfterExpand = null; + + // Update bubble positions in case any bubbles were added or removed during the + // expansion animation. + updateBubblePositions(); }; } else { after = () -> { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java index e105795ad57f..646e62062dfb 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerFake.java @@ -201,7 +201,7 @@ public class FalsingManagerFake implements FalsingManager { } @Override - public void onNotificatonStopDismissing() { + public void onNotificationStopDismissing() { } @@ -211,7 +211,7 @@ public class FalsingManagerFake implements FalsingManager { } @Override - public void onNotificatonStartDismissing() { + public void onNotificationStartDismissing() { } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java index 37c7a2e3027f..cc64fb53f15f 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java @@ -481,15 +481,15 @@ public class FalsingManagerImpl implements FalsingManager { mDataCollector.onNotificationDismissed(); } - public void onNotificatonStartDismissing() { + public void onNotificationStartDismissing() { if (FalsingLog.ENABLED) { - FalsingLog.i("onNotificatonStartDismissing", ""); + FalsingLog.i("onNotificationStartDismissing", ""); } mHumanInteractionClassifier.setType(Classifier.NOTIFICATION_DISMISS); mDataCollector.onNotificatonStartDismissing(); } - public void onNotificatonStopDismissing() { + public void onNotificationStopDismissing() { mDataCollector.onNotificatonStopDismissing(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 79b691bb3e37..ef2ef4570fca 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -302,8 +302,8 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { } @Override - public void onNotificatonStopDismissing() { - mInternalFalsingManager.onNotificatonStopDismissing(); + public void onNotificationStopDismissing() { + mInternalFalsingManager.onNotificationStopDismissing(); } @Override @@ -312,8 +312,8 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { } @Override - public void onNotificatonStartDismissing() { - mInternalFalsingManager.onNotificatonStartDismissing(); + public void onNotificationStartDismissing() { + mInternalFalsingManager.onNotificationStartDismissing(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index caab18712b0b..62254a64dfcc 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -380,7 +380,7 @@ public class BrightLineFalsingManager implements FalsingManager { @Override - public void onNotificatonStopDismissing() { + public void onNotificationStopDismissing() { } @Override @@ -388,7 +388,7 @@ public class BrightLineFalsingManager implements FalsingManager { } @Override - public void onNotificatonStartDismissing() { + public void onNotificationStartDismissing() { updateInteractionType(Classifier.NOTIFICATION_DISMISS); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index a6af6a11d8b7..ec8bfc6fa2ae 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -73,7 +73,7 @@ class ControlsProviderLifecycleManager( companion object { private const val BIND_RETRY_DELAY = 1000L // ms - private const val LOAD_TIMEOUT_SECONDS = 30L // seconds + private const val LOAD_TIMEOUT_SECONDS = 20L // seconds private const val MAX_BIND_RETRIES = 5 private const val DEBUG = true private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 4884781c64de..b2821579c389 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -174,30 +174,32 @@ class ControlsFavoritingActivity @Inject constructor( subtitleView.visibility = View.GONE } else { statusText.visibility = View.GONE - } - pageIndicator.setNumPages(listOfStructures.size) - pageIndicator.setLocation(0f) - pageIndicator.visibility = - if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE - - ControlsAnimations.enterAnimation(pageIndicator).apply { - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - // Position the tooltip if necessary after animations are complete - // so we can get the position on screen. The tooltip is not - // rooted in the layout root. - if (pageIndicator.visibility == View.VISIBLE && + + pageIndicator.setNumPages(listOfStructures.size) + pageIndicator.setLocation(0f) + pageIndicator.visibility = + if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE + + ControlsAnimations.enterAnimation(pageIndicator).apply { + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + // Position the tooltip if necessary after animations are complete + // so we can get the position on screen. The tooltip is not + // rooted in the layout root. + if (pageIndicator.visibility == View.VISIBLE && mTooltipManager != null) { - val p = IntArray(2) - pageIndicator.getLocationOnScreen(p) - val x = p[0] + pageIndicator.width / 2 - val y = p[1] + pageIndicator.height - mTooltipManager?.show(R.string.controls_structure_tooltip, x, y) + val p = IntArray(2) + pageIndicator.getLocationOnScreen(p) + val x = p[0] + pageIndicator.width / 2 + val y = p[1] + pageIndicator.height + mTooltipManager?.show( + R.string.controls_structure_tooltip, x, y) + } } - } - }) - }.start() - ControlsAnimations.enterAnimation(structurePager).start() + }) + }.start() + ControlsAnimations.enterAnimation(structurePager).start() + } } }, Consumer { runnable -> cancelLoadRunnable = runnable }) } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 951dc9936e1c..3bac196ca59f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -35,7 +35,6 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.DelayedWakeLock; @@ -56,9 +55,9 @@ public class DozeFactory { private final DockManager mDockManager; private final IWallpaperManager mWallpaperManager; private final ProximitySensor mProximitySensor; + private final ProximitySensor.ProximityCheck mProximityCheck; private final DelayedWakeLock.Builder mDelayedWakeLockBuilder; private final Handler mHandler; - private final DelayableExecutor mDelayableExecutor; private final BiometricUnlockController mBiometricUnlockController; private final BroadcastDispatcher mBroadcastDispatcher; private final DozeHost mDozeHost; @@ -69,9 +68,8 @@ public class DozeFactory { AsyncSensorManager asyncSensorManager, AlarmManager alarmManager, WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, @Nullable IWallpaperManager wallpaperManager, - ProximitySensor proximitySensor, + ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proximityCheck, DelayedWakeLock.Builder delayedWakeLockBuilder, @Main Handler handler, - @Main DelayableExecutor delayableExecutor, BiometricUnlockController biometricUnlockController, BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) { mFalsingManager = falsingManager; @@ -85,9 +83,9 @@ public class DozeFactory { mDockManager = dockManager; mWallpaperManager = wallpaperManager; mProximitySensor = proximitySensor; + mProximityCheck = proximityCheck; mDelayedWakeLockBuilder = delayedWakeLockBuilder; mHandler = handler; - mDelayableExecutor = delayableExecutor; mBiometricUnlockController = biometricUnlockController; mBroadcastDispatcher = broadcastDispatcher; mDozeHost = dozeHost; @@ -112,8 +110,8 @@ public class DozeFactory { new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()), new DozeFalsingManagerAdapter(mFalsingManager), createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost, - mAlarmManager, config, mDozeParameters, mDelayableExecutor, wakeLock, - machine, mDockManager, mDozeLog), + mAlarmManager, config, mDozeParameters, wakeLock, + machine, mDockManager, mDozeLog, mProximityCheck), createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler, mAlarmManager, mDozeParameters, mDozeLog), new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters, @@ -140,12 +138,13 @@ public class DozeFactory { private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager, DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config, - DozeParameters params, DelayableExecutor delayableExecutor, WakeLock wakeLock, - DozeMachine machine, DockManager dockManager, DozeLog dozeLog) { + DozeParameters params, WakeLock wakeLock, + DozeMachine machine, DockManager dockManager, DozeLog dozeLog, + ProximitySensor.ProximityCheck proximityCheck) { boolean allowPulseTriggers = true; return new DozeTriggers(context, machine, host, alarmManager, config, params, - sensorManager, delayableExecutor, wakeLock, allowPulseTriggers, dockManager, - mProximitySensor, dozeLog, mBroadcastDispatcher); + sensorManager, wakeLock, allowPulseTriggers, dockManager, + mProximitySensor, proximityCheck, dozeLog, mBroadcastDispatcher); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 6a5501445a4d..82639ba4375e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -42,7 +42,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; -import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; @@ -153,8 +152,8 @@ public class DozeTriggers implements DozeMachine.Part { public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, AsyncSensorManager sensorManager, - DelayableExecutor delayableExecutor, WakeLock wakeLock, boolean allowPulseTriggers, - DockManager dockManager, ProximitySensor proximitySensor, + WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager, + ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck, DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) { mContext = context; mMachine = machine; @@ -168,7 +167,7 @@ public class DozeTriggers implements DozeMachine.Part { config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; - mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, delayableExecutor); + mProxCheck = proxCheck; mDozeLog = dozeLog; mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt index 5f43e43c03c6..8ef20f89085c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt @@ -46,7 +46,9 @@ class KeyguardMediaController @Inject constructor( }) } - private var view: MediaHeaderView? = null + var visibilityChangedListener: ((Boolean) -> Unit)? = null + var view: MediaHeaderView? = null + private set /** * Attach this controller to a media view, initializing its state @@ -54,9 +56,10 @@ class KeyguardMediaController @Inject constructor( fun attach(mediaView: MediaHeaderView) { view = mediaView // First let's set the desired state that we want for this host - mediaHost.visibleChangedListener = { updateVisibility() } + mediaHost.addVisibilityChangeListener { updateVisibility() } mediaHost.expansion = 0.0f mediaHost.showsOnlyActiveMedia = true + mediaHost.falsingProtectionNeeded = true // Let's now initialize this view, which also creates the host view for us. mediaHost.init(MediaHierarchyManager.LOCATION_LOCKSCREEN) @@ -70,6 +73,11 @@ class KeyguardMediaController @Inject constructor( !bypassController.bypassEnabled && keyguardOrUserSwitcher && notifLockscreenUserManager.shouldShowLockscreenNotifications() - view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE + val previousVisibility = view?.visibility ?: View.GONE + val newVisibility = if (shouldBeVisible) View.VISIBLE else View.GONE + view?.visibility = newVisibility + if (previousVisibility != newVisibility) { + visibilityChangedListener?.invoke(shouldBeVisible) + } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index bccc3abd8a27..e2215d57a094 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -1,42 +1,67 @@ package com.android.systemui.media import android.content.Context +import android.content.Intent import android.graphics.Color +import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.view.LayoutInflater -import android.view.GestureDetector -import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.widget.HorizontalScrollView import android.widget.LinearLayout -import androidx.core.view.GestureDetectorCompat import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.Utils import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring +import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject import javax.inject.Provider import javax.inject.Singleton -private const val FLING_SLOP = 1000000 +private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS) /** * Class that is responsible for keeping the view carousel up to date. * This also handles changes in state and applies them to the media carousel like the expansion. */ @Singleton -class MediaViewManager @Inject constructor( +class MediaCarouselController @Inject constructor( private val context: Context, private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, private val mediaHostStatesManager: MediaHostStatesManager, + private val activityStarter: ActivityStarter, + @Main executor: DelayableExecutor, mediaManager: MediaDataCombineLatest, - configurationController: ConfigurationController + configurationController: ConfigurationController, + mediaDataManager: MediaDataManager, + falsingManager: FalsingManager ) { + /** + * The current width of the carousel + */ + private var currentCarouselWidth: Int = 0 + + /** + * The current height of the carousel + */ + private var currentCarouselHeight: Int = 0 + + /** + * Are we currently showing only active players + */ + private var currentlyShowingOnlyActive: Boolean = false /** + * Is the player currently visible (at the end of the transformation + */ + private var playersVisible: Boolean = false + /** * The desired location where we'll be at the end of the transformation. Usually this matches * the end location, except when we're still waiting on a state update call. */ @@ -73,17 +98,16 @@ class MediaViewManager @Inject constructor( private var carouselMeasureHeight: Int = 0 private var playerWidthPlusPadding: Int = 0 private var desiredHostState: MediaHostState? = null - private val mediaCarousel: HorizontalScrollView + private val mediaCarousel: MediaScrollView + private val mediaCarouselScrollHandler: MediaCarouselScrollHandler val mediaFrame: ViewGroup val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf() + private lateinit var settingsButton: View private val mediaData: MutableMap<String, MediaData> = mutableMapOf() private val mediaContent: ViewGroup private val pageIndicator: PageIndicator - private val gestureDetector: GestureDetectorCompat private val visualStabilityCallback: VisualStabilityManager.Callback - private var activeMediaIndex: Int = 0 private var needsReordering: Boolean = false - private var scrollIntoCurrentMedia: Int = 0 private var currentlyExpanded = true set(value) { if (field != value) { @@ -93,50 +117,25 @@ class MediaViewManager @Inject constructor( } } } - private val scrollChangedListener = object : View.OnScrollChangeListener { - override fun onScrollChange( - v: View?, - scrollX: Int, - scrollY: Int, - oldScrollX: Int, - oldScrollY: Int - ) { - if (playerWidthPlusPadding == 0) { - return - } - onMediaScrollingChanged(scrollX / playerWidthPlusPadding, - scrollX % playerWidthPlusPadding) - } - } - private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { - override fun onFling( - eStart: MotionEvent?, - eCurrent: MotionEvent?, - vX: Float, - vY: Float - ): Boolean { - return this@MediaViewManager.onFling(eStart, eCurrent, vX, vY) - } - } - private val touchListener = object : View.OnTouchListener { - override fun onTouch(view: View, motionEvent: MotionEvent?): Boolean { - return this@MediaViewManager.onTouch(view, motionEvent) - } - } private val configListener = object : ConfigurationController.ConfigurationListener { override fun onDensityOrFontScaleChanged() { recreatePlayers() + inflateSettingsButton() + } + + override fun onOverlayChanged() { + inflateSettingsButton() } } init { - gestureDetector = GestureDetectorCompat(context, gestureListener) mediaFrame = inflateMediaCarousel() mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller) pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator) - mediaCarousel.setOnScrollChangeListener(scrollChangedListener) - mediaCarousel.setOnTouchListener(touchListener) - mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER) + mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, + executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation, + falsingManager) + inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) visualStabilityCallback = VisualStabilityManager.Callback { @@ -152,8 +151,15 @@ class MediaViewManager @Inject constructor( mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { oldKey?.let { mediaData.remove(it) } - mediaData.put(key, data) - addOrUpdatePlayer(key, oldKey, data) + if (!data.active && !Utils.useMediaResumption(context)) { + // This view is inactive, let's remove this! This happens e.g when dismissing / + // timing out a view. We still have the data around because resumption could + // be on, but we should save the resources and release this. + onMediaDataRemoved(key) + } else { + mediaData.put(key, data) + addOrUpdatePlayer(key, oldKey, data) + } } override fun onMediaDataRemoved(key: String) { @@ -161,6 +167,11 @@ class MediaViewManager @Inject constructor( removePlayer(key) } }) + mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + // The pageIndicator is not laid out yet when we get the current state update, + // Lets make sure we have the right dimensions + updatePageIndicatorLocation() + } mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback { override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) { if (location == desiredLocation) { @@ -170,6 +181,20 @@ class MediaViewManager @Inject constructor( }) } + private fun inflateSettingsButton() { + val settings = LayoutInflater.from(context).inflate(R.layout.media_carousel_settings_button, + mediaFrame, false) as View + if (this::settingsButton.isInitialized) { + mediaFrame.removeView(settingsButton) + } + settingsButton = settings + mediaFrame.addView(settingsButton) + mediaCarouselScrollHandler.onSettingsButtonUpdated(settings) + settingsButton.setOnClickListener { + activityStarter.startActivity(settingsIntent, true /* dismissShade */) + } + } + private fun inflateMediaCarousel(): ViewGroup { return LayoutInflater.from(context).inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup @@ -183,68 +208,7 @@ class MediaViewManager @Inject constructor( mediaContent.addView(view, 0) } } - updateMediaPaddings() - updatePlayerVisibilities() - } - - private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) { - val wasScrolledIn = scrollIntoCurrentMedia != 0 - scrollIntoCurrentMedia = scrollInAmount - val nowScrolledIn = scrollIntoCurrentMedia != 0 - if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) { - activeMediaIndex = newIndex - updatePlayerVisibilities() - } - val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) - scrollInAmount.toFloat() / playerWidthPlusPadding else 0f - pageIndicator.setLocation(location) - } - - private fun onTouch(view: View, motionEvent: MotionEvent?): Boolean { - if (gestureDetector.onTouchEvent(motionEvent)) { - return true - } - if (motionEvent?.getAction() == MotionEvent.ACTION_UP) { - val pos = mediaCarousel.scrollX % playerWidthPlusPadding - if (pos > playerWidthPlusPadding / 2) { - mediaCarousel.smoothScrollBy(playerWidthPlusPadding - pos, 0) - } else { - mediaCarousel.smoothScrollBy(-1 * pos, 0) - } - return true - } - return view.onTouchEvent(motionEvent) - } - - private fun onFling( - eStart: MotionEvent?, - eCurrent: MotionEvent?, - vX: Float, - vY: Float - ): Boolean { - if (vX * vX < 0.5 * vY * vY) { - return false - } - if (vX * vX < FLING_SLOP) { - return false - } - val pos = mediaCarousel.scrollX - val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0 - var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex - destIndex = Math.max(0, destIndex) - destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex) - val view = mediaContent.getChildAt(destIndex) - mediaCarousel.smoothScrollTo(view.left, mediaCarousel.scrollY) - return true - } - - private fun updatePlayerVisibilities() { - val scrolledIn = scrollIntoCurrentMedia != 0 - for (i in 0 until mediaContent.childCount) { - val view = mediaContent.getChildAt(i) - val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn) - view.visibility = if (visible) View.VISIBLE else View.INVISIBLE - } + mediaCarouselScrollHandler.onPlayersChanged() } private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { @@ -259,6 +223,7 @@ class MediaViewManager @Inject constructor( existingPlayer = mediaControlPanelFactory.get() existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) + existingPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions mediaPlayers[key] = existingPlayer val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) @@ -280,28 +245,18 @@ class MediaViewManager @Inject constructor( } } existingPlayer?.bind(data) - updateMediaPaddings() updatePageIndicator() - updatePlayerVisibilities() + mediaCarouselScrollHandler.onPlayersChanged() mediaCarousel.requiresRemeasuring = true } private fun removePlayer(key: String) { val removed = mediaPlayers.remove(key) removed?.apply { - val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= - activeMediaIndex + mediaCarouselScrollHandler.onPrePlayerRemoved(removed) mediaContent.removeView(removed.view?.player) removed.onDestroy() - updateMediaPaddings() - if (beforeActive) { - // also update the index here since the scroll below might not always lead - // to a scrolling changed - activeMediaIndex = Math.max(0, activeMediaIndex - 1) - mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - - playerWidthPlusPadding, 0) - } - updatePlayerVisibilities() + mediaCarouselScrollHandler.onPlayersChanged() updatePageIndicator() } } @@ -317,20 +272,6 @@ class MediaViewManager @Inject constructor( } } - private fun updateMediaPaddings() { - val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) - val childCount = mediaContent.childCount - for (i in 0 until childCount) { - val mediaView = mediaContent.getChildAt(i) - val desiredPaddingEnd = if (i == childCount - 1) 0 else padding - val layoutParams = mediaView.layoutParams as ViewGroup.MarginLayoutParams - if (layoutParams.marginEnd != desiredPaddingEnd) { - layoutParams.marginEnd = desiredPaddingEnd - mediaView.layoutParams = layoutParams - } - } - } - private fun updatePageIndicator() { val numPages = mediaContent.getChildCount() pageIndicator.setNumPages(numPages, Color.WHITE) @@ -342,6 +283,12 @@ class MediaViewManager @Inject constructor( /** * Set a new interpolated state for all players. This is a state that is usually controlled * by a finger movement where the user drags from one state to the next. + * + * @param startLocation the start location of our state or -1 if this is directly set + * @param endLocation the ending location of our state. + * @param progress the progress of the transition between startLocation and endlocation. If + * this is not a guided transformation, this will be 1.0f + * @param immediately should this state be applied immediately, canceling all animations? */ fun setCurrentState( @MediaLocation startLocation: Int, @@ -349,9 +296,6 @@ class MediaViewManager @Inject constructor( progress: Float, immediately: Boolean ) { - // Hack: Since the indicator doesn't move with the player expansion, just make it disappear - // and then reappear at the end. - pageIndicator.alpha = if (progress == 1f || progress == 0f) 1f else 0f if (startLocation != currentStartLocation || endLocation != currentEndLocation || progress != currentTransitionProgress || @@ -363,6 +307,51 @@ class MediaViewManager @Inject constructor( for (mediaPlayer in mediaPlayers.values) { updatePlayerToState(mediaPlayer, immediately) } + maybeResetSettingsCog() + } + } + + private fun updatePageIndicatorLocation() { + // Update the location of the page indicator, carousel clipping + pageIndicator.translationX = (currentCarouselWidth - pageIndicator.width) / 2.0f + + mediaCarouselScrollHandler.contentTranslation + val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams + pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height - + layoutParams.bottomMargin).toFloat() + } + + /** + * Update the dimension of this carousel. + */ + private fun updateCarouselDimensions() { + var width = 0 + var height = 0 + for (mediaPlayer in mediaPlayers.values) { + val controller = mediaPlayer.mediaViewController + width = Math.max(width, controller.currentWidth) + height = Math.max(height, controller.currentHeight) + } + if (width != currentCarouselWidth || height != currentCarouselHeight) { + currentCarouselWidth = width + currentCarouselHeight = height + mediaCarouselScrollHandler.setCarouselBounds(currentCarouselWidth, currentCarouselHeight) + updatePageIndicatorLocation() + } + } + + private fun maybeResetSettingsCog() { + val hostStates = mediaHostStatesManager.mediaHostStates + val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia + ?: true + val startShowsActive = hostStates[currentStartLocation]?.showsOnlyActiveMedia + ?: endShowsActive + if (currentlyShowingOnlyActive != endShowsActive || + ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && + startShowsActive != endShowsActive)) { + /// Whenever we're transitioning from between differing states or the endstate differs + // we reset the translation + currentlyShowingOnlyActive = endShowsActive + mediaCarouselScrollHandler.resetTranslation(animate = true) } } @@ -404,6 +393,15 @@ class MediaViewManager @Inject constructor( } mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } + mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia + mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded + val nowVisible = it.visible + if (nowVisible != playersVisible) { + playersVisible = nowVisible + if (nowVisible) { + mediaCarouselScrollHandler.resetTranslation() + } + } updateCarouselSize() } } @@ -420,16 +418,7 @@ class MediaViewManager @Inject constructor( carouselMeasureHeight = height playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize( R.dimen.qs_media_padding) - // The player width has changed, let's update the scroll position to make sure - // it's still at the same place - var newScroll = activeMediaIndex * playerWidthPlusPadding - if (scrollIntoCurrentMedia > playerWidthPlusPadding) { - newScroll += playerWidthPlusPadding - - (scrollIntoCurrentMedia - playerWidthPlusPadding) - } else { - newScroll += scrollIntoCurrentMedia - } - mediaCarousel.scrollX = newScroll + mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding // Let's remeasure the carousel val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0 val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0 diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt new file mode 100644 index 000000000000..993c05fbbd6f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media + +import android.graphics.Outline +import android.util.MathUtils +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import androidx.core.view.GestureDetectorCompat +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.SpringForce +import com.android.settingslib.Utils +import com.android.systemui.Gefingerpoken +import com.android.systemui.qs.PageIndicator +import com.android.systemui.R +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.util.animation.PhysicsAnimator +import com.android.systemui.util.concurrency.DelayableExecutor + +private const val FLING_SLOP = 1000000 +private const val DISMISS_DELAY = 100L +private const val RUBBERBAND_FACTOR = 0.2f +private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f + +/** + * Default spring configuration to use for animations where stiffness and/or damping ratio + * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. + */ +private val translationConfig = PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, + SpringForce.DAMPING_RATIO_LOW_BOUNCY) + +/** + * A controller class for the media scrollview, responsible for touch handling + */ +class MediaCarouselScrollHandler( + private val scrollView: MediaScrollView, + private val pageIndicator: PageIndicator, + private val mainExecutor: DelayableExecutor, + private val dismissCallback: () -> Unit, + private var translationChangedListener: () -> Unit, + private val falsingManager: FalsingManager +) { + /** + * Do we need falsing protection? + */ + var falsingProtectionNeeded: Boolean = false + /** + * The width of the carousel + */ + private var carouselWidth: Int = 0 + + /** + * The height of the carousel + */ + private var carouselHeight: Int = 0 + + /** + * How much are we scrolled into the current media? + */ + private var cornerRadius: Int = 0 + + /** + * The content where the players are added + */ + private var mediaContent: ViewGroup + /** + * The gesture detector to detect touch gestures + */ + private val gestureDetector: GestureDetectorCompat + + /** + * The settings button view + */ + private lateinit var settingsButton: View + + /** + * What's the currently active player index? + */ + var activeMediaIndex: Int = 0 + private set + /** + * How much are we scrolled into the current media? + */ + private var scrollIntoCurrentMedia: Int = 0 + + /** + * how much is the content translated in X + */ + var contentTranslation = 0.0f + private set(value) { + field = value + mediaContent.translationX = value + updateSettingsPresentation() + translationChangedListener.invoke() + updateClipToOutline() + } + + /** + * The width of a player including padding + */ + var playerWidthPlusPadding: Int = 0 + set(value) { + field = value + // The player width has changed, let's update the scroll position to make sure + // it's still at the same place + var newScroll = activeMediaIndex * playerWidthPlusPadding + if (scrollIntoCurrentMedia > playerWidthPlusPadding) { + newScroll += playerWidthPlusPadding - + (scrollIntoCurrentMedia - playerWidthPlusPadding) + } else { + newScroll += scrollIntoCurrentMedia + } + scrollView.scrollX = newScroll + } + + /** + * Does the dismiss currently show the setting cog? + */ + var showsSettingsButton: Boolean = false + + /** + * A utility to detect gestures, used in the touch listener + */ + private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onFling( + eStart: MotionEvent?, + eCurrent: MotionEvent?, + vX: Float, + vY: Float + ) = onFling(vX, vY) + + override fun onScroll( + down: MotionEvent?, + lastMotion: MotionEvent?, + distanceX: Float, + distanceY: Float + ) = onScroll(down!!, lastMotion!!, distanceX) + + override fun onDown(e: MotionEvent?): Boolean { + if (falsingProtectionNeeded) { + falsingManager.onNotificationStartDismissing() + } + return false + } + } + + /** + * The touch listener for the scroll view + */ + private val touchListener = object : Gefingerpoken { + override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!) + override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!) + } + + /** + * A listener that is invoked when the scrolling changes to update player visibilities + */ + private val scrollChangedListener = object : View.OnScrollChangeListener { + override fun onScrollChange( + v: View?, + scrollX: Int, + scrollY: Int, + oldScrollX: Int, + oldScrollY: Int + ) { + if (playerWidthPlusPadding == 0) { + return + } + onMediaScrollingChanged(scrollX / playerWidthPlusPadding, + scrollX % playerWidthPlusPadding) + } + } + + init { + gestureDetector = GestureDetectorCompat(scrollView.context, gestureListener) + scrollView.touchListener = touchListener + scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER) + mediaContent = scrollView.contentContainer + scrollView.setOnScrollChangeListener(scrollChangedListener) + scrollView.outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View?, outline: Outline?) { + outline?.setRoundRect(0, 0, carouselWidth, carouselHeight, cornerRadius.toFloat()) + } + } + } + + fun onSettingsButtonUpdated(button: View) { + settingsButton = button + // We don't have a context to resolve, lets use the settingsbuttons one since that is + // reinflated appropriately + cornerRadius = settingsButton.resources.getDimensionPixelSize( + Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius)) + updateSettingsPresentation() + scrollView.invalidateOutline() + } + + private fun updateSettingsPresentation() { + if (showsSettingsButton) { + val settingsOffset = MathUtils.map( + 0.0f, + getMaxTranslation().toFloat(), + 0.0f, + 1.0f, + Math.abs(contentTranslation)) + val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width * + SETTINGS_BUTTON_TRANSLATION_FRACTION + val newTranslationX: Float + if (contentTranslation > 0) { + newTranslationX = settingsTranslation + } else { + newTranslationX = scrollView.width - settingsTranslation - settingsButton.width + } + val rotation = (1.0f - settingsOffset) * 50 + settingsButton.rotation = rotation * -Math.signum(contentTranslation) + val alpha = MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset) + settingsButton.alpha = alpha + settingsButton.visibility = if (alpha != 0.0f) View.VISIBLE else View.INVISIBLE + settingsButton.translationX = newTranslationX + settingsButton.translationY = (scrollView.height - settingsButton.height) / 2.0f + } else { + settingsButton.visibility = View.INVISIBLE + } + } + + private fun onTouch(motionEvent: MotionEvent): Boolean { + val isUp = motionEvent.action == MotionEvent.ACTION_UP + if (isUp && falsingProtectionNeeded) { + falsingManager.onNotificationStopDismissing() + } + if (gestureDetector.onTouchEvent(motionEvent)) { + if (isUp) { + // If this is an up and we're flinging, we don't want to have this touch reach + // the view, otherwise that would scroll, while we are trying to snap to the + // new page. Let's dispatch a cancel instead. + scrollView.cancelCurrentScroll() + return true + } else { + // Pass touches to the scrollView + return false + } + } + if (isUp || motionEvent.action == MotionEvent.ACTION_CANCEL) { + // It's an up and the fling didn't take it above + val pos = scrollView.scrollX % playerWidthPlusPadding + val scollXAmount: Int + if (pos > playerWidthPlusPadding / 2) { + scollXAmount = playerWidthPlusPadding - pos + } else { + scollXAmount = -1 * pos + } + if (scollXAmount != 0) { + // Delay the scrolling since scrollView calls springback which cancels + // the animation again.. + mainExecutor.execute { + scrollView.smoothScrollBy(scollXAmount, 0) + } + } + val currentTranslation = scrollView.getContentTranslation() + if (currentTranslation != 0.0f) { + // We started a Swipe but didn't end up with a fling. Let's either go to the + // dismissed position or go back. + val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 + || isFalseTouch() + val newTranslation: Float + if (springBack) { + newTranslation = 0.0f + } else { + newTranslation = getMaxTranslation() * Math.signum(currentTranslation) + if (!showsSettingsButton) { + // Delay the dismiss a bit to avoid too much overlap. Waiting until the + // animation has finished also feels a bit too slow here. + mainExecutor.executeDelayed({ + dismissCallback.invoke() + }, DISMISS_DELAY) + } + } + PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, + newTranslation, startVelocity = 0.0f, config = translationConfig).start() + scrollView.animationTargetX = newTranslation + } + } + // Always pass touches to the scrollView + return false + } + + private fun isFalseTouch() = falsingProtectionNeeded && falsingManager.isFalseTouch + + private fun getMaxTranslation() = if (showsSettingsButton) { + settingsButton.width + } else { + playerWidthPlusPadding + } + + private fun onInterceptTouch(motionEvent: MotionEvent): Boolean { + return gestureDetector.onTouchEvent(motionEvent) + } + + fun onScroll(down: MotionEvent, + lastMotion: MotionEvent, + distanceX: Float): Boolean { + val totalX = lastMotion.x - down.x + val currentTranslation = scrollView.getContentTranslation() + if (currentTranslation != 0.0f || + !scrollView.canScrollHorizontally((-totalX).toInt())) { + var newTranslation = currentTranslation - distanceX + val absTranslation = Math.abs(newTranslation) + if (absTranslation > getMaxTranslation()) { + // Rubberband all translation above the maximum + if (Math.signum(distanceX) != Math.signum(currentTranslation)) { + // The movement is in the same direction as our translation, + // Let's rubberband it. + if (Math.abs(currentTranslation) > getMaxTranslation()) { + // we were already overshooting before. Let's add the distance + // fully rubberbanded. + newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR + } else { + // We just crossed the boundary, let's rubberband it all + newTranslation = Math.signum(newTranslation) * (getMaxTranslation() + + (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR) + } + } // Otherwise we don't have do do anything, and will remove the unrubberbanded + // translation + } + if (Math.signum(newTranslation) != Math.signum(currentTranslation) + && currentTranslation != 0.0f) { + // We crossed the 0.0 threshold of the translation. Let's see if we're allowed + // to scroll into the new direction + if (scrollView.canScrollHorizontally(-newTranslation.toInt())) { + // We can actually scroll in the direction where we want to translate, + // Let's make sure to stop at 0 + newTranslation = 0.0f + } + } + val physicsAnimator = PhysicsAnimator.getInstance(this) + if (physicsAnimator.isRunning()) { + physicsAnimator.spring(CONTENT_TRANSLATION, + newTranslation, startVelocity = 0.0f, config = translationConfig).start() + } else { + contentTranslation = newTranslation + } + scrollView.animationTargetX = newTranslation + return true + } + return false + } + + private fun onFling( + vX: Float, + vY: Float + ): Boolean { + if (vX * vX < 0.5 * vY * vY) { + return false + } + if (vX * vX < FLING_SLOP) { + return false + } + val currentTranslation = scrollView.getContentTranslation() + if (currentTranslation != 0.0f) { + // We're translated and flung. Let's see if the fling is in the same direction + val newTranslation: Float + if (Math.signum(vX) != Math.signum(currentTranslation) || isFalseTouch()) { + // The direction of the fling isn't the same as the translation, let's go to 0 + newTranslation = 0.0f + } else { + newTranslation = getMaxTranslation() * Math.signum(currentTranslation) + // Delay the dismiss a bit to avoid too much overlap. Waiting until the animation + // has finished also feels a bit too slow here. + if (!showsSettingsButton) { + mainExecutor.executeDelayed({ + dismissCallback.invoke() + }, DISMISS_DELAY) + } + } + PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, + newTranslation, startVelocity = vX, config = translationConfig).start() + scrollView.animationTargetX = newTranslation + } else { + // We're flinging the player! Let's go either to the previous or to the next player + val pos = scrollView.scrollX + val currentIndex = if (playerWidthPlusPadding > 0) pos / playerWidthPlusPadding else 0 + var destIndex = if (vX <= 0) currentIndex + 1 else currentIndex + destIndex = Math.max(0, destIndex) + destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex) + val view = mediaContent.getChildAt(destIndex) + // We need to post this since we're dispatching a touch to the underlying view to cancel + // but canceling will actually abort the animation. + mainExecutor.execute { + scrollView.smoothScrollTo(view.left, scrollView.scrollY) + } + } + return true + } + + /** + * Reset the translation of the players when swiped + */ + fun resetTranslation(animate: Boolean = false) { + if (scrollView.getContentTranslation() != 0.0f) { + if (animate) { + PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, + 0.0f, config = translationConfig).start() + scrollView.animationTargetX = 0.0f + } else { + PhysicsAnimator.getInstance(this).cancel() + contentTranslation = 0.0f + } + } + } + + private fun updateClipToOutline() { + val clip = contentTranslation != 0.0f || scrollIntoCurrentMedia != 0 + scrollView.clipToOutline = clip + } + + private fun onMediaScrollingChanged(newIndex: Int, scrollInAmount: Int) { + val wasScrolledIn = scrollIntoCurrentMedia != 0 + scrollIntoCurrentMedia = scrollInAmount + val nowScrolledIn = scrollIntoCurrentMedia != 0 + if (newIndex != activeMediaIndex || wasScrolledIn != nowScrolledIn) { + activeMediaIndex = newIndex + updatePlayerVisibilities() + } + val location = activeMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) + scrollInAmount.toFloat() / playerWidthPlusPadding else 0f + pageIndicator.setLocation(location) + updateClipToOutline() + } + + /** + * Notified whenever the players or their order has changed + */ + fun onPlayersChanged() { + updatePlayerVisibilities() + updateMediaPaddings() + } + + private fun updateMediaPaddings() { + val padding = scrollView.context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) + val childCount = mediaContent.childCount + for (i in 0 until childCount) { + val mediaView = mediaContent.getChildAt(i) + val desiredPaddingEnd = if (i == childCount - 1) 0 else padding + val layoutParams = mediaView.layoutParams as ViewGroup.MarginLayoutParams + if (layoutParams.marginEnd != desiredPaddingEnd) { + layoutParams.marginEnd = desiredPaddingEnd + mediaView.layoutParams = layoutParams + } + } + } + + private fun updatePlayerVisibilities() { + val scrolledIn = scrollIntoCurrentMedia != 0 + for (i in 0 until mediaContent.childCount) { + val view = mediaContent.getChildAt(i) + val visible = (i == activeMediaIndex) || ((i == (activeMediaIndex + 1)) && scrolledIn) + view.visibility = if (visible) View.VISIBLE else View.INVISIBLE + } + } + + /** + * Notify that a player will be removed right away. This gives us the opporunity to look + * where it was and update our scroll position. + */ + fun onPrePlayerRemoved(removed: MediaControlPanel) { + val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= activeMediaIndex + if (beforeActive) { + // also update the index here since the scroll below might not always lead + // to a scrolling changed + activeMediaIndex = Math.max(0, activeMediaIndex - 1) + scrollView.scrollX = Math.max(scrollView.scrollX - + playerWidthPlusPadding, 0) + } + } + + /** + * Update the bounds of the carousel + */ + fun setCarouselBounds(currentCarouselWidth: Int, currentCarouselHeight: Int) { + if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) { + carouselWidth = currentCarouselWidth + carouselHeight = currentCarouselHeight + scrollView.invalidateOutline() + } + } + + companion object { + private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>( + "contentTranslation") { + override fun getValue(handler: MediaCarouselScrollHandler): Float { + return handler.contentTranslation + } + + override fun setValue(handler: MediaCarouselScrollHandler, value: Float) { + handler.contentTranslation = value + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index c80c82f61fc6..3fc162ead6d1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -20,13 +20,10 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.ColorStateList; -import android.graphics.Color; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; -import android.graphics.drawable.RippleDrawable; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; @@ -60,6 +57,7 @@ import javax.inject.Inject; */ public class MediaControlPanel { private static final String TAG = "MediaControlPanel"; + private static final float DISABLED_ALPHA = 0.38f; // Button IDs for QS controls static final int[] ACTION_IDS = { @@ -258,12 +256,6 @@ public class MediaControlPanel { ImageView iconView = mViewHolder.getSeamlessIcon(); TextView deviceName = mViewHolder.getSeamlessText(); - // Update the outline color - RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getForeground(); - GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0); - rect.setStroke(2, deviceName.getCurrentTextColor()); - rect.setColor(Color.TRANSPARENT); - final MediaDeviceData device = data.getDevice(); final int seamlessId = mViewHolder.getSeamless().getId(); final int seamlessFallbackId = mViewHolder.getSeamlessFallback().getId(); @@ -276,6 +268,11 @@ public class MediaControlPanel { mViewHolder.getSeamless().setVisibility(seamlessVisibility); expandedSet.setVisibility(seamlessId, seamlessVisibility); collapsedSet.setVisibility(seamlessId, seamlessVisibility); + final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f; + expandedSet.setAlpha(seamlessId, seamlessAlpha); + collapsedSet.setAlpha(seamlessId, seamlessAlpha); + // Disable clicking on output switcher for resumption controls. + mViewHolder.getSeamless().setEnabled(!data.getResumption()); if (showFallback) { iconView.setImageDrawable(null); deviceName.setText(null); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt index 0b0ffcede3af..8c9cb1b240bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt @@ -81,6 +81,11 @@ data class MediaData( */ var resumeAction: Runnable?, /** + * Indicates that this player is a resumption player (ie. It only shows a play actions which + * will start the app and start playing). + */ + var resumption: Boolean = false, + /** * Notification key for cancelling a media player after a timeout (when not using resumption.) */ val notificationKey: String? = null, diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index c59a548c8db4..416c81aee263 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -323,7 +323,7 @@ class MediaDataManager( onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName, null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), packageName, token, appIntent, device = null, active = false, - resumeAction = resumeAction, notificationKey = packageName, + resumeAction = resumeAction, resumption = true, notificationKey = packageName, hasCheckedForResume = true)) } } @@ -542,7 +542,7 @@ class MediaDataManager( val data = mediaEntries.remove(key)!! val resumeAction = getResumeMediaAction(data.resumeAction!!) val updated = data.copy(token = null, actions = listOf(resumeAction), - actionsToShowInCompact = listOf(0), active = false) + actionsToShowInCompact = listOf(0), active = false, resumption = true) mediaEntries.put(data.packageName, updated) // Notify listeners of "new" controls val listenersCopy = listeners.toSet() @@ -592,6 +592,16 @@ class MediaDataManager( } } + /** + * Invoked when the user has dismissed the media carousel + */ + fun onSwipeToDismiss() { + val mediaKeys = mediaEntries.keys.toSet() + mediaKeys.forEach { + setTimedOut(it, timedOut = true) + } + } + interface Listener { /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 26fa29613dc4..c41e6104833e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -49,7 +49,7 @@ class MediaHierarchyManager @Inject constructor( private val statusBarStateController: SysuiStatusBarStateController, private val keyguardStateController: KeyguardStateController, private val bypassController: KeyguardBypassController, - private val mediaViewManager: MediaViewManager, + private val mediaCarouselController: MediaCarouselController, private val notifLockscreenUserManager: NotificationLockscreenUserManager, wakefulnessLifecycle: WakefulnessLifecycle ) { @@ -65,7 +65,7 @@ class MediaHierarchyManager @Inject constructor( private var animationStartBounds: Rect = Rect() private var targetBounds: Rect = Rect() private val mediaFrame - get() = mediaViewManager.mediaFrame + get() = mediaCarouselController.mediaFrame private var statusbarState: Int = statusBarStateController.state private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply { interpolator = Interpolators.FAST_OUT_SLOW_IN @@ -140,6 +140,18 @@ class MediaHierarchyManager @Inject constructor( } /** + * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs, + * we wouldn't want to transition in that case. + */ + var collapsingShadeFromQS: Boolean = false + set(value) { + if (field != value) { + field = value + updateDesiredLocation(forceNoAnimation = true) + } + } + + /** * Are location changes currently blocked? */ private val blockLocationChanges: Boolean @@ -161,6 +173,19 @@ class MediaHierarchyManager @Inject constructor( } /** + * Are we currently fullyAwake + */ + private var fullyAwake: Boolean = false + set(value) { + if (field != value) { + field = value + if (value) { + updateDesiredLocation(forceNoAnimation = true) + } + } + } + + /** * Is the doze animation currently Running */ private var dozeAnimationRunning: Boolean = false @@ -206,10 +231,12 @@ class MediaHierarchyManager @Inject constructor( override fun onStartedGoingToSleep() { goingToSleep = true + fullyAwake = false } override fun onFinishedWakingUp() { goingToSleep = false + fullyAwake = true } override fun onStartedWakingUp() { @@ -227,6 +254,10 @@ class MediaHierarchyManager @Inject constructor( fun register(mediaObject: MediaHost): UniqueObjectHostView { val viewHost = createUniqueObjectHost() mediaObject.hostView = viewHost + mediaObject.addVisibilityChangeListener { + // Never animate because of a visibility change, only state changes should do that + updateDesiredLocation(forceNoAnimation = true) + } mediaHosts[mediaObject.location] = mediaObject if (mediaObject.location == desiredLocation) { // In case we are overriding a view that is already visible, make sure we attach it @@ -260,8 +291,10 @@ class MediaHierarchyManager @Inject constructor( /** * Updates the location that the view should be in. If it changes, an animation may be triggered * going from the old desired location to the new one. + * + * @param forceNoAnimation optional parameter telling the system not to animate */ - private fun updateDesiredLocation() { + private fun updateDesiredLocation(forceNoAnimation: Boolean = false) { val desiredLocation = calculateLocation() if (desiredLocation != this.desiredLocation) { if (this.desiredLocation >= 0) { @@ -270,11 +303,12 @@ class MediaHierarchyManager @Inject constructor( val isNewView = this.desiredLocation == -1 this.desiredLocation = desiredLocation // Let's perform a transition - val animate = shouldAnimateTransition(desiredLocation, previousLocation) + val animate = !forceNoAnimation && + shouldAnimateTransition(desiredLocation, previousLocation) val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation) val host = getHost(desiredLocation) - mediaViewManager.onDesiredLocationChanged(desiredLocation, host, animate, animDuration, - delay) + mediaCarouselController.onDesiredLocationChanged(desiredLocation, host, animate, + animDuration, delay) performTransitionToNewLocation(isNewView, animate) } } @@ -457,7 +491,7 @@ class MediaHierarchyManager @Inject constructor( val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1 val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f val endLocation = desiredLocation - mediaViewManager.setCurrentState(startLocation, endLocation, progress, immediately) + mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately) updateHostAttachment() if (currentAttachmentLocation == IN_OVERLAY) { mediaFrame.setLeftTopRightBottom( @@ -524,6 +558,18 @@ class MediaHierarchyManager @Inject constructor( !statusBarStateController.isDozing) { return LOCATION_QS } + if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS && + collapsingShadeFromQS) { + // When collapsing on the lockscreen, we want to remain in QS + return LOCATION_QS + } + if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN + && !fullyAwake) { + // When unlocking from dozing / while waking up, the media shouldn't be transitioning + // in an animated way. Let's keep it in the lockscreen until we're fully awake and + // reattach it without an animation + return LOCATION_LOCKSCREEN + } return location } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 7c5f0d1c2a16..1ae9d3ff4ca5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt @@ -2,6 +2,7 @@ package com.android.systemui.media import android.graphics.PointF import android.graphics.Rect +import android.util.ArraySet import android.view.View import android.view.View.OnAttachStateChangeListener import com.android.systemui.util.animation.MeasurementInput @@ -20,7 +21,7 @@ class MediaHost @Inject constructor( lateinit var hostView: UniqueObjectHostView var location: Int = -1 private set - var visibleChangedListener: ((Boolean) -> Unit)? = null + private var visibleChangedListeners: ArraySet<(Boolean) -> Unit> = ArraySet() private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0) @@ -58,6 +59,10 @@ class MediaHost @Inject constructor( } } + fun addVisibilityChangeListener(listener: (Boolean) -> Unit) { + visibleChangedListeners.add(listener) + } + /** * Initialize this MediaObject and create a host view. * All state should already be set on this host before calling this method in order to avoid @@ -113,8 +118,13 @@ class MediaHost @Inject constructor( } else { mediaDataManager.hasAnyMedia() } - hostView.visibility = if (visible) View.VISIBLE else View.GONE - visibleChangedListener?.invoke(visible) + val newVisibility = if (visible) View.VISIBLE else View.GONE + if (newVisibility != hostView.visibility) { + hostView.visibility = newVisibility + visibleChangedListeners.forEach { + it.invoke(visible) + } + } } class MediaHostStateHolder @Inject constructor() : MediaHostState { @@ -153,6 +163,15 @@ class MediaHost @Inject constructor( changedListener?.invoke() } + override var falsingProtectionNeeded: Boolean = false + set(value) { + if (field == value) { + return + } + field = value + changedListener?.invoke() + } + override fun getPivotX(): Float = gonePivot.x override fun getPivotY(): Float = gonePivot.y override fun setGonePivot(x: Float, y: Float) { @@ -178,6 +197,7 @@ class MediaHost @Inject constructor( mediaHostState.measurementInput = measurementInput?.copy() mediaHostState.visible = visible mediaHostState.gonePivot.set(gonePivot) + mediaHostState.falsingProtectionNeeded = falsingProtectionNeeded return mediaHostState } @@ -197,6 +217,9 @@ class MediaHost @Inject constructor( if (visible != other.visible) { return false } + if (falsingProtectionNeeded != other.falsingProtectionNeeded) { + return false + } if (!gonePivot.equals(other.getPivotX(), other.getPivotY())) { return false } @@ -206,6 +229,7 @@ class MediaHost @Inject constructor( override fun hashCode(): Int { var result = measurementInput?.hashCode() ?: 0 result = 31 * result + expansion.hashCode() + result = 31 * result + falsingProtectionNeeded.hashCode() result = 31 * result + showsOnlyActiveMedia.hashCode() result = 31 * result + if (visible) 1 else 2 result = 31 * result + gonePivot.hashCode() @@ -239,6 +263,11 @@ interface MediaHostState { var visible: Boolean /** + * Does this host need any falsing protection? + */ + var falsingProtectionNeeded: Boolean + + /** * Sets the pivot point when clipping the height or width. * Clipping happens when animating visibility when we're visible in QS but not on QQS, * for example. diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt new file mode 100644 index 000000000000..a079b06a0b10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt @@ -0,0 +1,100 @@ +package com.android.systemui.media + +import android.content.Context +import android.os.SystemClock +import android.util.AttributeSet +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewGroup +import android.widget.HorizontalScrollView +import com.android.systemui.Gefingerpoken +import com.android.systemui.util.animation.physicsAnimator + +/** + * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful + * when only measuring children but not the parent, when trying to apply a new scroll position + */ +class MediaScrollView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) + : HorizontalScrollView(context, attrs, defStyleAttr) { + + lateinit var contentContainer: ViewGroup + private set + var touchListener: Gefingerpoken? = null + + /** + * The target value of the translation X animation. Only valid if the physicsAnimator is running + */ + var animationTargetX = 0.0f + + /** + * Get the current content translation. This is usually the normal translationX of the content, + * but when animating, it might differ + */ + fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) { + animationTargetX + } else { + contentContainer.translationX + } + + /** + * Allow all scrolls to go through, use base implementation + */ + override fun scrollTo(x: Int, y: Int) { + if (mScrollX != x || mScrollY != y) { + val oldX: Int = mScrollX + val oldY: Int = mScrollY + mScrollX = x + mScrollY = y + invalidateParentCaches() + onScrollChanged(mScrollX, mScrollY, oldX, oldY) + if (!awakenScrollBars()) { + postInvalidateOnAnimation() + } + } + } + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + var intercept = false; + touchListener?.let { + intercept = it.onInterceptTouchEvent(ev) + } + return super.onInterceptTouchEvent(ev) || intercept; + } + + override fun onTouchEvent(ev: MotionEvent?): Boolean { + var touch = false; + touchListener?.let { + touch = it.onTouchEvent(ev) + } + return super.onTouchEvent(ev) || touch + } + + override fun onFinishInflate() { + super.onFinishInflate() + contentContainer = getChildAt(0) as ViewGroup + } + + override fun overScrollBy(deltaX: Int, deltaY: Int, scrollX: Int, scrollY: Int, + scrollRangeX: Int, scrollRangeY: Int, maxOverScrollX: Int, + maxOverScrollY: Int, isTouchEvent: Boolean): Boolean { + if (getContentTranslation() != 0.0f) { + // When we're dismissing we ignore all the scrolling + return false + } + return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, + scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent) + } + + /** + * Cancel the current touch event going on. + */ + fun cancelCurrentScroll() { + val now = SystemClock.uptimeMillis() + val event = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0) + event.source = InputDevice.SOURCE_TOUCHSCREEN + super.onTouchEvent(event) + event.recycle() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 90ccfc6ca725..fc22c026974a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -17,9 +17,11 @@ package com.android.systemui.media import android.content.Context +import android.content.res.Configuration import android.graphics.PointF import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.R +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.MeasurementOutput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionLayoutController @@ -32,9 +34,14 @@ import javax.inject.Inject */ class MediaViewController @Inject constructor( context: Context, + private val configurationController: ConfigurationController, private val mediaHostStatesManager: MediaHostStatesManager ) { + /** + * A listener when the current dimensions of the player change + */ + lateinit var sizeChangedListener: () -> Unit private var firstRefresh: Boolean = true private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() @@ -52,12 +59,14 @@ class MediaViewController @Inject constructor( * The ending location of the view where it ends when all animations and transitions have * finished */ + @MediaLocation private var currentEndLocation: Int = -1 /** * The ending location of the view where it ends when all animations and transitions have * finished */ + @MediaLocation private var currentStartLocation: Int = -1 /** @@ -76,10 +85,42 @@ class MediaViewController @Inject constructor( private val tmpPoint = PointF() /** + * The current width of the player. This might not factor in case the player is animating + * to the current state, but represents the end state + */ + var currentWidth: Int = 0 + /** + * The current height of the player. This might not factor in case the player is animating + * to the current state, but represents the end state + */ + var currentHeight: Int = 0 + + /** + * A callback for RTL config changes + */ + private val configurationListener = object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + // Because the TransitionLayout is not always attached (and calculates/caches layout + // results regardless of attach state), we have to force the layoutDirection of the view + // to the correct value for the user's current locale to ensure correct recalculation + // when/after calling refreshState() + newConfig?.apply { + if (transitionLayout?.rawLayoutDirection != layoutDirection) { + transitionLayout?.layoutDirection = layoutDirection + refreshState() + } + } + } + } + + /** * A callback for media state changes */ val stateCallback = object : MediaHostStatesManager.Callback { - override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) { + override fun onHostStateChanged( + @MediaLocation location: Int, + mediaHostState: MediaHostState + ) { if (location == currentEndLocation || location == currentStartLocation) { setCurrentState(currentStartLocation, currentEndLocation, @@ -105,6 +146,12 @@ class MediaViewController @Inject constructor( collapsedLayout.load(context, R.xml.media_collapsed) expandedLayout.load(context, R.xml.media_expanded) mediaHostStatesManager.addController(this) + layoutController.sizeChangedListener = { width: Int, height: Int -> + currentWidth = width + currentHeight = height + sizeChangedListener.invoke() + } + configurationController.addCallback(configurationListener) } /** @@ -112,6 +159,7 @@ class MediaViewController @Inject constructor( */ fun onDestroy() { mediaHostStatesManager.removeController(this) + configurationController.removeCallback(configurationListener) } private fun ensureAllMeasurements() { @@ -279,6 +327,8 @@ class MediaViewController @Inject constructor( tmpPoint, tmpState) tmpState } + currentWidth = result.width + currentHeight = result.height layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration, animationDelay) } @@ -324,7 +374,7 @@ class MediaViewController @Inject constructor( // Let's clear all of our measurements and recreate them! viewStates.clear() setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress, - applyImmediately = false) + applyImmediately = true) } firstRefresh = false } diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt index 9ede083fa9ac..600fdc27ef89 100644 --- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt @@ -48,6 +48,7 @@ class PlayerViewHolder private constructor(itemView: View) { // Seek bar val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar) + val progressTimes = itemView.requireViewById<ViewGroup>(R.id.notification_media_progress_time) val elapsedTimeView = itemView.requireViewById<TextView>(R.id.media_elapsed_time) val totalTimeView = itemView.requireViewById<TextView>(R.id.media_total_time) @@ -93,8 +94,16 @@ class PlayerViewHolder private constructor(itemView: View) { * @param parent Parent of inflated view. */ @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder { - val v = inflater.inflate(R.layout.media_view, parent, false) - return PlayerViewHolder(v) + val mediaView = inflater.inflate(R.layout.media_view, parent, false) + // Because this media view (a TransitionLayout) is used to measure and layout the views + // in various states before being attached to its parent, we can't depend on the default + // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. + mediaView.layoutDirection = View.LAYOUT_DIRECTION_LOCALE + return PlayerViewHolder(mediaView).apply { + // Media playback is in the direction of tape, not time, so it stays LTR + seekBar.layoutDirection = View.LAYOUT_DIRECTION_LTR + progressTimes.layoutDirection = View.LAYOUT_DIRECTION_LTR + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index cd8ed265bd53..c2631c923e45 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -19,6 +19,7 @@ package com.android.systemui.media import android.text.format.DateUtils import androidx.annotation.UiThread import androidx.lifecycle.Observer +import com.android.systemui.R /** * Observer for changes from SeekBarViewModel. @@ -27,10 +28,18 @@ import androidx.lifecycle.Observer */ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> { + val seekBarDefaultMaxHeight = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height) + val seekBarDisabledHeight = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height) + /** Updates seek bar views when the data model changes. */ @UiThread override fun onChanged(data: SeekBarViewModel.Progress) { if (!data.enabled) { + if (holder.seekBar.maxHeight != seekBarDisabledHeight) { + holder.seekBar.maxHeight = seekBarDisabledHeight + } holder.seekBar.setEnabled(false) holder.seekBar.getThumb().setAlpha(0) holder.seekBar.setProgress(0) @@ -42,6 +51,10 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) holder.seekBar.setEnabled(data.seekAvailable) + if (holder.seekBar.maxHeight != seekBarDefaultMaxHeight) { + holder.seekBar.maxHeight = seekBarDefaultMaxHeight + } + data.elapsedTime?.let { holder.seekBar.setProgress(it) holder.elapsedTimeView.setText(DateUtils.formatElapsedTime( diff --git a/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt deleted file mode 100644 index 8efc9549068a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/media/UnboundHorizontalScrollView.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.android.systemui.media - -import android.content.Context -import android.util.AttributeSet -import android.widget.HorizontalScrollView - -/** - * A Horizontal scrollview that doesn't limit itself to the childs bounds. This is useful - * when only measuring children but not the parent, when trying to apply a new scroll position - */ -class UnboundHorizontalScrollView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) - : HorizontalScrollView(context, attrs, defStyleAttr) { - - /** - * Allow all scrolls to go through, use base implementation - */ - override fun scrollTo(x: Int, y: Int) { - if (mScrollX != x || mScrollY != y) { - val oldX: Int = mScrollX - val oldY: Int = mScrollY - mScrollX = x - mScrollY = y - invalidateParentCaches() - onScrollChanged(mScrollX, mScrollY, oldX, oldY) - if (!awakenScrollBars()) { - postInvalidateOnAnimation() - } - } - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java index aa17c4aa79b1..309b32fc85d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/NonInterceptingScrollView.java @@ -46,6 +46,11 @@ public class NonInterceptingScrollView extends ScrollView { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } + } else if (!canScrollVertically(-1)) { + // Don't pass on the touch to the view, because scrolling will unconditionally + // disallow interception even if we can't scroll. + // if a user can't scroll at all, we should never listen to the touch. + return false; } break; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index bc8f5a8fb652..e66b33c660d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -18,7 +18,6 @@ import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnLayoutChangeListener; -import android.widget.ScrollView; import com.android.systemui.Dependency; import com.android.systemui.plugins.qs.QS; @@ -300,10 +299,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if (mQsPanel.getSecurityFooter() != null) { builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1); } + if (mQsPanel.getDivider() != null) { + builder.addFloat(mQsPanel.getDivider(), "alpha", 0, 1); + } mFirstPageDelayedAnimator = builder.build(); if (mQsPanel.getSecurityFooter() != null) { mAllViews.add(mQsPanel.getSecurityFooter().getView()); } + if (mQsPanel.getDivider() != null) { + mAllViews.add(mQsPanel.getDivider()); + } float px = 0; float py = 1; if (tiles.size() <= 3) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 0332bc3e0618..6b12e478f627 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -26,8 +26,12 @@ import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.util.animation.PhysicsAnimator; /** * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} @@ -35,7 +39,22 @@ import com.android.systemui.qs.customize.QSCustomizer; public class QSContainerImpl extends FrameLayout { private final Point mSizePoint = new Point(); + private static final FloatPropertyCompat<QSContainerImpl> BACKGROUND_BOTTOM = + new FloatPropertyCompat<QSContainerImpl>("backgroundBottom") { + @Override + public float getValue(QSContainerImpl qsImpl) { + return qsImpl.getBackgroundBottom(); + } + @Override + public void setValue(QSContainerImpl background, float value) { + background.setBackgroundBottom((int) value); + } + }; + private static final PhysicsAnimator.SpringConfig BACKGROUND_SPRING + = new PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM, + SpringForce.DAMPING_RATIO_LOW_BOUNCY); + private int mBackgroundBottom = -1; private int mHeightOverride = -1; private QSPanel mQSPanel; private View mQSDetail; @@ -53,6 +72,7 @@ public class QSContainerImpl extends FrameLayout { private boolean mQsDisabled; private int mContentPaddingStart = -1; private int mContentPaddingEnd = -1; + private boolean mAnimateBottomOnNextLayout; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -71,10 +91,30 @@ public class QSContainerImpl extends FrameLayout { mStatusBarBackground = findViewById(R.id.quick_settings_status_bar_background); mBackgroundGradient = findViewById(R.id.quick_settings_gradient_view); updateResources(); + mHeader.getHeaderQsPanel().setMediaVisibilityChangedListener((visible) -> { + if (mHeader.getHeaderQsPanel().isShown()) { + mAnimateBottomOnNextLayout = true; + } + }); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } + private void setBackgroundBottom(int value) { + // We're saving the bottom separately since otherwise the bottom would be overridden in + // the layout and the animation wouldn't properly start at the old position. + mBackgroundBottom = value; + mBackground.setBottom(value); + } + + private float getBackgroundBottom() { + if (mBackgroundBottom == -1) { + return mBackground.getBottom(); + } + return mBackgroundBottom; + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -140,7 +180,8 @@ public class QSContainerImpl extends FrameLayout { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - updateExpansion(); + updateExpansion(mAnimateBottomOnNextLayout /* animate */); + mAnimateBottomOnNextLayout = false; } public void disable(int state1, int state2, boolean animate) { @@ -181,13 +222,31 @@ public class QSContainerImpl extends FrameLayout { } public void updateExpansion() { + updateExpansion(false /* animate */); + } + + public void updateExpansion(boolean animate) { int height = calculateContainerHeight(); setBottom(getTop() + height); mQSDetail.setBottom(getTop() + height); // Pin the drag handle to the bottom of the panel. mDragHandle.setTranslationY(height - mDragHandle.getHeight()); mBackground.setTop(mQSPanelContainer.getTop()); - mBackground.setBottom(height); + updateBackgroundBottom(height, animate); + } + + private void updateBackgroundBottom(int height, boolean animated) { + PhysicsAnimator<QSContainerImpl> physicsAnimator = PhysicsAnimator.getInstance(this); + if (physicsAnimator.isPropertyAnimating(BACKGROUND_BOTTOM) || animated) { + // An animation is running or we want to animate + // Let's make sure to set the currentValue again, since the call below might only + // start in the next frame and otherwise we'd flicker + BACKGROUND_BOTTOM.setValue(this, BACKGROUND_BOTTOM.getValue(this)); + physicsAnimator.spring(BACKGROUND_BOTTOM, height, BACKGROUND_SPRING).start(); + } else { + BACKGROUND_BOTTOM.setValue(this, height); + } + } protected int calculateContainerHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 5021e0019d57..c8a34f010ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -65,6 +65,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; @@ -117,6 +118,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne @Nullable protected View mFooter; + @Nullable + protected View mDivider; @Nullable private ViewGroup mHeaderContainer; @@ -141,6 +144,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private int mLastOrientation = -1; private int mMediaTotalBottomMargin; private int mFooterMarginStartHorizontal; + private Consumer<Boolean> mMediaVisibilityChangedListener; @Inject @@ -158,8 +162,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mMediaTotalBottomMargin = getResources().getDimensionPixelSize( R.dimen.quick_settings_bottom_margin_media); mMediaHost = mediaHost; - mMediaHost.setVisibleChangedListener((visible) -> { - switchTileLayout(); + mMediaHost.addVisibilityChangeListener((visible) -> { + onMediaVisibilityChanged(visible); return null; }); mContext = context; @@ -207,6 +211,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateResources(); } + protected void onMediaVisibilityChanged(Boolean visible) { + switchTileLayout(); + if (mMediaVisibilityChangedListener != null) { + mMediaVisibilityChangedListener.accept(visible); + } + } + protected void addSecurityFooter() { mSecurityFooter = new QSSecurityFooter(this, mContext); } @@ -479,6 +490,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne protected void onFinishInflate() { super.onFinishInflate(); mFooter = findViewById(R.id.qs_footer); + mDivider = findViewById(R.id.divider); switchTileLayout(true /* force */); } @@ -489,6 +501,13 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne private boolean switchTileLayout(boolean force) { /** Whether or not the QuickQSPanel currently contains a media player. */ boolean horizontal = shouldUseHorizontalLayout(); + if (mDivider != null) { + if (!horizontal && mUsingMediaPlayer && mMediaHost.getVisible()) { + mDivider.setVisibility(View.VISIBLE); + } else { + mDivider.setVisibility(View.GONE); + } + } if (horizontal != mUsingHorizontalLayout || force) { mUsingHorizontalLayout = horizontal; View visibleView = horizontal ? mHorizontalLinearLayout : (View) mRegularTileLayout; @@ -522,6 +541,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } updateTileLayoutMargins(); updateFooterMargin(); + updateDividerMargin(); updateMediaHostContentMargins(); updateHorizontalLinearLayoutMargins(); updatePadding(); @@ -971,6 +991,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne return mSecurityFooter; } + @Nullable + public View getDivider() { + return mDivider; + } + public void showDeviceMonitoringDialog() { if (mSecurityFooter != null) { mSecurityFooter.showDeviceMonitoringDialog(); @@ -986,6 +1011,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mContentMarginEnd - mVisualTilePadding); updateMediaHostContentMargins(); updateFooterMargin(); + updateDividerMargin(); } private void updateFooterMargin() { @@ -1027,6 +1053,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne updateMargins((View) mTileLayout, mVisualMarginStart, marginEnd); } + private void updateDividerMargin() { + if (mDivider == null) return; + updateMargins(mDivider, mContentMarginStart, mContentMarginEnd); + } + /** * Update the margins of the media hosts */ @@ -1065,6 +1096,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne mHeaderContainer = headerContainer; } + public void setMediaVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) { + mMediaVisibilityChangedListener = visibilityChangedListener; + } + private class H extends Handler { private static final int SHOW_DETAIL = 1; private static final int SET_TILE_VISIBILITY = 2; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index d057a8a43c43..8347def2d430 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -97,6 +97,9 @@ public class ScreenRecordDialog extends Activity { mModes); a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mOptions.setAdapter(a); + mOptions.setOnItemClickListenerInt((parent, view, position, id) -> { + mAudioSwitch.setChecked(true); + }); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 33f0f4d8618b..9bbc4ddcc62c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -1105,14 +1105,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return; } - Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) { ScreenshotNotificationsController.cancelScreenshotNotification(context); } ActivityOptions opts = ActivityOptions.makeBasic(); opts.setDisallowEnterPictureInPictureWhileLaunching( intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); - context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT); + try { + actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled", e); + } + }; if (mStatusBar != null) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 10e6902f139e..e3fbdbc7c30d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -281,11 +281,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender()) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, requestCode, + sharingChooserIntent, 0, null, UserHandle.CURRENT); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) - .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, sharingChooserIntent) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, @@ -320,14 +322,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0, + editIntent, 0, null, UserHandle.CURRENT); + // Make sure pending intents for the system user are still unique across users // by setting the (otherwise unused) request code to the current user id. int requestCode = mContext.getUserId(); // Create a edit action - PendingIntent editAction = PendingIntent.getBroadcast(context, requestCode, + PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) - .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, editIntent) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) @@ -335,7 +340,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mSmartActionsEnabled) .setAction(Intent.ACTION_EDIT) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM); Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java index 821144a7f12d..71e788375d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java @@ -443,14 +443,10 @@ public class BrightnessController implements ToggleSlider.Listener { max = mMaximumBacklight; } // convertGammaToLinearFloat returns 0-1 - if (BrightnessSynchronizer.brightnessFloatToInt(mContext, brightnessValue) - == BrightnessSynchronizer.brightnessFloatToInt(mContext, + if (BrightnessSynchronizer.floatEquals(brightnessValue, convertGammaToLinearFloat(mControl.getValue(), min, max))) { - // If we have more resolution on the slider than we do in the actual setting, then - // multiple slider positions will map to the same setting value. Thus, if we see a - // setting value here that maps to the current slider position, we don't bother to - // calculate the new slider position since it may differ and look like a brightness - // change to the user even though it isn't one. + // If the value in the slider is equal to the value on the current brightness + // then the slider does not need to animate, since the brightness will not change. return; } // Returns GAMMA_SPACE_MIN - GAMMA_SPACE_MAX diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 03a0d930a3d9..ad312202e224 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -387,6 +387,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } // Always set this because we could be entering split when mMinimized is already true wct.setFocusable(mSplits.mPrimary.token, !mMinimized); + boolean onlyFocusable = true; // Update home-stack resizability final boolean homeResizableChanged = mHomeStackResizable != homeStackResizable; @@ -395,6 +396,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, if (isDividerVisible()) { WindowManagerProxy.applyHomeTasksMinimized( mSplitLayout, mSplits.mSecondary.token, wct); + onlyFocusable = false; } } @@ -416,7 +418,15 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks, } } updateTouchable(); - mWindowManagerProxy.applySyncTransaction(wct); + if (onlyFocusable) { + // If we are only setting focusability, a sync transaction isn't necessary (in fact it + // can interrupt other animations), so see if it can be submitted on pending instead. + if (!mSplits.mDivider.getWmProxy().queueSyncTransactionIfWaiting(wct)) { + WindowOrganizer.applyTransaction(wct); + } + } else { + mWindowManagerProxy.applySyncTransaction(wct); + } } void setAdjustedForIme(boolean adjustedForIme) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java index d782a3cadc19..47c8c0ad8a4e 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java @@ -29,6 +29,7 @@ import android.view.SurfaceControl; import android.window.TaskOrganizer; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import android.window.WindowOrganizer; import androidx.annotation.Nullable; @@ -173,46 +174,50 @@ class DividerImeController implements DisplayImeController.ImePositionProcessor } private void updateImeAdjustState() { - // Reposition the server's secondary split position so that it evaluates - // insets properly. - WindowContainerTransaction wct = new WindowContainerTransaction(); - final SplitDisplayLayout splitLayout = getLayout(); - if (mTargetAdjusted) { - splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); - wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary); - // "Freeze" the configuration size so that the app doesn't get a config - // or relaunch. This is required because normally nav-bar contributes - // to configuration bounds (via nondecorframe). - Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top - - splitLayout.mSecondary.top); - wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mSecondary.token, - mSplits.mSecondary.configuration.screenWidthDp, - mSplits.mSecondary.configuration.screenHeightDp); - - wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary); - adjustAppBounds = new Rect(mSplits.mPrimary.configuration - .windowConfiguration.getAppBounds()); - adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top - - splitLayout.mPrimary.top); - wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds); - wct.setScreenSizeDp(mSplits.mPrimary.token, - mSplits.mPrimary.configuration.screenWidthDp, - mSplits.mPrimary.configuration.screenHeightDp); - } else { - wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary); - wct.setAppBounds(mSplits.mSecondary.token, null); - wct.setScreenSizeDp(mSplits.mSecondary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary); - wct.setAppBounds(mSplits.mPrimary.token, null); - wct.setScreenSizeDp(mSplits.mPrimary.token, - SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); - } + if (mAdjusted != mTargetAdjusted) { + // Reposition the server's secondary split position so that it evaluates + // insets properly. + WindowContainerTransaction wct = new WindowContainerTransaction(); + final SplitDisplayLayout splitLayout = getLayout(); + if (mTargetAdjusted) { + splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop); + wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary); + // "Freeze" the configuration size so that the app doesn't get a config + // or relaunch. This is required because normally nav-bar contributes + // to configuration bounds (via nondecorframe). + Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration + .windowConfiguration.getAppBounds()); + adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top + - splitLayout.mSecondary.top); + wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds); + wct.setScreenSizeDp(mSplits.mSecondary.token, + mSplits.mSecondary.configuration.screenWidthDp, + mSplits.mSecondary.configuration.screenHeightDp); + + wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary); + adjustAppBounds = new Rect(mSplits.mPrimary.configuration + .windowConfiguration.getAppBounds()); + adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top + - splitLayout.mPrimary.top); + wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds); + wct.setScreenSizeDp(mSplits.mPrimary.token, + mSplits.mPrimary.configuration.screenWidthDp, + mSplits.mPrimary.configuration.screenHeightDp); + } else { + wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary); + wct.setAppBounds(mSplits.mSecondary.token, null); + wct.setScreenSizeDp(mSplits.mSecondary.token, + SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); + wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary); + wct.setAppBounds(mSplits.mPrimary.token, null); + wct.setScreenSizeDp(mSplits.mPrimary.token, + SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED); + } - mSplits.mDivider.getWmProxy().applySyncTransaction(wct); + if (!mSplits.mDivider.getWmProxy().queueSyncTransactionIfWaiting(wct)) { + WindowOrganizer.applyTransaction(wct); + } + } // Update all the adjusted-for-ime states if (!mPaused) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 42d8c959371f..6f554e698c58 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -352,8 +352,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, minimizeLeft + mMinimizedShadow.getMeasuredWidth(), minimizeTop + mMinimizedShadow.getMeasuredHeight()); if (changed) { - mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(), - mHandle.getRight(), mHandle.getBottom())); notifySplitScreenBoundsChanged(); } } @@ -679,6 +677,14 @@ public class DividerView extends FrameLayout implements OnTouchListener, private void notifySplitScreenBoundsChanged() { mOtherTaskRect.set(mSplitLayout.mSecondary); + mTmpRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom()); + if (isHorizontalDivision()) { + mTmpRect.offsetTo(0, mDividerPositionY); + } else { + mTmpRect.offsetTo(mDividerPositionX, 0); + } + mWindowManagerProxy.setTouchRegion(mTmpRect); + mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets()); switch (mSplitLayout.getPrimarySplitSide()) { case WindowManager.DOCKED_LEFT: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 8c7e071e0d3e..a1444532bd5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -25,6 +25,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.UserInfo; import android.content.res.ColorStateList; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; @@ -34,12 +35,16 @@ import android.os.BatteryManager; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; import android.text.TextUtils; import android.text.format.Formatter; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.widget.ViewClippingUtil; @@ -96,6 +101,7 @@ public class KeyguardIndicationController implements StateListener, private final SettableWakeLock mWakeLock; private final DockManager mDockManager; private final DevicePolicyManager mDevicePolicyManager; + private final UserManager mUserManager; private BroadcastReceiver mBroadcastReceiver; private LockscreenLockIconController mLockIconController; @@ -142,7 +148,8 @@ public class KeyguardIndicationController implements StateListener, DockManager dockManager, BroadcastDispatcher broadcastDispatcher, DevicePolicyManager devicePolicyManager, - IBatteryStats iBatteryStats) { + IBatteryStats iBatteryStats, + UserManager userManager) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mDevicePolicyManager = devicePolicyManager; @@ -155,6 +162,7 @@ public class KeyguardIndicationController implements StateListener, mWakeLock = new SettableWakeLock( wakeLockBuilder.setTag("Doze:KeyguardIndication").build(), TAG); mBatteryInfo = iBatteryStats; + mUserManager = userManager; mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback()); mKeyguardUpdateMonitor.registerCallback(mTickReceiver); @@ -180,8 +188,10 @@ public class KeyguardIndicationController implements StateListener, updateDisclosure(); } }; - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, new IntentFilter( - DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter); } } @@ -223,9 +233,8 @@ public class KeyguardIndicationController implements StateListener, private void updateDisclosure() { // NOTE: Because this uses IPC, avoid calling updateDisclosure() on a critical path. - if (whitelistIpcs(mDevicePolicyManager::isDeviceManaged)) { - final CharSequence organizationName = - mDevicePolicyManager.getDeviceOwnerOrganizationName(); + if (whitelistIpcs(this::isOrganizationOwnedDevice)) { + CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); if (organizationName != null) { mDisclosure.switchIndication(mContext.getResources().getString( R.string.do_disclosure_with_name, organizationName)); @@ -238,6 +247,38 @@ public class KeyguardIndicationController implements StateListener, } } + private boolean isOrganizationOwnedDevice() { + return mDevicePolicyManager.isDeviceManaged() + || mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(); + } + + @Nullable + private CharSequence getOrganizationOwnedDeviceOrganizationName() { + if (mDevicePolicyManager.isDeviceManaged()) { + return mDevicePolicyManager.getDeviceOwnerOrganizationName(); + } else if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { + return getWorkProfileOrganizationName(); + } + return null; + } + + private CharSequence getWorkProfileOrganizationName() { + final int profileId = getWorkProfileUserId(UserHandle.myUserId()); + if (profileId == UserHandle.USER_NULL) { + return null; + } + return mDevicePolicyManager.getOrganizationNameForUser(profileId); + } + + private int getWorkProfileUserId(int userId) { + for (final UserInfo userInfo : mUserManager.getProfiles(userId)) { + if (userInfo.isManagedProfile()) { + return userInfo.id; + } + } + return UserHandle.USER_NULL; + } + public void setVisible(boolean visible) { mVisible = visible; mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 33771449abc9..423f85f2ddd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -178,6 +178,7 @@ public final class NotificationEntry extends ListEntry { private int mBucket = BUCKET_ALERTING; @Nullable private Long mPendingAnimationDuration; private boolean mIsMarkedForUserTriggeredMovement; + private boolean mShelfIconVisible; /** * @param sbn the StatusBarNotification from system server @@ -431,6 +432,7 @@ public final class NotificationEntry extends ListEntry { //TODO: This will go away when we have a way to bind an entry to a row public void setRow(ExpandableNotificationRow row) { this.row = row; + updateShelfIconVisibility(); } public ExpandableNotificationRowController getRowController() { @@ -951,6 +953,18 @@ public final class NotificationEntry extends ListEntry { return mIsMarkedForUserTriggeredMovement; } + /** Whether or not the icon for this notification is visible in the shelf. */ + public void setShelfIconVisible(boolean shelfIconVisible) { + mShelfIconVisible = shelfIconVisible; + updateShelfIconVisibility(); + } + + private void updateShelfIconVisibility() { + if (row != null) { + row.setShelfIconVisible(mShelfIconVisible); + } + } + /** * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a * conversation). This can then be used for custom animations. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 5c802bd2e5b7..df1de63b65a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.view.accessibility.AccessibilityManager; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Background; @@ -116,7 +117,8 @@ public interface NotificationsModule { ChannelEditorDialogController channelEditorDialogController, CurrentUserContextTracker contextTracker, Provider<PriorityOnboardingDialogController.Builder> builderProvider, - BubbleController bubbleController) { + BubbleController bubbleController, + UiEventLogger uiEventLogger) { return new NotificationGutsManager( context, visualStabilityManager, @@ -131,7 +133,8 @@ public interface NotificationsModule { channelEditorDialogController, contextTracker, builderProvider, - bubbleController); + bubbleController, + uiEventLogger); } /** Provides an instance of {@link VisualStabilityManager} */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 011ad19b41db..13f7a53f5e54 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -96,13 +96,11 @@ class IconManager @Inject constructor( val shelfIcon = iconBuilder.createIconView(entry) shelfIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE - shelfIcon.visibility = View.INVISIBLE // TODO: This doesn't belong here shelfIcon.setOnVisibilityChangedListener { newVisibility: Int -> - if (entry.row != null) { - entry.row.setShelfIconVisible(newVisibility == View.VISIBLE) - } + entry.setShelfIconVisible(newVisibility == View.VISIBLE) } + shelfIcon.visibility = View.INVISIBLE // Construct the aod icon view. val aodIcon = iconBuilder.createIconView(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java index 9a25c480dfe8..c147023edf8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java @@ -16,6 +16,13 @@ package com.android.systemui.statusbar.notification.logging; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_FOREGROUND_SERVICE; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_HEADS_UP; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; + import android.annotation.Nullable; import android.service.notification.StatusBarNotification; @@ -23,6 +30,7 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.logging.nano.Notifications; +import com.android.systemui.statusbar.notification.stack.PriorityBucket; import java.util.List; /** @@ -84,7 +92,7 @@ public interface NotificationPanelLogger { if (n.getNotification() != null) { proto.isGroupSummary = n.getNotification().isGroupSummary(); } - proto.section = 1 + ne.getBucket(); // We want 0 to mean not set / unknown + proto.section = toNotificationSection(ne.getBucket()); proto_array[i] = proto; } ++i; @@ -92,4 +100,25 @@ public interface NotificationPanelLogger { notificationList.notifications = proto_array; return notificationList; } + + /** + * Maps PriorityBucket enum to Notification.SECTION constant. The two lists should generally + * use matching names, but the values may differ, because PriorityBucket order changes from + * time to time, while logs need to have stable meanings. + * @param bucket PriorityBucket constant + * @return Notification.SECTION constant + */ + static int toNotificationSection(@PriorityBucket int bucket) { + switch(bucket) { + case BUCKET_MEDIA_CONTROLS : return Notifications.Notification.SECTION_MEDIA_CONTROLS; + case BUCKET_HEADS_UP: return Notifications.Notification.SECTION_HEADS_UP; + case BUCKET_FOREGROUND_SERVICE: + return Notifications.Notification.SECTION_FOREGROUND_SERVICE; + case BUCKET_PEOPLE: return Notifications.Notification.SECTION_PEOPLE; + case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING; + case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT; + } + return Notifications.Notification.SECTION_UNKNOWN; + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto index 552a5fb40a1c..c2ab2758dd74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/Notifications.proto @@ -33,13 +33,16 @@ message Notification { optional bool is_group_summary = 5; // The section of the shade that the notification is in. - // See NotificationSectionsManager.PriorityBucket. + // Sections follow NotificationSectionsManager.PriorityBucket but enum constants do not, + // as PriorityBucket order changes from time to time, while logs need to have stable meanings. enum NotificationSection { SECTION_UNKNOWN = 0; SECTION_HEADS_UP = 1; - SECTION_PEOPLE = 2; - SECTION_ALERTING = 3; - SECTION_SILENT = 4; + SECTION_MEDIA_CONTROLS = 2; + SECTION_PEOPLE = 3; + SECTION_ALERTING = 4; + SECTION_SILENT = 5; + SECTION_FOREGROUND_SERVICE = 6; } optional NotificationSection section = 6; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java index e445c9d73bbb..28c53dc6d9b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java @@ -31,6 +31,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; @@ -50,6 +51,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon private MetricsLogger mMetricsLogger; private OnSettingsClickListener mOnSettingsClickListener; private NotificationGuts mGutsContainer; + private UiEventLogger mUiEventLogger; private OnClickListener mOnOk = v -> { mGutsContainer.closeControls(v, false); @@ -66,6 +68,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon public void bindGuts(final PackageManager pm, final OnSettingsClickListener onSettingsClick, final StatusBarNotification sbn, + final UiEventLogger uiEventLogger, ArraySet<Integer> activeOps) { mPkg = sbn.getPackageName(); mSbn = sbn; @@ -73,11 +76,13 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon mAppName = mPkg; mOnSettingsClickListener = onSettingsClick; mAppOps = activeOps; + mUiEventLogger = uiEventLogger; bindHeader(); bindPrompt(); bindButtons(); + logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN); mMetricsLogger = new MetricsLogger(); mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, true); } @@ -188,6 +193,7 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon @Override public boolean handleCloseControls(boolean save, boolean force) { + logUiEvent(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_CLOSE); if (mMetricsLogger != null) { mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); } @@ -198,4 +204,11 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon public int getActualHeight() { return getHeight(); } + + private void logUiEvent(NotificationAppOpsEvent event) { + if (mSbn != null) { + mUiEventLogger.logWithInstanceId(event, + mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId()); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 7ed8350249ec..ccfd8a329ffd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -769,10 +769,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { return mContentTranslation; } - public boolean wantsAddAndRemoveAnimations() { - return true; - } - /** Sets whether this view is the first notification in a section. */ public void setFirstInSection(boolean firstInSection) { mFirstInSection = firstInSection; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java new file mode 100644 index 000000000000..c856245ebe83 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationAppOpsEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +enum NotificationAppOpsEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "User opened app ops controls on a notification (for active " + + "privacy-sensitive permissions usage)") + NOTIFICATION_APP_OPS_OPEN(597), + + @UiEvent(doc = "User closed app ops controls") + NOTIFICATION_APP_OPS_CLOSE(598), + + @UiEvent(doc = "User clicked through to settings in app ops controls") + NOTIFICATION_APP_OPS_SETTINGS_CLICK(599); + + private final int mId; + NotificationAppOpsEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java new file mode 100644 index 000000000000..6833326d47e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationControlsEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +enum NotificationControlsEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The user opened the notification inline controls.") + NOTIFICATION_CONTROLS_OPEN(594), + + @UiEvent(doc = "In notification inline controls, the user saved a notification channel " + + "importance change.") + NOTIFICATION_CONTROLS_SAVE_IMPORTANCE(595), + + @UiEvent(doc = "The user closed the notification inline controls.") + NOTIFICATION_CONTROLS_CLOSE(596); + + private final int mId; + NotificationControlsEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 8337cbe449d5..24883f51a984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -41,6 +41,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.Dependency; @@ -121,6 +122,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final ShortcutManager mShortcutManager; private final CurrentUserContextTracker mContextTracker; private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider; + private final UiEventLogger mUiEventLogger; /** * Injected constructor. See {@link NotificationsModule}. @@ -135,7 +137,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx ChannelEditorDialogController channelEditorDialogController, CurrentUserContextTracker contextTracker, Provider<PriorityOnboardingDialogController.Builder> builderProvider, - BubbleController bubbleController) { + BubbleController bubbleController, + UiEventLogger uiEventLogger) { mContext = context; mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; @@ -150,6 +153,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mBuilderProvider = builderProvider; mChannelEditorDialogController = channelEditorDialogController; mBubbleController = bubbleController; + mUiEventLogger = uiEventLogger; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -315,12 +319,16 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx AppOpsInfo.OnSettingsClickListener onSettingsClick = (View v, String pkg, int uid, ArraySet<Integer> ops) -> { - mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS); - guts.resetFalsingCheck(); - startAppOpsSettingsActivity(pkg, uid, ops, row); + mUiEventLogger.logWithInstanceId( + NotificationAppOpsEvent.NOTIFICATION_APP_OPS_SETTINGS_CLICK, + sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); + mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_OPS_GUTS_SETTINGS); + guts.resetFalsingCheck(); + startAppOpsSettingsActivity(pkg, uid, ops, row); }; if (!row.getEntry().mActiveAppOps.isEmpty()) { - appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, row.getEntry().mActiveAppOps); + appOpsInfoView.bindGuts(pmUser, onSettingsClick, sbn, mUiEventLogger, + row.getEntry().mActiveAppOps); } } @@ -370,6 +378,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getEntry(), onSettingsClick, onAppSettingsClick, + mUiEventLogger, mDeviceProvisionedController.isDeviceProvisioned(), row.getIsNonblockable(), mHighPriorityProvider.isHighPriority(row.getEntry())); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index a131ebef77db..f0c93b10578c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -56,6 +56,7 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -122,6 +123,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; private Drawable mPkgIcon; + private UiEventLogger mUiEventLogger; @VisibleForTesting boolean mSkipPost = false; @@ -182,6 +184,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G NotificationEntry entry, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, + UiEventLogger uiEventLogger, boolean isDeviceProvisioned, boolean isNonblockable, boolean wasShownHighPriority) @@ -205,6 +208,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; + mUiEventLogger = uiEventLogger; int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); @@ -223,6 +227,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G bindInlineControls(); + logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN); mMetricsLogger.write(notificationControlsLogMaker()); } @@ -397,6 +402,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G */ private void updateImportance() { if (mChosenImportance != null) { + logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE); mMetricsLogger.write(importanceChangeLogMaker()); int newImportance = mChosenImportance; @@ -483,6 +489,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G bindInlineControls(); + logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE); mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE)); } @@ -627,6 +634,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } + private void logUiEvent(NotificationControlsEvent event) { + if (mSbn != null) { + mUiEventLogger.logWithInstanceId(event, + mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId()); + } + } + /** * Returns a LogMaker with all available notification information. * Caller should set category, type, and maybe subtype, before passing it to mMetricsLogger. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index b4220f1da715..11e698b03823 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -83,6 +83,8 @@ public class AmbientState { private float mDozeAmount = 0.0f; private HeadsUpManager mHeadUpManager; private Runnable mOnPulseHeightChangedListener; + private ExpandableNotificationRow mTrackedHeadsUpRow; + private float mAppearFraction; public AmbientState( Context context, @@ -543,4 +545,27 @@ public class AmbientState { public Runnable getOnPulseHeightChangedListener() { return mOnPulseHeightChangedListener; } + + public void setTrackedHeadsUpRow(ExpandableNotificationRow row) { + mTrackedHeadsUpRow = row; + } + + /** + * Returns the currently tracked heads up row, if there is one and it is currently above the + * shelf (still appearing). + */ + public ExpandableNotificationRow getTrackedHeadsUpRow() { + if (mTrackedHeadsUpRow == null || !mTrackedHeadsUpRow.isAboveShelf()) { + return null; + } + return mTrackedHeadsUpRow; + } + + public void setAppearFraction(float appearFraction) { + mAppearFraction = appearFraction; + } + + public float getAppearFraction() { + return mAppearFraction; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java index 383f2a2b0e9f..040f707e12f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java @@ -50,9 +50,4 @@ public class MediaHeaderView extends ExpandableView { layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; } - - @Override - public boolean wantsAddAndRemoveAnimations() { - return false; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 684bf1958154..b9d31a93f408 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -97,6 +97,7 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; @@ -200,6 +201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private final KeyguardBypassController mKeyguardBypassController; private final DynamicPrivacyController mDynamicPrivacyController; private final SysuiStatusBarStateController mStatusbarStateController; + private final KeyguardMediaController mKeyguardMediaController; private ExpandHelper mExpandHelper; private final NotificationSwipeHelper mSwipeHelper; @@ -533,6 +535,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private float mLastSentAppear; private float mLastSentExpandedHeight; private boolean mWillExpand; + private int mGapHeight; private int mWaterfallTopInset; @@ -552,6 +555,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd SysuiStatusBarStateController statusBarStateController, HeadsUpManagerPhone headsUpManager, KeyguardBypassController keyguardBypassController, + KeyguardMediaController keyguardMediaController, FalsingManager falsingManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationGutsManager notificationGutsManager, @@ -670,6 +674,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd initializeForegroundServiceSection(fgsFeatureController); mUiEventLogger = uiEventLogger; mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); + mKeyguardMediaController = keyguardMediaController; + keyguardMediaController.setVisibilityChangedListener((visible) -> { + if (visible) { + generateAddAnimation(keyguardMediaController.getView(), false /*fromMoreCard */); + } else { + generateRemoveAnimation(keyguardMediaController.getView()); + } + requestChildrenUpdate(); + return null; + }); } private void initializeForegroundServiceSection( @@ -1047,6 +1061,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd Resources res = context.getResources(); mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); + mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mStackScrollAlgorithm.initView(context); mAmbientState.reload(context); mPaddingBetweenElements = Math.max(1, @@ -1407,14 +1422,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // start translationY = height - appearStartPosition + getExpandTranslationStart(); } + stackHeight = (int) (height - translationY); if (isHeadsUpTransition()) { - stackHeight = - getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight(); translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); - } else { - stackHeight = (int) (height - translationY); } } + mAmbientState.setAppearFraction(appearFraction); if (stackHeight != mCurrentStackHeight) { mCurrentStackHeight = stackHeight; updateAlgorithmHeightAndPadding(); @@ -1532,17 +1545,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ @ShadeViewRefactor(RefactorComponent.COORDINATOR) private float getAppearEndPosition() { - int appearPosition; - int notGoneChildCount = getNotGoneChildCount(); - if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) { + int appearPosition = 0; + int visibleNotifCount = getVisibleNotificationCount(); + if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { if (isHeadsUpTransition() || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) { - appearPosition = getTopHeadsUpPinnedHeight(); - } else { - appearPosition = 0; - if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) { - appearPosition += mShelf.getIntrinsicHeight(); + if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) { + appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; } + appearPosition += getTopHeadsUpPinnedHeight() + + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); + } else if (mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight(); } } else { appearPosition = mEmptyShadeView.getHeight(); @@ -1552,9 +1566,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean isHeadsUpTransition() { - NotificationSection firstVisibleSection = getFirstVisibleSection(); - return mTrackingHeadsUp && firstVisibleSection != null - && firstVisibleSection.getFirstVisibleChild().isAboveShelf(); + return mAmbientState.getTrackedHeadsUpRow() != null; } /** @@ -2962,7 +2974,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getLayoutMinHeight() { if (isHeadsUpTransition()) { - return getTopHeadsUpPinnedHeight(); + ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow(); + if (trackedHeadsUpRow.isAboveShelf()) { + int hunDistance = (int) MathUtils.lerp( + 0, + getPositionInLinearLayout(trackedHeadsUpRow), + mAmbientState.getAppearFraction()); + return getTopHeadsUpPinnedHeight() + hunDistance; + } else { + return getTopHeadsUpPinnedHeight(); + } } return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); } @@ -3101,9 +3122,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd */ @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private boolean generateRemoveAnimation(ExpandableView child) { - if (!child.wantsAddAndRemoveAnimations()) { - return false; - } if (removeRemovedChildFromHeadsUpChangeAnimations(child)) { mAddedHeadsUpChildren.remove(child); return false; @@ -3458,8 +3476,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { - if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden() - && child.wantsAddAndRemoveAnimations()) { + if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) { // Generate Animations mChildrenToAddAnimated.add(child); if (fromMoreCard) { @@ -3654,6 +3671,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd ignoreChildren = false; } childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth(); + } else if (child instanceof MediaHeaderView) { + childWasSwipedOut = true; } if (!childWasSwipedOut) { Rect clipBounds = child.getClipBounds(); @@ -5082,8 +5101,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public int getFooterViewHeight() { - return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements; + public int getFooterViewHeightWithPadding() { + return mFooterView == null ? 0 : mFooterView.getHeight() + + mPaddingBetweenElements + + mGapHeight; } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -5284,6 +5305,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setTrackingHeadsUp(ExpandableNotificationRow row) { + mAmbientState.setTrackedHeadsUpRow(row); mTrackingHeadsUp = row != null; mRoundnessManager.setTrackingHeadsUp(row); } @@ -6370,7 +6392,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public void onDragCancelled(View v) { setSwipingInProgress(false); - mFalsingManager.onNotificatonStopDismissing(); + mFalsingManager.onNotificationStopDismissing(); } /** @@ -6470,7 +6492,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @Override public void onBeginDrag(View v) { - mFalsingManager.onNotificatonStartDismissing(); + mFalsingManager.onNotificationStartDismissing(); setSwipingInProgress(true); mAmbientState.onBeginDrag((ExpandableView) v); updateContinuousShadowDrawing(); @@ -6629,7 +6651,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd /* Only ever called as a consequence of a lockscreen expansion gesture. */ @Override public boolean onDraggedDown(View startingChild, int dragLengthY) { - if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) { + boolean canDragDown = hasActiveNotifications() + || mKeyguardMediaController.getView().getVisibility() == VISIBLE; + if (mStatusBarState == StatusBarState.KEYGUARD && canDragDown) { mLockscreenGestureLogger.write( MetricsEvent.ACTION_LS_SHADE, (int) (dragLengthY / mDisplayMetrics.density), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index a4598e9cec9f..541c7845a5d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.util.Log; +import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -441,7 +442,7 @@ public class StackScrollAlgorithm { } else if (isEmptyShadeView) { childViewState.yTranslation = ambientState.getInnerHeight() - childHeight + ambientState.getStackTranslation() * 0.25f; - } else { + } else if (child != ambientState.getTrackedHeadsUpRow()) { clampPositionToShelf(child, childViewState, ambientState); } @@ -539,6 +540,19 @@ public class StackScrollAlgorithm { private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState, AmbientState ambientState) { int childCount = algorithmState.visibleChildren.size(); + + // Move the tracked heads up into position during the appear animation, by interpolating + // between the HUN inset (where it will appear as a HUN) and the end position in the shade + ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); + if (trackedHeadsUpRow != null) { + ExpandableViewState childState = trackedHeadsUpRow.getViewState(); + if (childState != null) { + float endPosition = childState.yTranslation - ambientState.getStackTranslation(); + childState.yTranslation = MathUtils.lerp( + mHeadsUpInset, endPosition, ambientState.getAppearFraction()); + } + } + ExpandableNotificationRow topHeadsUpEntry = null; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); @@ -561,7 +575,7 @@ public class StackScrollAlgorithm { && !row.showingPulsing()) { // Ensure that the heads up is always visible even when scrolled off clampHunToTop(ambientState, row, childState); - if (i == 0 && row.isAboveShelf()) { + if (isTopEntry && row.isAboveShelf()) { // the first hun can't get off screen. clampHunToMaxTranslation(ambientState, row, childState); childState.hidden = false; @@ -636,9 +650,13 @@ public class StackScrollAlgorithm { return; } + ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); + boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null + && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow); + int shelfStart = ambientState.getInnerHeight() - ambientState.getShelf().getIntrinsicHeight(); - if (ambientState.isAppearing() && !child.isAboveShelf()) { + if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) { // Don't show none heads-up notifications while in appearing phase. childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart); } @@ -695,7 +713,8 @@ public class StackScrollAlgorithm { } childViewState.zTranslation = baseZ + childrenOnTop * zDistanceBetweenElements; - } else if (i == 0 && (child.isAboveShelf() || child.showingPulsing())) { + } else if (child == ambientState.getTrackedHeadsUpRow() + || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) { // In case this is a new view that has never been measured before, we don't want to // elevate if we are currently expanded more then the notification int shelfHeight = ambientState.getShelf() == null ? 0 : @@ -703,7 +722,7 @@ public class StackScrollAlgorithm { float shelfStart = ambientState.getInnerHeight() - shelfHeight + ambientState.getTopPadding() + ambientState.getStackTranslation(); - float notificationEnd = childViewState.yTranslation + child.getPinnedHeadsUpHeight() + float notificationEnd = childViewState.yTranslation + child.getIntrinsicHeight() + mPaddingBetweenElements; if (shelfStart > notificationEnd) { childViewState.zTranslation = baseZ; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 90548ba7ba82..063305e0af90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -221,7 +221,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback */ private VerticalNavigationHandle mOrientationHandle; private WindowManager.LayoutParams mOrientationParams; - private int mStartingQuickSwitchRotation; + private int mStartingQuickSwitchRotation = -1; private int mCurrentRotation; private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener; private UiEventLogger mUiEventLogger; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 06484a226165..dbff643c6e32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -74,6 +74,7 @@ import com.android.systemui.recents.RecentsOnboarding; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; @@ -372,6 +373,11 @@ public class NavigationBarView extends FrameLayout implements @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (isGesturalMode(mNavBarMode) && mImeVisible + && event.getAction() == MotionEvent.ACTION_DOWN) { + SysUiStatsLog.write(SysUiStatsLog.IME_TOUCH_REPORTED, + (int) event.getX(), (int) event.getY()); + } return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index e720d820fd76..f2eec39ed17e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -443,6 +443,7 @@ public class NotificationPanelViewController extends PanelViewController { */ private boolean mDelayShowingKeyguardStatusBar; + private boolean mAnimatingQS; private int mOldLayoutDirection; private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() { @@ -1860,6 +1861,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onAnimationEnd(Animator animation) { + mAnimatingQS = false; notifyExpandingFinished(); mNotificationStackScroller.resetCheckSnoozeLeavebehind(); mQsExpansionAnimator = null; @@ -1868,6 +1870,9 @@ public class NotificationPanelViewController extends PanelViewController { } } }); + // Let's note that we're animating QS. Moving the animator here will cancel it immediately, + // so we need a separate flag. + mAnimatingQS = true; animator.start(); mQsExpansionAnimator = animator; mQsAnimatorExpand = expanding; @@ -2220,6 +2225,9 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScroller.onExpansionStarted(); mIsExpanding = true; mQsExpandedWhenExpandingStarted = mQsFullyExpanded; + mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted && + /* We also start expanding when flinging closed Qs. Let's exclude that */ + !mAnimatingQS); if (mQsExpanded) { onQsExpansionStarted(); } @@ -2236,6 +2244,7 @@ public class NotificationPanelViewController extends PanelViewController { mHeadsUpManager.onExpandingFinished(); mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed()); mIsExpanding = false; + mMediaHierarchyManager.setCollapsingShadeFromQS(false); if (isFullyCollapsed()) { DejankUtils.postAfterTraversal(new Runnable() { @Override @@ -2397,8 +2406,8 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - protected int getClearAllHeight() { - return mNotificationStackScroller.getFooterViewHeight(); + protected int getClearAllHeightWithPadding() { + return mNotificationStackScroller.getFooterViewHeightWithPadding(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index a902e1b0c960..caddc4a874f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -537,9 +537,9 @@ public abstract class PanelViewController { // the animation only to the last notification, and then jump to the maximum panel height so // clear all just fades in and the decelerating motion is towards the last notification. final boolean clearAllExpandHack = expand && - shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeight()); + shouldExpandToTopOfClearAll(getMaxPanelHeight() - getClearAllHeightWithPadding()); if (clearAllExpandHack) { - target = getMaxPanelHeight() - getClearAllHeight(); + target = getMaxPanelHeight() - getClearAllHeightWithPadding(); } if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) { notifyExpandingFinished(); @@ -1030,9 +1030,9 @@ public abstract class PanelViewController { protected abstract boolean isClearAllVisible(); /** - * @return the height of the clear all button, in pixels + * @return the height of the clear all button, in pixels including padding */ - protected abstract int getClearAllHeight(); + protected abstract int getClearAllHeightWithPadding(); public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 3df1c11bc044..6bc0565510a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -72,9 +72,6 @@ public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } - - // Creating AudioRecordingDisclosureBar and just letting it run - new AudioRecordingDisclosureBar(mContext); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index e5b126d7ff7f..3c0a23aa2eca 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -150,15 +150,10 @@ class TransitionLayout @JvmOverloads constructor( } override fun dispatchDraw(canvas: Canvas?) { - val clip = !boundsRect.isEmpty - if (clip) { - canvas?.save() - canvas?.clipRect(boundsRect) - } + canvas?.save() + canvas?.clipRect(boundsRect) super.dispatchDraw(canvas) - if (clip) { - canvas?.restore() - } + canvas?.restore() } private fun updateBounds() { diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt index b73aeb30009c..5143e429768e 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt @@ -46,6 +46,9 @@ open class TransitionLayoutController { private var state = TransitionViewState() private var pivot = PointF() private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) + private var currentHeight: Int = 0 + private var currentWidth: Int = 0 + var sizeChangedListener: ((Int, Int) -> Unit)? = null init { animator.apply { @@ -67,7 +70,16 @@ open class TransitionLayoutController { progress = animator.animatedFraction, pivot = pivot, resultState = currentState) - view.setState(currentState) + applyStateToLayout(currentState) + } + + private fun applyStateToLayout(state: TransitionViewState) { + transitionLayout?.setState(state) + if (currentHeight != state.height || currentWidth != state.width) { + currentHeight = state.height + currentWidth = state.width + sizeChangedListener?.invoke(currentWidth, currentHeight) + } } /** @@ -213,7 +225,7 @@ open class TransitionLayoutController { this.state = state.copy() if (applyImmediately || transitionLayout == null) { animator.cancel() - transitionLayout?.setState(this.state) + applyStateToLayout(this.state) currentState = state.copy(reusedState = currentState) } else if (animated) { animationStartState = currentState.copy() @@ -221,7 +233,7 @@ open class TransitionLayoutController { animator.startDelay = delay animator.start() } else if (!animator.isRunning) { - transitionLayout?.setState(this.state) + applyStateToLayout(this.state) currentState = state.copy(reusedState = currentState) } // otherwise the desired state was updated and the animation will go to the new target diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt index 5b6444d8feaf..d6e7a8b28f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt @@ -70,7 +70,10 @@ class UniqueObjectHostView( } override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) { - if (child?.measuredWidth == 0 || measuredWidth == 0 || child?.requiresRemeasuring == true) { + if (child == null) { + throw IllegalArgumentException("child must be non-null") + } + if (child.measuredWidth == 0 || measuredWidth == 0 || child.requiresRemeasuring == true) { super.addView(child, index, params) return } @@ -78,11 +81,13 @@ class UniqueObjectHostView( // right size when being attached to this view invalidate() addViewInLayout(child, index, params, true /* preventRequestLayout */) + // RTL properties are normally resolved in onMeasure(), which we are intentionally skipping + child.resolveRtlPropertiesIfNeeded() val left = paddingLeft val top = paddingTop val paddingHorizontal = paddingStart + paddingEnd val paddingVertical = paddingTop + paddingBottom - child!!.layout(left, + child.layout(left, top, left + measuredWidth - paddingHorizontal, top + measuredHeight - paddingVertical) diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index 7b114525adcd..8ba5b9951c54 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -56,12 +56,14 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private static final boolean DEBUG = false; + // NOTE: All these constants came from InsetsController. public static final int ANIMATION_DURATION_SHOW_MS = 275; public static final int ANIMATION_DURATION_HIDE_MS = 340; public static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); private static final int DIRECTION_NONE = 0; private static final int DIRECTION_SHOW = 1; private static final int DIRECTION_HIDE = 2; + private static final int FLOATING_IME_BOTTOM_INSET = -80; SystemWindows mSystemWindows; final Handler mHandler; @@ -271,8 +273,16 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } // Set frame, but only if the new frame isn't empty -- this maintains continuity final Rect newFrame = imeSource.getFrame(); - if (newFrame.height() != 0) { + mImeFrame.set(newFrame); + final boolean isFloating = newFrame.height() == 0; + if (isFloating) { + // This is likely a "floating" or "expanded" IME, so to get animations, just + // pretend the ime has some size just below the screen. mImeFrame.set(newFrame); + final int floatingInset = (int) ( + mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId).density() + * FLOATING_IME_BOTTOM_INSET); + mImeFrame.bottom -= floatingInset; } if (DEBUG) { Slog.d(TAG, "Run startAnim show:" + show + " was:" @@ -316,6 +326,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged SurfaceControl.Transaction t = mTransactionPool.acquire(); float value = (float) animation.getAnimatedValue(); t.setPosition(mImeSourceControl.getLeash(), x, value); + final float alpha = isFloating ? (value - hiddenY) / (shownY - hiddenY) : 1.f; + t.setAlpha(mImeSourceControl.getLeash(), alpha); dispatchPositionChanged(mDisplayId, imeTop(value), t); t.apply(); mTransactionPool.release(t); @@ -327,6 +339,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setPosition(mImeSourceControl.getLeash(), x, startY); + final float alpha = isFloating ? (startY - hiddenY) / (shownY - hiddenY) : 1.f; + t.setAlpha(mImeSourceControl.getLeash(), alpha); if (DEBUG) { Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:" + imeTop(hiddenY) + "->" + imeTop(shownY) @@ -351,6 +365,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged SurfaceControl.Transaction t = mTransactionPool.acquire(); if (!mCancelled) { t.setPosition(mImeSourceControl.getLeash(), x, endY); + t.setAlpha(mImeSourceControl.getLeash(), 1.f); } dispatchEndPositioning(mDisplayId, mCancelled, t); if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 73aaeffd6044..1cdc02fdd01a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -41,12 +41,10 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; import com.android.systemui.util.sensors.ProximitySensor; -import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -72,11 +70,12 @@ public class DozeTriggersTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private DockManager mDockManager; + @Mock + private ProximitySensor.ProximityCheck mProximityCheck; private DozeTriggers mTriggers; private FakeSensorManager mSensors; private Sensor mTapSensor; private FakeProximitySensor mProximitySensor; - private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { @@ -91,8 +90,8 @@ public class DozeTriggersTest extends SysuiTestCase { mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager); mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters, - asyncSensorManager, mFakeExecutor, wakeLock, true, - mDockManager, mProximitySensor, mock(DozeLog.class), mBroadcastDispatcher); + asyncSensorManager, wakeLock, true, mDockManager, mProximitySensor, + mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher); waitForSensorManager(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt index 9aee11e4924f..008dc12bba03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaHeaderView import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.util.mockito.capture import org.junit.Before import org.junit.Rule import org.junit.Test @@ -94,7 +95,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private fun triggerVisibilityListener() { keyguardMediaController.attach(mediaHeaderView) - verify(mediaHost).visibleChangedListener = visibilityListener.capture() + verify(mediaHost).addVisibilityChangeListener(capture(visibilityListener)) visibilityListener.value.invoke(true) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index a297f32b9c34..b7f317b38743 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -242,4 +242,15 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( com.android.internal.R.string.ext_media_seamless_action)) } + + @Test + fun bindDeviceResumptionPlayer() { + player.attach(holder) + val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), + emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null, + resumption = true) + player.bind(state) + assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) + assertThat(seamless.isEnabled()).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java index 618ee892b2b0..9fdd9ad744ff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java @@ -79,8 +79,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { mManager.addListener(mListener); mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, - new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, KEY, - false); + new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false, + KEY, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 54520be8a03f..20a6da548848 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -1,6 +1,11 @@ package com.android.systemui.media -import android.app.Notification +import android.app.Notification.MediaStyle +import android.app.PendingIntent +import android.media.MediaDescription +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession import android.service.notification.StatusBarNotification import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -8,6 +13,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.SbnBuilder +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -18,12 +26,14 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit -import java.util.concurrent.Executor import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val KEY = "KEY" private const val PACKAGE_NAME = "com.android.systemui" +private const val APP_NAME = "SystemUI" +private const val SESSION_ARTIST = "artist" +private const val SESSION_TITLE = "title" private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> anyObject(): T { @@ -36,33 +46,47 @@ private fun <T> anyObject(): T { class MediaDataManagerTest : SysuiTestCase() { @Mock lateinit var mediaControllerFactory: MediaControllerFactory - @Mock lateinit var backgroundExecutor: Executor - @Mock lateinit var foregroundExecutor: Executor + @Mock lateinit var controller: MediaController + lateinit var session: MediaSession + lateinit var metadataBuilder: MediaMetadata.Builder + lateinit var backgroundExecutor: FakeExecutor + lateinit var foregroundExecutor: FakeExecutor @Mock lateinit var dumpManager: DumpManager @Mock lateinit var broadcastDispatcher: BroadcastDispatcher @Mock lateinit var mediaTimeoutListener: MediaTimeoutListener @Mock lateinit var mediaResumeListener: MediaResumeListener + @Mock lateinit var pendingIntent: PendingIntent @JvmField @Rule val mockito = MockitoJUnit.rule() lateinit var mediaDataManager: MediaDataManager lateinit var mediaNotification: StatusBarNotification @Before fun setup() { + foregroundExecutor = FakeExecutor(FakeSystemClock()) + backgroundExecutor = FakeExecutor(FakeSystemClock()) mediaDataManager = MediaDataManager(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, useMediaResumption = true, useQsMediaPlayer = true) - val sbn = mock(StatusBarNotification::class.java) - val notification = mock(Notification::class.java) - whenever(notification.hasMediaSession()).thenReturn(true) - whenever(notification.notificationStyle).thenReturn(Notification.MediaStyle::class.java) - whenever(sbn.notification).thenReturn(notification) - whenever(sbn.packageName).thenReturn(PACKAGE_NAME) - mediaNotification = sbn + session = MediaSession(context, "MediaDataManagerTestSession") + mediaNotification = SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + } + build() + } + metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller) } @After fun tearDown() { + session.release() mediaDataManager.destroy() } @@ -82,7 +106,7 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testLoadsMetadataOnBackground() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) - verify(backgroundExecutor).execute(anyObject()) + assertThat(backgroundExecutor.numPending()).isEqualTo(1) } @Test @@ -123,4 +147,66 @@ class MediaDataManagerTest : SysuiTestCase() { verify(listener).onMediaDataRemoved(eq(KEY)) } -}
\ No newline at end of file + + @Test + fun testOnNotificationRemoved_withResumption() { + // GIVEN that the manager has a notification with a resume action + val listener = TestListener() + mediaDataManager.addListener(listener) + whenever(controller.metadata).thenReturn(metadataBuilder.build()) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + val data = listener.data!! + assertThat(data.resumption).isFalse() + mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {})) + // WHEN the notification is removed + mediaDataManager.onNotificationRemoved(KEY) + // THEN the media data indicates that it is + assertThat(listener.data!!.resumption).isTrue() + } + + @Test + fun testAddResumptionControls() { + val listener = TestListener() + mediaDataManager.addListener(listener) + // WHEN resumption controls are added` + val desc = MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + build() + } + mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME, + pendingIntent, PACKAGE_NAME) + assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) + assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) + // THEN the media data indicates that it is for resumption + val data = listener.data!! + assertThat(data.resumption).isTrue() + assertThat(data.song).isEqualTo(SESSION_TITLE) + assertThat(data.app).isEqualTo(APP_NAME) + assertThat(data.actions).hasSize(1) + } + + /** + * Simple implementation of [MediaDataManager.Listener] for the test. + * + * Giving up on trying to get a mock Listener and ArgumentCaptor to work. + */ + private class TestListener : MediaDataManager.Listener { + var data: MediaData? = null + var key: String? = null + var oldKey: String? = null + + override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { + this.key = key + this.oldKey = oldKey + this.data = data + } + + override fun onMediaDataRemoved(key: String) { + this.key = key + oldKey = null + data = null + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index c9e6f55ff59a..91c5ff8ee627 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -70,7 +70,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager @Mock - private lateinit var mediaViewManager: MediaViewManager + private lateinit var mediaCarouselController: MediaCarouselController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Captor @@ -82,13 +82,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Before fun setup() { - `when`(mediaViewManager.mediaFrame).thenReturn(mediaFrame) + `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) mediaHiearchyManager = MediaHierarchyManager( context, statusBarStateController, keyguardStateController, bypassController, - mediaViewManager, + mediaCarouselController, notificationLockscreenUserManager, wakefulnessLifecycle) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) @@ -97,7 +97,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS) `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE) // We'll use the viewmanager to verify a few calls below, let's reset this. - clearInvocations(mediaViewManager) + clearInvocations(mediaCarouselController) } @@ -118,14 +118,14 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fun testBlockedWhenScreenTurningOff() { // Let's set it onto QS: mediaHiearchyManager.qsExpansion = 1.0f - verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) observer.onStartedGoingToSleep() - clearInvocations(mediaViewManager) + clearInvocations(mediaCarouselController) mediaHiearchyManager.qsExpansion = 0.0f - verify(mediaViewManager, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + verify(mediaCarouselController, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) } @@ -133,13 +133,13 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fun testAllowedWhenNotTurningOff() { // Let's set it onto QS: mediaHiearchyManager.qsExpansion = 1.0f - verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) - clearInvocations(mediaViewManager) + clearInvocations(mediaCarouselController) mediaHiearchyManager.qsExpansion = 0.0f - verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(), + verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt index 75018df023cc..e9a0a40fe8ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt @@ -22,6 +22,7 @@ import android.view.View import android.widget.SeekBar import android.widget.TextView import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -36,6 +37,9 @@ import org.mockito.Mockito.`when` as whenever @TestableLooper.RunWithLooper public class SeekBarObserverTest : SysuiTestCase() { + private val disabledHeight = 1 + private val enabledHeight = 2 + private lateinit var observer: SeekBarObserver @Mock private lateinit var mockHolder: PlayerViewHolder private lateinit var seekBarView: SeekBar @@ -45,12 +49,19 @@ public class SeekBarObserverTest : SysuiTestCase() { @Before fun setUp() { mockHolder = mock(PlayerViewHolder::class.java) + + context.orCreateTestableResources + .addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight) + context.orCreateTestableResources + .addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight) + seekBarView = SeekBar(context) elapsedTimeView = TextView(context) totalTimeView = TextView(context) whenever(mockHolder.seekBar).thenReturn(seekBarView) whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView) whenever(mockHolder.totalTimeView).thenReturn(totalTimeView) + observer = SeekBarObserver(mockHolder) } @@ -60,11 +71,12 @@ public class SeekBarObserverTest : SysuiTestCase() { val isEnabled = false val data = SeekBarViewModel.Progress(isEnabled, false, null, null) observer.onChanged(data) - // THEN seek bar shows just a line with no text + // THEN seek bar shows just a thin line with no text assertThat(seekBarView.isEnabled()).isFalse() assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0) assertThat(elapsedTimeView.getText()).isEqualTo("") assertThat(totalTimeView.getText()).isEqualTo("") + assertThat(seekBarView.maxHeight).isEqualTo(disabledHeight) } @Test @@ -73,10 +85,11 @@ public class SeekBarObserverTest : SysuiTestCase() { val isEnabled = true val data = SeekBarViewModel.Progress(isEnabled, true, 3000, 12000) observer.onChanged(data) - // THEN seek bar is visible + // THEN seek bar is visible and thick assertThat(seekBarView.getVisibility()).isEqualTo(View.VISIBLE) assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.VISIBLE) assertThat(totalTimeView.getVisibility()).isEqualTo(View.VISIBLE) + assertThat(seekBarView.maxHeight).isEqualTo(enabledHeight) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 22f50d0fb591..e0d268127d90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; @@ -40,6 +42,7 @@ import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.pm.UserInfo; import android.graphics.Color; import android.hardware.biometrics.BiometricSourceType; import android.hardware.face.FaceManager; @@ -79,6 +82,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Collections; + @SmallTest @RunWith(AndroidJUnit4.class) public class KeyguardIndicationControllerTest extends SysuiTestCase { @@ -154,7 +159,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mController = new KeyguardIndicationController(mContext, mWakeLockBuilder, mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor, - mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats); + mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats, + mUserManager); mController.setIndicationArea(mIndicationArea); mController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); clearInvocations(mIBatteryStats); @@ -242,6 +248,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void disclosure_unmanaged() { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); + when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(false); createController(); verify(mDisclosure).setVisibility(View.GONE); @@ -249,7 +256,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test - public void disclosure_managedNoOwnerName() { + public void disclosure_deviceOwner_noOwnerName() { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); createController(); @@ -260,6 +267,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void disclosure_orgOwnedDeviceWithManagedProfile_noOwnerName() { + when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); + when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( + new UserInfo(10, /* name */ null, /* flags */ FLAG_MANAGED_PROFILE))); + when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(null); + createController(); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(R.string.do_disclosure_generic); + verifyNoMoreInteractions(mDisclosure); + } + + @Test public void disclosure_hiddenWhenDozing() { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(null); @@ -292,7 +312,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test - public void disclosure_managedOwnerName() { + public void disclosure_deviceOwner_withOwnerName() { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn(ORGANIZATION_NAME); createController(); @@ -303,6 +323,19 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { } @Test + public void disclosure_orgOwnedDeviceWithManagedProfile_withOwnerName() { + when(mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()).thenReturn(true); + when(mUserManager.getProfiles(anyInt())).thenReturn(Collections.singletonList( + new UserInfo(10, /* name */ null, FLAG_MANAGED_PROFILE))); + when(mDevicePolicyManager.getOrganizationNameForUser(eq(10))).thenReturn(ORGANIZATION_NAME); + createController(); + + verify(mDisclosure).setVisibility(View.VISIBLE); + verify(mDisclosure).switchIndication(mDisclosureWithOrganization); + verifyNoMoreInteractions(mDisclosure); + } + + @Test public void disclosure_updateOnTheFly() { ArgumentCaptor<BroadcastReceiver> receiver = ArgumentCaptor.forClass( BroadcastReceiver.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 06bad80d6f87..c979dc637fde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.logging; -import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -200,7 +198,7 @@ public class NotificationLoggerTest extends SysuiTestCase { assertEquals(TEST_UID, n.uid); assertEquals(1, n.instanceId); assertFalse(n.isGroupSummary); - assertEquals(1 + BUCKET_ALERTING, n.section); + assertEquals(Notifications.Notification.SECTION_ALERTING, n.section); } @Test @@ -217,7 +215,7 @@ public class NotificationLoggerTest extends SysuiTestCase { assertEquals(TEST_UID, n.uid); assertEquals(1, n.instanceId); assertFalse(n.isGroupSummary); - assertEquals(1 + BUCKET_ALERTING, n.section); + assertEquals(Notifications.Notification.SECTION_ALERTING, n.section); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java index ec73a7571969..43d8b50bcf72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/AppOpsInfoTest.java @@ -49,6 +49,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -69,6 +70,7 @@ public class AppOpsInfoTest extends SysuiTestCase { private final PackageManager mMockPackageManager = mock(PackageManager.class); private final NotificationGuts mGutsParent = mock(NotificationGuts.class); private StatusBarNotification mSbn; + private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); @Before public void setUp() throws Exception { @@ -94,7 +96,7 @@ public class AppOpsInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextApplicationName() { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, new ArraySet<>()); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); final TextView textView = mAppOpsInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); } @@ -104,7 +106,7 @@ public class AppOpsInfoTest extends SysuiTestCase { final Drawable iconDrawable = mock(Drawable.class); when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, new ArraySet<>()); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); final ImageView iconView = mAppOpsInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -120,7 +122,7 @@ public class AppOpsInfoTest extends SysuiTestCase { assertEquals(expectedOps, ops); assertEquals(TEST_UID, uid); latch.countDown(); - }, mSbn, expectedOps); + }, mSbn, mUiEventLogger, expectedOps); final View settingsButton = mAppOpsInfo.findViewById(R.id.settings); settingsButton.performClick(); @@ -129,6 +131,14 @@ public class AppOpsInfoTest extends SysuiTestCase { } @Test + public void testBindNotification_LogsOpen() throws Exception { + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, new ArraySet<>()); + assertEquals(1, mUiEventLogger.numLogs()); + assertEquals(NotificationAppOpsEvent.NOTIFICATION_APP_OPS_OPEN.getId(), + mUiEventLogger.eventId(0)); + } + + @Test public void testOk() { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_CAMERA); @@ -139,7 +149,7 @@ public class AppOpsInfoTest extends SysuiTestCase { assertEquals(expectedOps, ops); assertEquals(TEST_UID, uid); latch.countDown(); - }, mSbn, expectedOps); + }, mSbn, mUiEventLogger, expectedOps); final View okButton = mAppOpsInfo.findViewById(R.id.ok); okButton.performClick(); @@ -151,7 +161,7 @@ public class AppOpsInfoTest extends SysuiTestCase { public void testPrompt_camera() { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_CAMERA); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is using the camera.", prompt.getText()); } @@ -160,7 +170,7 @@ public class AppOpsInfoTest extends SysuiTestCase { public void testPrompt_mic() { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_RECORD_AUDIO); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is using the microphone.", prompt.getText()); } @@ -169,7 +179,7 @@ public class AppOpsInfoTest extends SysuiTestCase { public void testPrompt_overlay() { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is displaying over other apps on your screen.", prompt.getText()); } @@ -179,7 +189,7 @@ public class AppOpsInfoTest extends SysuiTestCase { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_CAMERA); expectedOps.add(OP_RECORD_AUDIO); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is using the microphone and camera.", prompt.getText()); } @@ -190,7 +200,7 @@ public class AppOpsInfoTest extends SysuiTestCase { expectedOps.add(OP_CAMERA); expectedOps.add(OP_RECORD_AUDIO); expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is displaying over other apps on your screen and using" + " the microphone and camera.", prompt.getText()); @@ -201,7 +211,7 @@ public class AppOpsInfoTest extends SysuiTestCase { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_CAMERA); expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is displaying over other apps on your screen and using" + " the camera.", prompt.getText()); @@ -212,7 +222,7 @@ public class AppOpsInfoTest extends SysuiTestCase { ArraySet<Integer> expectedOps = new ArraySet<>(); expectedOps.add(OP_RECORD_AUDIO); expectedOps.add(OP_SYSTEM_ALERT_WINDOW); - mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, expectedOps); + mAppOpsInfo.bindGuts(mMockPackageManager, null, mSbn, mUiEventLogger, expectedOps); TextView prompt = mAppOpsInfo.findViewById(R.id.prompt); assertEquals("This app is displaying over other apps on your screen and using" + " the microphone.", prompt.getText()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 5b5873b7a1cf..9dee84347ae1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -64,6 +64,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -149,7 +151,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider, mINotificationManager, mLauncherApps, mShortcutManager, - mChannelEditorDialogController, mContextTracker, mProvider, mBubbleController); + mChannelEditorDialogController, mContextTracker, mProvider, mBubbleController, + new UiEventLoggerFake()); mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -362,6 +365,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), + any(UiEventLogger.class), eq(false), eq(false), eq(true) /* wasShownHighPriority */); @@ -394,6 +398,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), + any(UiEventLogger.class), eq(true), eq(false), eq(false) /* wasShownHighPriority */); @@ -424,6 +429,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), + any(UiEventLogger.class), eq(false), eq(false), eq(false) /* wasShownHighPriority */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 6bf60721cd8e..ed982ab7d989 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -62,6 +62,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -102,6 +103,7 @@ public class NotificationInfoTest extends SysuiTestCase { private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>(); private StatusBarNotification mSbn; private NotificationEntry mEntry; + private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); @Rule public MockitoRule mockito = MockitoJUnit.rule(); @@ -187,6 +189,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -211,6 +214,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -231,6 +235,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -260,6 +265,7 @@ public class NotificationInfoTest extends SysuiTestCase { entry, null, null, + mUiEventLogger, true, false, true); @@ -281,6 +287,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -307,6 +314,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -328,6 +336,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -348,6 +357,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -372,6 +382,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -392,6 +403,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, true, true); @@ -416,6 +428,7 @@ public class NotificationInfoTest extends SysuiTestCase { latch.countDown(); }, null, + mUiEventLogger, true, false, true); @@ -439,6 +452,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -462,6 +476,7 @@ public class NotificationInfoTest extends SysuiTestCase { assertEquals(mNotificationChannel, c); }, null, + mUiEventLogger, false, false, true); @@ -482,6 +497,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -496,6 +512,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, (View v, NotificationChannel c, int appUid) -> { }, null, + mUiEventLogger, true, false, true); @@ -519,6 +536,7 @@ public class NotificationInfoTest extends SysuiTestCase { latch.countDown(); }, null, + mUiEventLogger, true, true, true); @@ -543,6 +561,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -565,6 +584,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -587,6 +607,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, true, true); @@ -611,6 +632,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -630,6 +652,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -649,6 +672,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -658,6 +682,28 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test + public void testBindNotification_LogsOpen() throws Exception { + mNotificationInfo.bindNotification( + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + mChannelEditorDialogController, + TEST_PACKAGE_NAME, + mNotificationChannel, + mNotificationChannelSet, + mEntry, + null, + null, + mUiEventLogger, + true, + false, + true); + assertEquals(1, mUiEventLogger.numLogs()); + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), + mUiEventLogger.eventId(0)); + } + + @Test public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_LOW); mNotificationInfo.bindNotification( @@ -671,6 +717,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -696,6 +743,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -721,6 +769,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -730,6 +779,13 @@ public class NotificationInfoTest extends SysuiTestCase { verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( anyString(), eq(TEST_UID), any()); assertEquals(originalImportance, mNotificationChannel.getImportance()); + + assertEquals(2, mUiEventLogger.numLogs()); + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), + mUiEventLogger.eventId(0)); + // The SAVE_IMPORTANCE event is logged whenever importance is saved, even if unchanged. + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(), + mUiEventLogger.eventId(1)); } @Test @@ -747,6 +803,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -773,6 +830,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -789,6 +847,12 @@ public class NotificationInfoTest extends SysuiTestCase { assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0); assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance()); + + assertEquals(2, mUiEventLogger.numLogs()); + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), + mUiEventLogger.eventId(0)); + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(), + mUiEventLogger.eventId(1)); } @Test @@ -805,6 +869,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -838,6 +903,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -871,6 +937,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -907,6 +974,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -942,6 +1010,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, true); @@ -968,6 +1037,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -997,6 +1067,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -1029,6 +1100,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false); @@ -1040,6 +1112,10 @@ public class NotificationInfoTest extends SysuiTestCase { mTestableLooper.processAllMessages(); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel)); + + assertEquals(1, mUiEventLogger.numLogs()); + assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN.getId(), + mUiEventLogger.eventId(0)); } @Test @@ -1056,6 +1132,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false @@ -1088,6 +1165,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false @@ -1113,6 +1191,7 @@ public class NotificationInfoTest extends SysuiTestCase { mEntry, null, null, + mUiEventLogger, true, false, false diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index c4bd1b281b24..b286f9486e13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -53,6 +53,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.FeatureFlags; @@ -133,6 +134,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { @Mock private MetricsLogger mMetricsLogger; @Mock private NotificationRoundnessManager mNotificationRoundnessManager; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private ZenModeController mZenModeController; @Mock private NotificationSectionsManager mNotificationSectionsManager; @Mock private NotificationSection mNotificationSection; @@ -209,6 +211,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(SysuiStatusBarStateController.class), mHeadsUpManager, mKeyguardBypassController, + mKeyguardMediaController, new FalsingManagerFake(), mLockscreenUserManager, mock(NotificationGutsManager.class), diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 05af5c9d0ef1..d36cf934b0cc 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -171,6 +171,14 @@ public class TetheringManager { */ public static final int TETHERING_ETHERNET = 5; + /** + * WIGIG tethering type. Use a separate type to prevent + * conflicts with TETHERING_WIFI + * This type is only used internally by the tethering module + * @hide + */ + public static final int TETHERING_WIGIG = 6; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 9269c6f0fd52..9b9dcde910e7 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -43,6 +43,13 @@ </string-array> <!-- List of regexpressions describing the interface (if any) that represent tetherable + WiGig interfaces. If the device doesn't want to support tethering over WiGig this + should be empty. An example would be "wigig\\d" --> + <string-array translatable="false" name="config_tether_wigig_regexs"> + <item>"wigig\\d"</item> + </string-array> + + <!-- List of regexpressions describing the interface (if any) that represent tetherable Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this should be empty. An example would be "p2p-p2p\\d-.*" --> <string-array translatable="false" name="config_tether_wifi_p2p_regexs"> diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml index 4e2bb1e31b2a..6a33d55cb0de 100644 --- a/packages/Tethering/res/values/overlayable.xml +++ b/packages/Tethering/res/values/overlayable.xml @@ -20,6 +20,7 @@ <item type="array" name="config_tether_usb_regexs"/> <item type="array" name="config_tether_ncm_regexs" /> <item type="array" name="config_tether_wifi_regexs"/> + <item type="array" name="config_tether_wigig_regexs"/> <item type="array" name="config_tether_wifi_p2p_regexs"/> <item type="array" name="config_tether_bluetooth_regexs"/> <item type="array" name="config_tether_dhcp_range"/> diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 35c156304c77..8af1797a9dd7 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -617,7 +617,8 @@ public class IpServer extends StateMachine { final Boolean setIfaceUp; if (mInterfaceType == TetheringManager.TETHERING_WIFI || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P - || mInterfaceType == TetheringManager.TETHERING_ETHERNET) { + || mInterfaceType == TetheringManager.TETHERING_ETHERNET + || mInterfaceType == TetheringManager.TETHERING_WIGIG) { // The WiFi and Ethernet stack has ownership of the interface up/down state. // It is unclear whether the Bluetooth or USB stacks will manage their own // state. diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index e1b36707558f..7508a653599d 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -40,6 +40,7 @@ import static android.net.TetheringManager.TETHERING_NCM; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHERING_WIGIG; import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; @@ -495,7 +496,8 @@ public class Tethering { if (up) { maybeTrackNewInterfaceLocked(iface); } else { - if (ifaceNameToType(iface) == TETHERING_BLUETOOTH) { + if (ifaceNameToType(iface) == TETHERING_BLUETOOTH + || ifaceNameToType(iface) == TETHERING_WIGIG) { stopTrackingInterfaceLocked(iface); } else { // Ignore usb0 down after enabling RNDIS. @@ -517,6 +519,8 @@ public class Tethering { if (cfg.isWifi(iface)) { return TETHERING_WIFI; + } else if (cfg.isWigig(iface)) { + return TETHERING_WIGIG; } else if (cfg.isWifiP2p(iface)) { return TETHERING_WIFI_P2P; } else if (cfg.isUsb(iface)) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index 18b2b7804fb0..e1771a561370 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -92,6 +92,7 @@ public class TetheringConfiguration { public final String[] tetherableUsbRegexs; public final String[] tetherableWifiRegexs; + public final String[] tetherableWigigRegexs; public final String[] tetherableWifiP2pRegexs; public final String[] tetherableBluetoothRegexs; public final String[] tetherableNcmRegexs; @@ -125,6 +126,7 @@ public class TetheringConfiguration { // us an interface name. Careful consideration needs to be given to // implications for Settings and for provisioning checks. tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs); + tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs); tetherableWifiP2pRegexs = getResourceStringArray( res, R.array.config_tether_wifi_p2p_regexs); tetherableBluetoothRegexs = getResourceStringArray( @@ -167,6 +169,11 @@ public class TetheringConfiguration { return matchesDownstreamRegexs(iface, tetherableWifiRegexs); } + /** Check whether input interface belong to wigig.*/ + public boolean isWigig(String iface) { + return matchesDownstreamRegexs(iface, tetherableWigigRegexs); + } + /** Check whether this interface is Wifi P2P interface. */ public boolean isWifiP2p(String iface) { return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 3114a6a02e31..2b9ce2f07c70 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1005,6 +1005,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState || disableDuration > 0) { // Response is "empty" from an UI point of view, need to notify client. notifyUnavailableToClient(sessionFinishedState, /* autofillableIds= */ null); + synchronized (mLock) { + mInlineSessionController.setInlineFillUiLocked( + InlineFillUi.emptyUi(mCurrentViewId)); + } } if (requestLog != null) { @@ -2979,8 +2983,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionsRequest.get(), response, focusedId, filterText, /*uiCallback*/this, /*onErrorCallback*/ () -> { synchronized (mLock) { - mInlineSessionController.hideInlineSuggestionsUiLocked( - focusedId); + mInlineSessionController.setInlineFillUiLocked( + InlineFillUi.emptyUi(focusedId)); } }, remoteRenderService); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); @@ -3166,12 +3170,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds); removeSelf(); } else { - if (sVerbose) { - if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { + if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { + if (sVerbose) { Slog.v(TAG, "keeping session " + id + " when service returned null and " + "augmented service is disabled for password fields. " + "AutofillableIds: " + autofillableIds); - } else { + } + mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); + } else { + if (sVerbose) { Slog.v(TAG, "keeping session " + id + " when service returned null but " + "it can be augmented. AutofillableIds: " + autofillableIds); } @@ -3197,7 +3204,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // non-null response but without datasets (for example, just SaveInfo) @GuardedBy("mLock") private Runnable triggerAugmentedAutofillLocked(int flags) { - // (TODO: b/141703197) Fix later by passing info to service. + // TODO: (b/141703197) Fix later by passing info to service. if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { return null; } @@ -3242,7 +3249,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); } logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), - mCurrentViewId, isWhitelisted, /*isInline*/null); + mCurrentViewId, isWhitelisted, /* isInline= */ null); return null; } @@ -3284,6 +3291,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /*onErrorCallback=*/ () -> { synchronized (mLock) { cancelAugmentedAutofillLocked(); + + // Also cancel augmented in IME + mInlineSessionController.setInlineFillUiLocked( + InlineFillUi.emptyUi(mCurrentViewId)); } }, mService.getRemoteInlineSuggestionRenderServiceLocked()); } diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 507e98369855..1c3116699b2d 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -33,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.metrics.LogMaker; import android.os.Handler; import android.os.IBinder; +import android.os.UserHandle; import android.service.autofill.BatchUpdates; import android.service.autofill.CustomDescription; import android.service.autofill.InternalOnClickAction; @@ -196,7 +197,9 @@ final class SaveUi { } intent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true); - PendingIntent p = PendingIntent.getActivity(this, 0, intent, 0); + PendingIntent p = PendingIntent.getActivityAsUser( + this, /* requestCode= */ 0, intent, /* flags= */ 0, /* options= */ null, + UserHandle.CURRENT); if (sDebug) { Slog.d(TAG, "startActivity add save UI restored with intent=" + intent); } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index dc35c774f71e..f6c4918a7dfe 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -318,7 +318,7 @@ public class UserBackupManagerService { private static final String SERIAL_ID_FILE = "serial_id"; - private static final String SKIP_USER_FACING_DATA = "backup_skip_user_facing_data"; + private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages"; private static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup"; private final @UserIdInt int mUserId; @@ -3557,7 +3557,7 @@ public class UserBackupManagerService { } /** - * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_data' is + * We want to skip backup/restore of certain packages if 'backup_skip_user_facing_packages' is * set to true in secure settings. See b/153940088 for details. * * TODO(b/154822946): Remove this logic in the next release. @@ -3581,7 +3581,7 @@ public class UserBackupManagerService { @VisibleForTesting public boolean shouldSkipUserFacingData() { - return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_DATA, + return Settings.Secure.getInt(mContext.getContentResolver(), SKIP_USER_FACING_PACKAGES, /* def */ 0) != 0; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index b46bebbd03d5..6e8eca3f46cf 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -458,6 +458,12 @@ class StorageManagerService extends IStorageManager.Stub "(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/(?:data|media|obb|sandbox)/)([^/]+)(/.*)?"); + /** Automotive device unlockes users before system boot complete and this requires special + * handling as vold reset can lead into race conditions. When this is set, all users unlocked + * in {@code UserManager} level are unlocked after vold reset. + */ + private final boolean mIsAutomotive; + private VolumeInfo findVolumeByIdOrThrow(String id) { synchronized (mLock) { final VolumeInfo vol = mVolumes.get(id); @@ -1082,14 +1088,12 @@ class StorageManagerService extends IStorageManager.Stub Slog.d(TAG, "Thinking about reset, mBootCompleted=" + mBootCompleted + ", mDaemonConnected=" + mDaemonConnected); if (mBootCompleted && mDaemonConnected) { - final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers(); + final UserManager userManager = mContext.getSystemService(UserManager.class); + final List<UserInfo> users = userManager.getUsers(); if (mIsFuseEnabled) { mStorageSessionController.onReset(mVold, () -> { - mHandler.removeMessages(H_RESET); - mHandler.removeMessages(H_VOLUME_BROADCAST); - mHandler.removeMessages(H_VOLUME_MOUNT); - mHandler.removeMessages(H_VOLUME_UNMOUNT); + mHandler.removeCallbacksAndMessages(null); }); } else { killMediaProvider(users); @@ -1097,7 +1101,9 @@ class StorageManagerService extends IStorageManager.Stub final int[] systemUnlockedUsers; synchronized (mLock) { - systemUnlockedUsers = mSystemUnlockedUsers; + // make copy as sorting can change order + systemUnlockedUsers = Arrays.copyOf(mSystemUnlockedUsers, + mSystemUnlockedUsers.length); mDisks.clear(); mVolumes.clear(); @@ -1119,6 +1125,9 @@ class StorageManagerService extends IStorageManager.Stub mVold.onUserStarted(userId); mStoraged.onUserStarted(userId); } + if (mIsAutomotive) { + restoreAllUnlockedUsers(userManager, users, systemUnlockedUsers); + } mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing); mStorageManagerInternal.onReset(mVold); } catch (Exception e) { @@ -1127,6 +1136,29 @@ class StorageManagerService extends IStorageManager.Stub } } + private void restoreAllUnlockedUsers(UserManager userManager, List<UserInfo> allUsers, + int[] systemUnlockedUsers) throws Exception { + Arrays.sort(systemUnlockedUsers); + UserManager.invalidateIsUserUnlockedCache(); + for (UserInfo user : allUsers) { + int userId = user.id; + if (!userManager.isUserRunning(userId)) { + continue; + } + if (Arrays.binarySearch(systemUnlockedUsers, userId) >= 0) { + continue; + } + boolean unlockingOrUnlocked = userManager.isUserUnlockingOrUnlocked(userId); + if (!unlockingOrUnlocked) { + continue; + } + Slog.w(TAG, "UNLOCK_USER lost from vold reset, will retry, user:" + userId); + mVold.onUserStarted(userId); + mStoraged.onUserStarted(userId); + mHandler.obtainMessage(H_COMPLETE_UNLOCK_USER, userId).sendToTarget(); + } + } + private void onUnlockUser(int userId) { Slog.d(TAG, "onUnlockUser " + userId); @@ -1157,6 +1189,15 @@ class StorageManagerService extends IStorageManager.Stub // Record user as started so newly mounted volumes kick off events // correctly, then synthesize events for any already-mounted volumes. synchronized (mLock) { + if (mIsAutomotive) { + for (int unlockedUser : mSystemUnlockedUsers) { + if (unlockedUser == userId) { + // This can happen as restoreAllUnlockedUsers can double post the message. + Log.i(TAG, "completeUnlockUser called for already unlocked user:" + userId); + return; + } + } + } for (int i = 0; i < mVolumes.size(); i++) { final VolumeInfo vol = mVolumes.valueAt(i); if (vol.isVisibleForRead(userId) && vol.isMountedReadable()) { @@ -1822,6 +1863,9 @@ class StorageManagerService extends IStorageManager.Stub if (WATCHDOG_ENABLE) { Watchdog.getInstance().addMonitor(this); } + + mIsAutomotive = context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); } /** @@ -4477,6 +4521,7 @@ class StorageManagerService extends IStorageManager.Stub pw.println("Forced scoped storage app list: " + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, PROP_FORCED_SCOPED_STORAGE_WHITELIST)); + pw.println("isAutomotive:" + mIsAutomotive); } synchronized (mObbMounts) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index f2c4e4428af2..2d8d2c32a4c3 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -348,6 +348,17 @@ class UserController implements Handler.Callback { @GuardedBy("mUserIdToUserJourneyMap") private final SparseArray<UserJourneySession> mUserIdToUserJourneyMap = new SparseArray<>(); + /** + * Sets on {@link #setInitialConfig(boolean, int, boolean)}, which is called by + * {@code ActivityManager} when the system is started. + * + * <p>It's useful to ignore external operations (i.e., originated outside {@code system_server}, + * like from {@code adb shell am switch-user})) that could happen before such call is made and + * the system is ready. + */ + @GuardedBy("mLock") + private boolean mInitialized; + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -372,6 +383,7 @@ class UserController implements Handler.Callback { mUserSwitchUiEnabled = userSwitchUiEnabled; mMaxRunningUsers = maxRunningUsers; mDelayUserDataLocking = delayUserDataLocking; + mInitialized = true; } } @@ -1587,6 +1599,11 @@ class UserController implements Handler.Callback { } boolean userSwitchUiEnabled; synchronized (mLock) { + if (!mInitialized) { + Slog.e(TAG, "Cannot switch to User #" + targetUserId + + ": UserController not ready yet"); + return false; + } mTargetUserId = targetUserId; userSwitchUiEnabled = mUserSwitchUiEnabled; } @@ -2422,6 +2439,7 @@ class UserController implements Handler.Callback { pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); + pw.println(" mInitialized:" + mInitialized); } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7e172a21de43..b5c173c91a53 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -3744,7 +3744,7 @@ public class AppOpsService extends IAppOpsService.Stub { mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, this, code, uidState.uid, true, null)); - } else { + } else if (uidState.pkgOps != null) { final ArraySet<ModeCallback> callbacks = mOpModeWatchers.get(code); if (callbacks != null) { for (int cbi = callbacks.size() - 1; cbi >= 0; cbi--) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index eef547087f86..6f3ba7c25847 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -413,6 +413,13 @@ public class AudioService extends IAudioService.Stub AppOpsManager.OP_AUDIO_MEDIA_VOLUME // STREAM_ASSISTANT }; + private static Set<Integer> sDeviceVolumeBehaviorSupportedDeviceOutSet = new HashSet<>( + Arrays.asList( + AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_OUT_HDMI_ARC, + AudioSystem.DEVICE_OUT_SPDIF, + AudioSystem.DEVICE_OUT_LINE)); + private final boolean mUseFixedVolume; /** @@ -525,7 +532,6 @@ public class AudioService extends IAudioService.Stub // Devices for which the volume is fixed (volume is either max or muted) Set<Integer> mFixedVolumeDevices = new HashSet<>(Arrays.asList( - AudioSystem.DEVICE_OUT_HDMI, AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, AudioSystem.DEVICE_OUT_HDMI_ARC, @@ -920,6 +926,8 @@ public class AudioService extends IAudioService.Stub if (mHdmiManager != null) { mHdmiManager.addHdmiControlStatusChangeListener( mHdmiControlStatusChangeListenerCallback); + mHdmiManager.addHdmiCecVolumeControlFeatureListener(mContext.getMainExecutor(), + mMyHdmiCecVolumeControlFeatureListener); } mHdmiTvClient = mHdmiManager.getTvClient(); if (mHdmiTvClient != null) { @@ -927,11 +935,6 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET); } mHdmiPlaybackClient = mHdmiManager.getPlaybackClient(); - if (mHdmiPlaybackClient != null) { - // not a television: HDMI output will be always at max - mFixedVolumeDevices.remove(AudioSystem.DEVICE_OUT_HDMI); - mFullVolumeDevices.add(AudioSystem.DEVICE_OUT_HDMI); - } mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient(); } } @@ -2248,6 +2251,7 @@ public class AudioService extends IAudioService.Stub if (mHdmiManager != null) { // mHdmiCecSink true => mHdmiPlaybackClient != null if (mHdmiCecSink + && mHdmiCecVolumeControlEnabled && streamTypeAlias == AudioSystem.STREAM_MUSIC // vol change on a full volume device && isFullVolumeDevice(device)) { @@ -2320,7 +2324,8 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mHdmiClientLock") private void maybeSendSystemAudioStatusCommand(boolean isMuteAdjust) { if (mHdmiAudioSystemClient == null - || !mHdmiSystemAudioSupported) { + || !mHdmiSystemAudioSupported + || !mHdmiCecVolumeControlEnabled) { return; } @@ -2340,7 +2345,8 @@ public class AudioService extends IAudioService.Stub || mHdmiTvClient == null || oldVolume == newVolume || (flags & AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME) != 0 - || !mHdmiSystemAudioSupported) { + || !mHdmiSystemAudioSupported + || !mHdmiCecVolumeControlEnabled) { return; } final long token = Binder.clearCallingIdentity(); @@ -2940,16 +2946,18 @@ public class AudioService extends IAudioService.Stub mVolumeController.postVolumeChanged(streamType, flags); } - // If Hdmi-CEC system audio mode is on and we are a TV panel, never show volume bar. + // Don't show volume UI when: + // - Hdmi-CEC system audio mode is on and we are a TV panel + // - CEC volume control enabled on a set-top box private int updateFlagsForTvPlatform(int flags) { synchronized (mHdmiClientLock) { - if (mHdmiTvClient != null && mHdmiSystemAudioSupported) { + if ((mHdmiTvClient != null && mHdmiSystemAudioSupported && mHdmiCecVolumeControlEnabled) + || (mHdmiPlaybackClient != null && mHdmiCecVolumeControlEnabled)) { flags &= ~AudioManager.FLAG_SHOW_UI; } } return flags; } - // UI update and Broadcast Intent private void sendMasterMuteUpdate(boolean muted, int flags) { mVolumeController.postMasterMuteChanged(updateFlagsForTvPlatform(flags)); @@ -4042,6 +4050,11 @@ public class AudioService extends IAudioService.Stub } readVolumeGroupsSettings(); + + if (DEBUG_VOL) { + Log.d(TAG, "Restoring device volume behavior"); + } + restoreDeviceVolumeBehavior(); } /** @see AudioManager#setSpeakerphoneOn(boolean) */ @@ -4901,50 +4914,47 @@ public class AudioService extends IAudioService.Stub if (pkgName == null) { pkgName = ""; } - // translate Java device type to native device type (for the devices masks for full / fixed) - final int type; - switch (device.getType()) { - case AudioDeviceInfo.TYPE_HDMI: - type = AudioSystem.DEVICE_OUT_HDMI; - break; - case AudioDeviceInfo.TYPE_HDMI_ARC: - type = AudioSystem.DEVICE_OUT_HDMI_ARC; - break; - case AudioDeviceInfo.TYPE_LINE_DIGITAL: - type = AudioSystem.DEVICE_OUT_SPDIF; - break; - case AudioDeviceInfo.TYPE_AUX_LINE: - type = AudioSystem.DEVICE_OUT_LINE; - break; - default: - // unsupported for now - throw new IllegalArgumentException("Unsupported device type " + device.getType()); + + int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( + device.getType()); + setDeviceVolumeBehaviorInternal(audioSystemDeviceOut, deviceVolumeBehavior, pkgName); + + persistDeviceVolumeBehavior(audioSystemDeviceOut, deviceVolumeBehavior); + } + + private void setDeviceVolumeBehaviorInternal(int audioSystemDeviceOut, + @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) { + if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut)) { + // unsupported for now + throw new IllegalArgumentException("Unsupported device type " + audioSystemDeviceOut); } + // update device masks based on volume behavior switch (deviceVolumeBehavior) { case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: - mFullVolumeDevices.remove(type); - mFixedVolumeDevices.remove(type); + removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut); + removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: - mFullVolumeDevices.remove(type); - mFixedVolumeDevices.add(type); + removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut); + addAudioSystemDeviceOutToFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: - mFullVolumeDevices.add(type); - mFixedVolumeDevices.remove(type); + addAudioSystemDeviceOutToFullVolumeDevices(audioSystemDeviceOut); + removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: throw new IllegalArgumentException("Absolute volume unsupported for now"); } + // log event and caller sDeviceLogger.log(new AudioEventLogger.StringEvent( - "Volume behavior " + deviceVolumeBehavior - + " for dev=0x" + Integer.toHexString(type) + " by pkg:" + pkgName)); + "Volume behavior " + deviceVolumeBehavior + " for dev=0x" + + Integer.toHexString(audioSystemDeviceOut) + " from:" + caller)); // make sure we have a volume entry for this device, and that volume is updated according // to volume behavior - checkAddAllFixedVolumeDevices(type, "setDeviceVolumeBehavior:" + pkgName); + checkAddAllFixedVolumeDevices(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller); } /** @@ -4952,45 +4962,38 @@ public class AudioService extends IAudioService.Stub * @param device the audio output device type * @return the volume behavior for the device */ - public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior( + public @AudioManager.DeviceVolumeBehaviorState int getDeviceVolumeBehavior( @NonNull AudioDeviceAttributes device) { // verify permissions enforceModifyAudioRoutingPermission(); + // translate Java device type to native device type (for the devices masks for full / fixed) - final int type; - switch (device.getType()) { - case AudioDeviceInfo.TYPE_HEARING_AID: - type = AudioSystem.DEVICE_OUT_HEARING_AID; - break; - case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: - type = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - break; - case AudioDeviceInfo.TYPE_HDMI: - type = AudioSystem.DEVICE_OUT_HDMI; - break; - case AudioDeviceInfo.TYPE_HDMI_ARC: - type = AudioSystem.DEVICE_OUT_HDMI_ARC; - break; - case AudioDeviceInfo.TYPE_LINE_DIGITAL: - type = AudioSystem.DEVICE_OUT_SPDIF; - break; - case AudioDeviceInfo.TYPE_AUX_LINE: - type = AudioSystem.DEVICE_OUT_LINE; - break; - default: - // unsupported for now - throw new IllegalArgumentException("Unsupported device type " + device.getType()); + final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( + device.getType()); + if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut) + && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_HEARING_AID) { + throw new IllegalArgumentException("Unsupported volume behavior " + + audioSystemDeviceOut); + } + + int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut); + if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { + return setDeviceVolumeBehavior; } - if ((mFullVolumeDevices.contains(type))) { + + // setDeviceVolumeBehavior has not been explicitly called for the device type. Deduce the + // current volume behavior. + if ((mFullVolumeDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL; } - if ((mFixedVolumeDevices.contains(type))) { + if ((mFixedVolumeDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED; } - if ((mAbsVolumeMultiModeCaseDevices.contains(type))) { + if ((mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE; } - if (type == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + if (audioSystemDeviceOut == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE; } @@ -7113,18 +7116,20 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mHdmiClientLock") private void updateHdmiCecSinkLocked(boolean hdmiCecSink) { mHdmiCecSink = hdmiCecSink; - if (mHdmiCecSink) { - if (DEBUG_VOL) { - Log.d(TAG, "CEC sink: setting HDMI as full vol device"); - } - mFullVolumeDevices.add(AudioSystem.DEVICE_OUT_HDMI); - } else { - if (DEBUG_VOL) { - Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device"); + if (!hasDeviceVolumeBehavior(AudioSystem.DEVICE_OUT_HDMI)) { + if (mHdmiCecSink) { + if (DEBUG_VOL) { + Log.d(TAG, "CEC sink: setting HDMI as full vol device"); + } + addAudioSystemDeviceOutToFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI); + } else { + if (DEBUG_VOL) { + Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device"); + } + // Android TV devices without CEC service apply software volume on + // HDMI output + removeAudioSystemDeviceOutFromFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI); } - // Android TV devices without CEC service apply software volume on - // HDMI output - mFullVolumeDevices.remove(AudioSystem.DEVICE_OUT_HDMI); } checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, @@ -7141,9 +7146,21 @@ public class AudioService extends IAudioService.Stub } }; + private class MyHdmiCecVolumeControlFeatureListener + implements HdmiControlManager.HdmiCecVolumeControlFeatureListener { + public void onHdmiCecVolumeControlFeature(boolean enabled) { + synchronized (mHdmiClientLock) { + if (mHdmiManager == null) return; + mHdmiCecVolumeControlEnabled = enabled; + } + } + }; + private final Object mHdmiClientLock = new Object(); // If HDMI-CEC system audio is supported + // Note that for CEC volume commands mHdmiCecVolumeControlEnabled will play a role on volume + // commands private boolean mHdmiSystemAudioSupported = false; // Set only when device is tv. @GuardedBy("mHdmiClientLock") @@ -7161,10 +7178,16 @@ public class AudioService extends IAudioService.Stub // Set only when device is an audio system. @GuardedBy("mHdmiClientLock") private HdmiAudioSystemClient mHdmiAudioSystemClient; + // True when volume control over HDMI CEC is used when CEC is enabled (meaningless otherwise) + @GuardedBy("mHdmiClientLock") + private boolean mHdmiCecVolumeControlEnabled; private MyHdmiControlStatusChangeListenerCallback mHdmiControlStatusChangeListenerCallback = new MyHdmiControlStatusChangeListenerCallback(); + private MyHdmiCecVolumeControlFeatureListener mMyHdmiCecVolumeControlFeatureListener = + new MyHdmiCecVolumeControlFeatureListener(); + @Override public int setHdmiSystemAudioSupported(boolean on) { int device = AudioSystem.DEVICE_NONE; @@ -7403,6 +7426,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient); pw.print(" mHdmiTvClient="); pw.println(mHdmiTvClient); pw.print(" mHdmiSystemAudioSupported="); pw.println(mHdmiSystemAudioSupported); + pw.print(" mHdmiCecVolumeControlEnabled="); pw.println(mHdmiCecVolumeControlEnabled); pw.print(" mIsCallScreeningModeSupported="); pw.println(mIsCallScreeningModeSupported); pw.print(" mic mute FromSwitch=" + mMicMuteFromSwitch + " FromRestrictions=" + mMicMuteFromRestrictions @@ -8944,4 +8968,91 @@ public class AudioService extends IAudioService.Stub } return mFullVolumeDevices.contains(deviceType); } + + //==================== + // Helper functions for {set,get}DeviceVolumeBehavior + //==================== + private static String getSettingsNameForDeviceVolumeBehavior(int deviceType) { + return "AudioService_DeviceVolumeBehavior_" + AudioSystem.getOutputDeviceName(deviceType); + } + + private void persistDeviceVolumeBehavior(int deviceType, + @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) { + if (DEBUG_VOL) { + Log.d(TAG, "Persisting Volume Behavior for DeviceType: " + deviceType); + } + System.putIntForUser(mContentResolver, + getSettingsNameForDeviceVolumeBehavior(deviceType), + deviceVolumeBehavior, + UserHandle.USER_CURRENT); + } + + @AudioManager.DeviceVolumeBehaviorState + private int retrieveStoredDeviceVolumeBehavior(int deviceType) { + return System.getIntForUser(mContentResolver, + getSettingsNameForDeviceVolumeBehavior(deviceType), + AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET, + UserHandle.USER_CURRENT); + } + + private void restoreDeviceVolumeBehavior() { + for (int deviceType : sDeviceVolumeBehaviorSupportedDeviceOutSet) { + if (DEBUG_VOL) { + Log.d(TAG, "Retrieving Volume Behavior for DeviceType: " + deviceType); + } + int deviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(deviceType); + if (deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { + if (DEBUG_VOL) { + Log.d(TAG, "Skipping Setting Volume Behavior for DeviceType: " + deviceType); + } + continue; + } + + setDeviceVolumeBehaviorInternal(deviceType, deviceVolumeBehavior, + "AudioService.restoreDeviceVolumeBehavior()"); + } + } + + /** + * @param audioSystemDeviceOut one of AudioSystem.DEVICE_OUT_* + * @return whether {@code audioSystemDeviceOut} has previously been set to a specific volume + * behavior + */ + private boolean hasDeviceVolumeBehavior( + int audioSystemDeviceOut) { + return retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut) + != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET; + } + + private void addAudioSystemDeviceOutToFixedVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " to mFixedVolumeDevices"); + } + mFixedVolumeDevices.add(audioSystemDeviceOut); + } + + private void removeAudioSystemDeviceOutFromFixedVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " from mFixedVolumeDevices"); + } + mFixedVolumeDevices.remove(audioSystemDeviceOut); + } + + private void addAudioSystemDeviceOutToFullVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " to mFullVolumeDevices"); + } + mFullVolumeDevices.add(audioSystemDeviceOut); + } + + private void removeAudioSystemDeviceOutFromFullVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " from mFullVolumeDevices"); + } + mFullVolumeDevices.remove(audioSystemDeviceOut); + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d0bd635c2255..2c0ddaf35182 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -213,6 +213,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mLocalDeviceAddresses = initLocalDeviceAddresses(); resetSelectRequestBuffer(); launchDeviceDiscovery(); + startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { mService.sendCecCommand(HdmiCecMessageBuilder.buildRequestActiveSource(mAddress)); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index de13bd86a415..70f0399d1070 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -17,6 +17,7 @@ package com.android.server.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.os.IBinder; import android.view.inputmethod.InlineSuggestionsRequest; @@ -109,6 +110,16 @@ public abstract class InputMethodManagerInternal { int displayId); /** + * Reports that IME control has transferred to the given window token, or if null that + * control has been taken away from client windows (and is instead controlled by the policy + * or SystemUI). + * + * @param windowToken the window token that is now in control, or {@code null} if no client + * window is in control of the IME. + */ + public abstract void reportImeControl(@Nullable IBinder windowToken); + + /** * Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing. */ private static final InputMethodManagerInternal NOP = @@ -151,6 +162,10 @@ public abstract class InputMethodManagerInternal { int displayId) { return false; } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + } }; /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index fea7980c1c24..9acb47538043 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -179,6 +179,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -593,6 +594,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean mCurClientInKeyguard; /** + * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} + */ + private boolean mCurPerceptible; + + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ @@ -2936,6 +2942,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { vis = 0; } + if (!mCurPerceptible) { + vis = 0; + } // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBar != null) { @@ -3148,6 +3157,28 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @BinderThread + @Override + public void reportPerceptible(IBinder windowToken, boolean perceptible) { + Objects.requireNonNull(windowToken, "windowToken must not be null"); + int uid = Binder.getCallingUid(); + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } + final long ident = Binder.clearCallingIdentity(); + try { + if (mCurFocusedWindow == windowToken + && mCurPerceptible != perceptible) { + mCurPerceptible = perceptible; + updateSystemUiLocked(mImeWindowVis, mBackDisposition); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + @GuardedBy("mMethodMap") boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { @@ -3451,6 +3482,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow = windowToken; mCurFocusedWindowSoftInputMode = softInputMode; mCurFocusedWindowClient = cs; + mCurPerceptible = true; // Should we auto-show the IME even if the caller has not // specified what should be done with it? @@ -5010,6 +5042,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken); } + private void reportImeControl(@Nullable IBinder windowToken) { + synchronized (mMethodMap) { + if (mCurFocusedWindow != windowToken) { + // mCurPerceptible was set by the focused window, but it is no longer in control, + // so we reset mCurPerceptible. + mCurPerceptible = true; + } + } + } + private static final class LocalServiceImpl extends InputMethodManagerInternal { @NonNull private final InputMethodManagerService mService; @@ -5062,6 +5104,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int displayId) { return mService.transferTouchFocusToImeWindow(sourceInputToken, displayId); } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + mService.reportImeControl(windowToken); + } } @BinderThread @@ -5164,6 +5211,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub p.println(" mCurMethodId=" + mCurMethodId); client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); + p.println(" mCurPerceptible=" + mCurPerceptible); p.println(" mCurFocusedWindow=" + mCurFocusedWindow + " softInputMode=" + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 33c78e403ce3..2e3d3963c9df 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -221,6 +221,10 @@ public final class MultiClientInputMethodManagerService { reportNotSupported(); return false; } + + @Override + public void reportImeControl(@Nullable IBinder windowToken) { + } }); } @@ -1753,6 +1757,12 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override + public void reportPerceptible(IBinder windowClient, boolean perceptible) { + reportNotSupported(); + } + + @BinderThread + @Override public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, String[] args, @Nullable ShellCallback callback, ResultReceiver resultReceiver) { diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 802a35560ba5..09fd33d5b4ed 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -212,7 +212,7 @@ public class StatsPullAtomService extends SystemService { private static final int DIMENSION_KEY_SIZE_HARD_LIMIT = 800; private static final int DIMENSION_KEY_SIZE_SOFT_LIMIT = 500; private static final long APP_OPS_SAMPLING_INITIALIZATION_DELAY_MILLIS = 45000; - private static final int APP_OPS_SIZE_ESTIMATE = 5000; + private static final int APP_OPS_SIZE_ESTIMATE = 2000; private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; /** @@ -320,8 +320,7 @@ public class StatsPullAtomService extends SystemService { private StatsPullAtomCallbackImpl mStatsCallbackImpl; - private final Object mAppOpsSamplingRateLock = new Object(); - @GuardedBy("mAppOpsSamplingRateLock") + @GuardedBy("mAttributedAppOpsLock") private int mAppOpsSamplingRate = 0; private final Object mDangerousAppOpsListLock = new Object(); @GuardedBy("mDangerousAppOpsListLock") @@ -3084,7 +3083,7 @@ public class StatsPullAtomService extends SystemService { int pullDangerousPermissionStateLocked(int atomTag, List<StatsEvent> pulledData) { final long token = Binder.clearCallingIdentity(); float samplingRate = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_PERMISSIONS, - DANGEROUS_PERMISSION_STATE_SAMPLE_RATE, 0.02f); + DANGEROUS_PERMISSION_STATE_SAMPLE_RATE, 0.015f); Set<Integer> reportedUids = new HashSet<>(); try { PackageManager pm = mContext.getPackageManager(); @@ -3479,23 +3478,21 @@ public class StatsPullAtomService extends SystemService { HistoricalOps histOps = ops.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - synchronized (mAppOpsSamplingRateLock) { - if (mAppOpsSamplingRate == 0) { - mContext.getMainThreadHandler().postDelayed(new Runnable() { - @Override - public void run() { - try { - estimateAppOpsSamplingRate(); - } catch (Throwable e) { - Slog.e(TAG, "AppOps sampling ratio estimation failed: ", e); - synchronized (mAppOpsSamplingRateLock) { - mAppOpsSamplingRate = min(mAppOpsSamplingRate, 10); - } + if (mAppOpsSamplingRate == 0) { + mContext.getMainThreadHandler().postDelayed(new Runnable() { + @Override + public void run() { + try { + estimateAppOpsSamplingRate(); + } catch (Throwable e) { + Slog.e(TAG, "AppOps sampling ratio estimation failed: ", e); + synchronized (mAttributedAppOpsLock) { + mAppOpsSamplingRate = min(mAppOpsSamplingRate, 10); } } - }, APP_OPS_SAMPLING_INITIALIZATION_DELAY_MILLIS); - mAppOpsSamplingRate = 100; - } + } + }, APP_OPS_SAMPLING_INITIALIZATION_DELAY_MILLIS); + mAppOpsSamplingRate = 100; } List<AppOpEntry> opsList = @@ -3503,9 +3500,7 @@ public class StatsPullAtomService extends SystemService { int newSamplingRate = sampleAppOps(pulledData, opsList, atomTag, mAppOpsSamplingRate); - synchronized (mAppOpsSamplingRateLock) { - mAppOpsSamplingRate = min(mAppOpsSamplingRate, newSamplingRate); - } + mAppOpsSamplingRate = min(mAppOpsSamplingRate, newSamplingRate); } catch (Throwable t) { // TODO: catch exceptions at a more granular level Slog.e(TAG, "Could not read appops", t); @@ -3544,7 +3539,7 @@ public class StatsPullAtomService extends SystemService { } int estimatedSamplingRate = (int) constrain( appOpsTargetCollectionSize * 100 / estimatedSize, 0, 100); - synchronized (mAppOpsSamplingRateLock) { + synchronized (mAttributedAppOpsLock) { mAppOpsSamplingRate = min(mAppOpsSamplingRate, estimatedSamplingRate); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ed055eb7eb09..9b5d94ebb1ac 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -210,6 +210,7 @@ import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledFunction; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.utils.DisplayRotationUtil; @@ -3566,6 +3567,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void updateImeControlTarget() { mInputMethodControlTarget = computeImeControlTarget(); mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); + + final WindowState win = mInputMethodControlTarget != null + ? mInputMethodControlTarget.getWindow() : null; + final IBinder token = win != null ? win.mClient.asBinder() : null; + // Note: not allowed to call into IMMS with the WM lock held, hence the post. + mWmService.mH.post(() -> + InputMethodManagerInternal.get().reportImeControl(token) + ); } private void updateImeParent() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8cfe1cd5e98f..72b014cab2bb 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3782,7 +3782,8 @@ public class DisplayPolicy { && (behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE || behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE) && getInsetsPolicy().isHidden(ITYPE_NAVIGATION_BAR) - && win != getNotificationShade(); + && win != getNotificationShade() + && !win.isActivityTypeDream(); } /** diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index c7b91067d59f..254356d673e3 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -482,6 +482,12 @@ class InsetsPolicy { WindowInsetsAnimation animation, Bounds bounds) { } + + @Override + public void reportPerceptible(int types, boolean perceptible) { + // No-op for now - only client windows report perceptibility for now, with policy + // controllers assumed to always be perceptible. + } } } } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 4a0da75cdad8..892ee717e21f 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -175,7 +175,7 @@ public class LockTaskController { * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED}, * {@link ActivityManager#LOCK_TASK_MODE_PINNED} */ - private int mLockTaskModeState = LOCK_TASK_MODE_NONE; + private volatile int mLockTaskModeState = LOCK_TASK_MODE_NONE; /** * This is ActivityStackSupervisor's Handler. @@ -500,24 +500,29 @@ public class LockTaskController { // This method should only be called on the handler thread private void performStopLockTask(int userId) { + // Update the internal mLockTaskModeState early to avoid the scenario that SysUI queries + // mLockTaskModeState (from setStatusBarState) and gets staled state. + // TODO: revisit this approach. + // The race condition raised above can be addressed by moving this function out of handler + // thread, which makes it guarded by ATMS#mGlobalLock as ATMS#getLockTaskModeState. + final int oldLockTaskModeState = mLockTaskModeState; + mLockTaskModeState = LOCK_TASK_MODE_NONE; // When lock task ends, we enable the status bars. try { - setStatusBarState(LOCK_TASK_MODE_NONE, userId); - setKeyguardState(LOCK_TASK_MODE_NONE, userId); - if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { + setStatusBarState(mLockTaskModeState, userId); + setKeyguardState(mLockTaskModeState, userId); + if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) { lockKeyguardIfNeeded(); } if (getDevicePolicyManager() != null) { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); } - if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { + if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) { getStatusBarService().showPinningEnterExitToast(false /* entering */); } - mWindowManager.onLockTaskStateChanged(LOCK_TASK_MODE_NONE); + mWindowManager.onLockTaskStateChanged(mLockTaskModeState); } catch (RemoteException ex) { throw new RuntimeException(ex); - } finally { - mLockTaskModeState = LOCK_TASK_MODE_NONE; } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 3a1619ba11a1..1a6d85547c92 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1897,6 +1897,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } boolean needsZBoost() { + if (mNeedsZBoost) return true; for (int i = 0; i < mChildren.size(); i++) { if (mChildren.get(i).needsZBoost()) { return true; diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index c7dac787c653..465a3518ab65 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -3109,9 +3109,7 @@ static jboolean android_location_GnssMeasurementsProvider_start_measurement_coll result = gnssMeasurementIface_V1_1->setCallback_1_1(cbIface, enableFullTracking); } else { if (enableFullTracking == JNI_TRUE) { - // full tracking mode not supported in 1.0 HAL - result.assertOk(); // isOk() must be called before result destructor is invoked. - return JNI_FALSE; + ALOGW("Full tracking mode not supported in 1.0 GNSS HAL."); } result = gnssMeasurementIface->setCallback(cbIface); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java new file mode 100644 index 000000000000..b88573ae168a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.hdmi; + +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Handler; +import android.os.IPowerManager; +import android.os.IThermalService; +import android.os.Looper; +import android.os.PowerManager; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@SmallTest +@RunWith(JUnit4.class) +/** Tests for {@link HdmiCecLocalDeviceTv} class. */ +public class HdmiCecLocalDeviceTvTest { + + private HdmiControlService mHdmiControlService; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDeviceTv mHdmiCecLocalDeviceTv; + private FakeNativeWrapper mNativeWrapper; + private Looper mMyLooper; + private TestLooper mTestLooper = new TestLooper(); + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mTvPhysicalAddress; + + @Mock + private IPowerManager mIPowerManagerMock; + @Mock + private IThermalService mIThermalServiceMock; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context context = InstrumentationRegistry.getTargetContext(); + mMyLooper = mTestLooper.getLooper(); + PowerManager powerManager = new PowerManager(context, mIPowerManagerMock, + mIThermalServiceMock, new Handler(mMyLooper)); + mHdmiControlService = + new HdmiControlService(InstrumentationRegistry.getTargetContext()) { + @Override + boolean isControlEnabled() { + return true; + } + + @Override + boolean isTvDevice() { + return true; + } + + @Override + void writeStringSystemProperty(String key, String value) { + // do nothing + } + + @Override + PowerManager getPowerManager() { + return powerManager; + } + }; + + mHdmiCecLocalDeviceTv = new HdmiCecLocalDeviceTv(mHdmiControlService); + mHdmiCecLocalDeviceTv.init(); + mHdmiControlService.setIoLooper(mMyLooper); + mNativeWrapper = new FakeNativeWrapper(); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); + mHdmiControlService.setCecController(mHdmiCecController); + mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); + mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); + mLocalDevices.add(mHdmiCecLocalDeviceTv); + mHdmiControlService.initPortInfo(); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTvPhysicalAddress = 0x0000; + mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress); + mTestLooper.dispatchAll(); + mNativeWrapper.clearResultMessages(); + } + + @Test + public void initialPowerStateIsStandby() { + assertThat(mHdmiCecLocalDeviceTv.getPowerStatus()).isEqualTo( + HdmiControlManager.POWER_STATUS_STANDBY); + } + + @Test + public void onAddressAllocated_invokesDeviceDiscovery() { + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiCecLocalDeviceTv.onAddressAllocated(0, HdmiControlService.INITIATED_BY_BOOT_UP); + + mTestLooper.dispatchAll(); + + // Check for for <Give Physical Address> being sent to available device (ADDR_PLAYBACK_1). + // This message is sent as part of the DeviceDiscoveryAction to available devices. + HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(ADDR_TV, + ADDR_PLAYBACK_1); + assertThat(mNativeWrapper.getResultMessages()).contains(givePhysicalAddress); + } +} diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 1a38a42873b7..bc987a6282c7 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -303,12 +303,6 @@ public final class TelephonyPermissions { String message, boolean allowCarrierPrivilegeOnAnySub) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); - PermissionManager permissionManager = (PermissionManager) context.getSystemService( - Context.PERMISSION_SERVICE); - if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId, - pid, uid) == PackageManager.PERMISSION_GRANTED) { - return true; - } // If the calling package has carrier privileges for specified sub, then allow access. if (checkCarrierPrivilegeForSubId(context, subId)) return true; @@ -319,6 +313,13 @@ public final class TelephonyPermissions { return true; } + PermissionManager permissionManager = (PermissionManager) context.getSystemService( + Context.PERMISSION_SERVICE); + if (permissionManager.checkDeviceIdentifierAccess(callingPackage, message, callingFeatureId, + pid, uid) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage, message); } @@ -433,16 +434,6 @@ public final class TelephonyPermissions { public static boolean checkReadPhoneNumber( Context context, int subId, int pid, int uid, String callingPackage, @Nullable String callingFeatureId, String message) { - // Default SMS app can always read it. - AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - if (appOps.noteOp(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, callingFeatureId, - null) == AppOpsManager.MODE_ALLOWED) { - return true; - } - - // NOTE(b/73308711): If an app has one of the following AppOps bits explicitly revoked, they - // will be denied access, even if they have another permission and AppOps bit if needed. - // First, check if the SDK version is below R boolean preR = false; try { @@ -477,21 +468,29 @@ public final class TelephonyPermissions { } } + // Default SMS app can always read it. + AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + if (appOps.noteOp(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, callingFeatureId, + null) == AppOpsManager.MODE_ALLOWED) { + return true; + } // Can be read with READ_SMS too. try { context.enforcePermission(android.Manifest.permission.READ_SMS, pid, uid, message); - return appOps.noteOp(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, - callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; - + if (appOps.noteOp(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { + return true; + } } catch (SecurityException readSmsSecurityException) { } // Can be read with READ_PHONE_NUMBERS too. try { context.enforcePermission(android.Manifest.permission.READ_PHONE_NUMBERS, pid, uid, message); - return appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, - callingFeatureId, null) == AppOpsManager.MODE_ALLOWED; - + if (appOps.noteOp(AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, + callingFeatureId, null) == AppOpsManager.MODE_ALLOWED) { + return true; + } } catch (SecurityException readPhoneNumberSecurityException) { } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index d62cd0a63b44..11667c83bc6a 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -305,11 +305,14 @@ public class SubscriptionInfo implements Parcelable { } /** - * Returns the ICC ID if the calling app has been granted the READ_PRIVILEGED_PHONE_STATE - * permission, has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}), or - * is a device owner or profile owner that has been granted the READ_PHONE_STATE permission. - * The profile owner is an app that owns a managed profile on the device; for more details see - * <a href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile + * Returns the ICC ID. + * + * Starting with API level 30, returns the ICC ID if the calling app has been granted the + * READ_PRIVILEGED_PHONE_STATE permission, has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}), or is a device owner or profile owner that + * has been granted the READ_PHONE_STATE permission. The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile * owner access is deprecated and will be removed in a future release. * * @return the ICC ID, or an empty string if one of these requirements is not met @@ -449,8 +452,22 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the number of this subscription if the calling app has been granted the - * READ_PHONE_NUMBERS permission, or an empty string otherwise + * Returns the number of this subscription. + * + * Starting with API level 30, returns the number of this subscription if the calling app meets + * one of the following requirements: + * <ul> + * <li>If the calling app's target SDK is API level 29 or lower and the app has been granted + * the READ_PHONE_STATE permission. + * <li>If the calling app has been granted any of READ_PRIVILEGED_PHONE_STATE, + * READ_PHONE_NUMBERS, or READ_SMS. + * <li>If the calling app has carrier privileges (see {@link + * TelephonyManager#hasCarrierPrivileges}). + * <li>If the calling app is the default SMS role holder. + * </ul> + * + * @return the number of this subscription, or an empty string if one of these requirements is + * not met */ public String getNumber() { return mNumber; @@ -670,12 +687,15 @@ public class SubscriptionInfo implements Parcelable { } /** - * Returns the card string if the calling app has been granted the READ_PRIVILEGED_PHONE_STATE - * permission, has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}), or - * is a device owner or profile owner on an organization owned device that has been granted the - * READ_PHONE_STATE permission. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. + * Returns the card string of the SIM card which contains the subscription. + * + * Starting with API level 30, returns the card string if the calling app has been granted the + * READ_PRIVILEGED_PHONE_STATE permission, has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}), or is a device owner or profile owner that + * has been granted the READ_PHONE_STATE permission. The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile + * owner access is deprecated and will be removed in a future release. * * @return the card string of the SIM card which contains the subscription or an empty string * if these requirements are not met. The card string is the ICCID for UICCs or the EID for diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java index 4a0ca664049a..2df0024bdea9 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java @@ -153,7 +153,14 @@ public class DummyBlobData { public void writeToSession(BlobStoreManager.Session session, long offsetBytes, long lengthBytes) throws Exception { try (FileInputStream in = new FileInputStream(mFile)) { - Utils.writeToSession(session, in, offsetBytes, lengthBytes); + Utils.writeToSession(session, in, offsetBytes, lengthBytes, lengthBytes); + } + } + + public void writeToSession(BlobStoreManager.Session session, + long offsetBytes, long lengthBytes, long allocateBytes) throws Exception { + try (FileInputStream in = new FileInputStream(mFile)) { + Utils.writeToSession(session, in, offsetBytes, lengthBytes, allocateBytes); } } diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java index b9bd661dfd67..ec859955694c 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java @@ -59,15 +59,15 @@ public class Utils { public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input, long lengthBytes) throws IOException { try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) { - writeToSession(session, in, 0, lengthBytes); + writeToSession(session, in, 0, lengthBytes, lengthBytes); } } public static void writeToSession(BlobStoreManager.Session session, FileInputStream in, - long offsetBytes, long lengthBytes) throws IOException { + long offsetBytes, long lengthBytes, long allocateBytes) throws IOException { in.getChannel().position(offsetBytes); try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( - session.openWrite(offsetBytes, lengthBytes))) { + session.openWrite(offsetBytes, allocateBytes))) { copy(in, out, lengthBytes); } } diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp index b1e2487c54fe..e3b6db08c503 100644 --- a/tools/stats_log_api_gen/Android.bp +++ b/tools/stats_log_api_gen/Android.bp @@ -21,7 +21,6 @@ cc_binary_host { name: "stats-log-api-gen", srcs: [ "Collation.cpp", - "atoms_info_writer.cpp", "java_writer.cpp", "java_writer_q.cpp", "main.cpp", diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 958e94efcf9c..a230de46dcf3 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -52,9 +52,7 @@ AtomDecl::AtomDecl(const AtomDecl& that) defaultState(that.defaultState), triggerStateReset(that.triggerStateReset), nested(that.nested), - uidField(that.uidField), - whitelisted(that.whitelisted), - truncateTimestamp(that.truncateTimestamp) { + uidField(that.uidField) { } AtomDecl::AtomDecl(int c, const string& n, const string& m) : code(c), name(n), message(m) { @@ -520,13 +518,6 @@ int collate_atoms(const Descriptor* descriptor, const string& moduleName, Atoms* shared_ptr<AtomDecl> atomDecl = make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name()); - if (atomField->options().GetExtension(os::statsd::allow_from_any_uid) == true) { - atomDecl->whitelisted = true; - if (dbg) { - printf("%s is whitelisted\n", atomField->name().c_str()); - } - } - if (atomDecl->code < PULL_ATOM_START_ID && atomField->options().GetExtension(os::statsd::truncate_timestamp)) { addAnnotationToAtomDecl(atomDecl.get(), ATOM_ID_FIELD_NUMBER, diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index 043f8b1e74d8..10b34ecf5f54 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -164,10 +164,6 @@ struct AtomDecl { int uidField = 0; - bool whitelisted = false; - - bool truncateTimestamp = false; - AtomDecl(); AtomDecl(const AtomDecl& that); AtomDecl(int code, const string& name, const string& message); diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp deleted file mode 100644 index 292cb21bac30..000000000000 --- a/tools/stats_log_api_gen/atoms_info_writer.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "atoms_info_writer.h" - -#include <map> -#include <set> -#include <vector> - -#include "utils.h" - -namespace android { -namespace stats_log_api_gen { - -static void write_atoms_info_header_body(FILE* out) { - fprintf(out, "struct AtomsInfo {\n"); - fprintf(out, " const static std::set<int> kWhitelistedAtoms;\n"); - fprintf(out, "};\n"); -} - -static void write_atoms_info_cpp_body(FILE* out, const Atoms& atoms) { - - fprintf(out, "const std::set<int> AtomsInfo::kWhitelistedAtoms = {\n"); - for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end(); - atomIt++) { - if ((*atomIt)->whitelisted) { - const string constant = make_constant_name((*atomIt)->name); - fprintf(out, " %d, // %s\n", (*atomIt)->code, constant.c_str()); - } - } - - fprintf(out, "};\n"); - fprintf(out, "\n"); - -} - -int write_atoms_info_header(FILE* out, const string& namespaceStr) { - // Print prelude - fprintf(out, "// This file is autogenerated\n"); - fprintf(out, "\n"); - fprintf(out, "#pragma once\n"); - fprintf(out, "\n"); - fprintf(out, "#include <vector>\n"); - fprintf(out, "#include <map>\n"); - fprintf(out, "#include <set>\n"); - fprintf(out, "\n"); - - write_namespace(out, namespaceStr); - - write_atoms_info_header_body(out); - - fprintf(out, "\n"); - write_closing_namespace(out, namespaceStr); - - return 0; -} - -int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr, - const string& importHeader) { - // Print prelude - fprintf(out, "// This file is autogenerated\n"); - fprintf(out, "\n"); - fprintf(out, "#include <%s>\n", importHeader.c_str()); - fprintf(out, "\n"); - - write_namespace(out, namespaceStr); - - write_atoms_info_cpp_body(out, atoms); - - // Print footer - fprintf(out, "\n"); - write_closing_namespace(out, namespaceStr); - - return 0; -} - -} // namespace stats_log_api_gen -} // namespace android diff --git a/tools/stats_log_api_gen/atoms_info_writer.h b/tools/stats_log_api_gen/atoms_info_writer.h deleted file mode 100644 index 09a4303eaee6..000000000000 --- a/tools/stats_log_api_gen/atoms_info_writer.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <stdio.h> -#include <string.h> - -#include "Collation.h" - -namespace android { -namespace stats_log_api_gen { - -using namespace std; - -int write_atoms_info_cpp(FILE* out, const Atoms& atoms, const string& namespaceStr, - const string& importHeader); - -int write_atoms_info_header(FILE* out, const string& namespaceStr); - -} // namespace stats_log_api_gen -} // namespace android diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index 136933b8cfb2..b888ce904b31 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -9,7 +9,6 @@ #include <vector> #include "Collation.h" -#include "atoms_info_writer.h" #include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include "java_writer.h" #include "java_writer_q.h" @@ -30,12 +29,6 @@ static void print_usage() { fprintf(stderr, "OPTIONS\n"); fprintf(stderr, " --cpp FILENAME the header file to output for write helpers\n"); fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n"); - fprintf(stderr, - " --atomsInfoCpp FILENAME the header file to output for " - "statsd metadata\n"); - fprintf(stderr, - " --atomsInfoHeader FILENAME the cpp file to output for statsd " - "metadata\n"); fprintf(stderr, " --help this message\n"); fprintf(stderr, " --java FILENAME the java file to output\n"); fprintf(stderr, " --module NAME optional, module name to generate outputs for\n"); @@ -49,10 +42,6 @@ static void print_usage() { " --importHeader NAME required for cpp/jni to say which header to " "import " "for write helpers\n"); - fprintf(stderr, - " --atomsInfoImportHeader NAME required for cpp to say which " - "header to import " - "for statsd metadata\n"); fprintf(stderr, " --javaPackage PACKAGE the package for the java file.\n"); fprintf(stderr, " required for java with module\n"); fprintf(stderr, " --javaClass CLASS the class name of the java class.\n"); @@ -74,15 +63,12 @@ static int run(int argc, char const* const* argv) { string cppFilename; string headerFilename; string javaFilename; - string atomsInfoCppFilename; - string atomsInfoHeaderFilename; string javaPackage; string javaClass; string moduleName = DEFAULT_MODULE_NAME; string cppNamespace = DEFAULT_CPP_NAMESPACE; string cppHeaderImport = DEFAULT_CPP_HEADER_IMPORT; - string atomsInfoCppHeaderImport = DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT; bool supportQ = false; bool supportWorkSource = false; bool compileQ = false; @@ -148,27 +134,6 @@ static int run(int argc, char const* const* argv) { return 1; } javaClass = argv[index]; - } else if (0 == strcmp("--atomsInfoHeader", argv[index])) { - index++; - if (index >= argc) { - print_usage(); - return 1; - } - atomsInfoHeaderFilename = argv[index]; - } else if (0 == strcmp("--atomsInfoCpp", argv[index])) { - index++; - if (index >= argc) { - print_usage(); - return 1; - } - atomsInfoCppFilename = argv[index]; - } else if (0 == strcmp("--atomsInfoImportHeader", argv[index])) { - index++; - if (index >= argc) { - print_usage(); - return 1; - } - atomsInfoCppHeaderImport = argv[index]; } else if (0 == strcmp("--supportQ", argv[index])) { supportQ = true; } else if (0 == strcmp("--worksource", argv[index])) { @@ -180,8 +145,7 @@ static int run(int argc, char const* const* argv) { index++; } - if (cppFilename.size() == 0 && headerFilename.size() == 0 && javaFilename.size() == 0 && - atomsInfoHeaderFilename.size() == 0 && atomsInfoCppFilename.size() == 0) { + if (cppFilename.size() == 0 && headerFilename.size() == 0 && javaFilename.size() == 0) { print_usage(); return 1; } @@ -210,29 +174,6 @@ static int run(int argc, char const* const* argv) { collate_atom(android::os::statsd::AttributionNode::descriptor(), &attributionDecl, &attributionSignature); - // Write the atoms info .cpp file - if (atomsInfoCppFilename.size() != 0) { - FILE* out = fopen(atomsInfoCppFilename.c_str(), "w"); - if (out == NULL) { - fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoCppFilename.c_str()); - return 1; - } - errorCount = android::stats_log_api_gen::write_atoms_info_cpp(out, atoms, cppNamespace, - atomsInfoCppHeaderImport); - fclose(out); - } - - // Write the atoms info .h file - if (atomsInfoHeaderFilename.size() != 0) { - FILE* out = fopen(atomsInfoHeaderFilename.c_str(), "w"); - if (out == NULL) { - fprintf(stderr, "Unable to open file for write: %s\n", atomsInfoHeaderFilename.c_str()); - return 1; - } - errorCount = android::stats_log_api_gen::write_atoms_info_header(out, cppNamespace); - fclose(out); - } - // Write the .cpp file if (cppFilename.size() != 0) { FILE* out = fopen(cppFilename.c_str(), "w"); diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto index d22acc6e6598..aaa488e44fee 100644 --- a/tools/stats_log_api_gen/test.proto +++ b/tools/stats_log_api_gen/test.proto @@ -187,24 +187,6 @@ message GoodStateAtom3 { optional int32 state = 3 [(android.os.statsd.state_field_option).exclusive_state = true]; } -message WhitelistedAtom { - optional int32 field = 1; -} - -message NonWhitelistedAtom { - optional int32 field = 1; -} - -message ListedAtoms { - oneof event { - // Atoms can be whitelisted i.e. they can be triggered by any source - WhitelistedAtom whitelisted_atom = 1 [(android.os.statsd.allow_from_any_uid) = true]; - // Atoms are not whitelisted by default, so they can only be triggered - // by whitelisted sources - NonWhitelistedAtom non_whitelisted_atom = 2; - } -} - message ModuleOneAtom { optional int32 field = 1 [(android.os.statsd.is_uid) = true]; } diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp index 150475223bfa..dbae58889333 100644 --- a/tools/stats_log_api_gen/test_collation.cpp +++ b/tools/stats_log_api_gen/test_collation.cpp @@ -225,25 +225,6 @@ TEST(CollationTest, FailOnBadBinaryFieldAtom) { EXPECT_TRUE(errorCount > 0); } -TEST(CollationTest, PassOnWhitelistedAtom) { - Atoms atoms; - int errorCount = collate_atoms(ListedAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms); - EXPECT_EQ(errorCount, 0); - EXPECT_EQ(atoms.decls.size(), 2ul); -} - -TEST(CollationTest, RecogniseWhitelistedAtom) { - Atoms atoms; - collate_atoms(ListedAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms); - for (const auto& atomDecl : atoms.decls) { - if (atomDecl->code == 1) { - EXPECT_TRUE(atomDecl->whitelisted); - } else { - EXPECT_FALSE(atomDecl->whitelisted); - } - } -} - TEST(CollationTest, PassOnLogFromModuleAtom) { Atoms atoms; int errorCount = collate_atoms(ModuleAtoms::descriptor(), DEFAULT_MODULE_NAME, &atoms); diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h index 7d6d08ebbcbe..73e0cb838227 100644 --- a/tools/stats_log_api_gen/utils.h +++ b/tools/stats_log_api_gen/utils.h @@ -32,7 +32,6 @@ using namespace std; const string DEFAULT_CPP_NAMESPACE = "android,util"; const string DEFAULT_CPP_HEADER_IMPORT = "statslog.h"; -const string DEFAULT_ATOMS_INFO_CPP_HEADER_IMPORT = "atoms_info.h"; const int JAVA_MODULE_REQUIRES_FLOAT = 0x01; const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1b0497a0ecd5..fb6af5b550b0 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -928,19 +928,26 @@ public class WifiManager { /** * Broadcast intent action indicating that the configured networks changed. - * This can be as a result of adding/updating/deleting a network. If - * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration - * can be retreived with the {@link #EXTRA_WIFI_CONFIGURATION} extra. If multiple - * Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present. + * This can be as a result of adding/updating/deleting a network. + * <br /> + * {@link #EXTRA_CHANGE_REASON} contains whether the configuration was added/changed/removed. + * {@link #EXTRA_WIFI_CONFIGURATION} is never set starting in Android 11. + * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set for backwards compatibility reasons, but + * its value is always true, even if only a single network changed. + * <br /> + * The {@link android.Manifest.permission#ACCESS_WIFI_STATE ACCESS_WIFI_STATE} permission is + * required to receive this broadcast. + * * @hide */ @SystemApi public static final String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE"; /** - * The lookup key for a (@link android.net.wifi.WifiConfiguration} object representing + * The lookup key for a {@link android.net.wifi.WifiConfiguration} object representing * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION} * broadcast is sent. + * Note: this extra is never set starting in Android 11. * @hide */ @SystemApi @@ -948,14 +955,16 @@ public class WifiManager { /** * Multiple network configurations have changed. * @see #CONFIGURED_NETWORKS_CHANGED_ACTION - * + * Note: this extra is always true starting in Android 11. * @hide */ @SystemApi public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; /** * The lookup key for an integer indicating the reason a Wi-Fi network configuration - * has changed. Only present if {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is {@code false} + * has changed. One of {@link #CHANGE_REASON_ADDED}, {@link #CHANGE_REASON_REMOVED}, + * {@link #CHANGE_REASON_CONFIG_CHANGE}. + * * @see #CONFIGURED_NETWORKS_CHANGED_ACTION * @hide */ |