Loading packages/SystemUI/res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -2333,4 +2333,7 @@ <!-- Name for device services grouping system uid apps in Ongoing Privacy Dialog [CHAR_LIMIT=NONE] --> <string name="device_services">Device Services</string> <!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] --> <string name="music_controls_no_title">No title</string> </resources> packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +134 −50 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.keyguard; import android.annotation.AnyThread; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; Loading @@ -24,14 +25,20 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Typeface; import android.graphics.drawable.Icon; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; import android.media.MediaMetadata; import android.net.Uri; import android.os.Handler; import android.os.Trace; import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.StyleSpan; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; Loading @@ -43,7 +50,9 @@ import androidx.slice.builders.SliceAction; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; Loading @@ -58,13 +67,17 @@ import java.util.concurrent.TimeUnit; * Simple Slice provider that shows the current date. */ public class KeyguardSliceProvider extends SliceProvider implements NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback { NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback, NotificationMediaManager.MediaListener { private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD); public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; public static final String KEYGUARD_NEXT_ALARM_URI = "content://com.android.systemui.keyguard/alarm"; public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd"; public static final String KEYGUARD_MEDIA_URI = "content://com.android.systemui.keyguard/media"; public static final String KEYGUARD_ACTION_URI = "content://com.android.systemui.keyguard/action"; Loading @@ -80,6 +93,7 @@ public class KeyguardSliceProvider extends SliceProvider implements protected final Uri mDateUri; protected final Uri mAlarmUri; protected final Uri mDndUri; protected final Uri mMediaUri; private final Date mCurrentTime = new Date(); private final Handler mHandler; private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm; Loading @@ -94,6 +108,8 @@ public class KeyguardSliceProvider extends SliceProvider implements protected ContentResolver mContentResolver; private AlarmManager.AlarmClockInfo mNextAlarmInfo; private PendingIntent mPendingIntent; protected NotificationMediaManager mMediaManager; protected MediaMetadata mMediaMetaData; /** * Receiver responsible for time ticking and updating the date format. Loading @@ -104,9 +120,13 @@ public class KeyguardSliceProvider extends SliceProvider implements public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_DATE_CHANGED.equals(action)) { mHandler.post(KeyguardSliceProvider.this::updateClock); synchronized (this) { updateClockLocked(); } } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); synchronized (this) { cleanDateFormatLocked(); } } } }; Loading @@ -116,12 +136,16 @@ public class KeyguardSliceProvider extends SliceProvider implements new KeyguardUpdateMonitorCallback() { @Override public void onTimeChanged() { mHandler.post(KeyguardSliceProvider.this::updateClock); synchronized (this) { updateClockLocked(); } } @Override public void onTimeZoneChanged(TimeZone timeZone) { mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); synchronized (this) { cleanDateFormatLocked(); } } }; Loading @@ -140,22 +164,62 @@ public class KeyguardSliceProvider extends SliceProvider implements mDateUri = Uri.parse(KEYGUARD_DATE_URI); mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); mDndUri = Uri.parse(KEYGUARD_DND_URI); mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI); } public void initDependencies() { mMediaManager = Dependency.get(NotificationMediaManager.class); mMediaManager.addCallback(this); } @AnyThread @Override public Slice onBindSlice(Uri sliceUri) { Trace.beginSection("KeyguardSliceProvider#onBindSlice"); Slice slice; synchronized (this) { ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); if (mMediaMetaData != null) { addMediaLocked(builder); } else { builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); addNextAlarm(builder); addZenMode(builder); addPrimaryAction(builder); Slice slice = builder.build(); addNextAlarmLocked(builder); addZenModeLocked(builder); } addPrimaryActionLocked(builder); slice = builder.build(); } Trace.endSection(); return slice; } protected void addPrimaryAction(ListBuilder builder) { protected void addMediaLocked(ListBuilder listBuilder) { if (mMediaMetaData != null) { SpannableStringBuilder builder = new SpannableStringBuilder(); CharSequence title = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_TITLE); if (TextUtils.isEmpty(title)) { title = getContext().getResources().getString(R.string.music_controls_no_title); } builder.append(title); builder.setSpan(BOLD_STYLE, 0, title.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); CharSequence album = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_ARTIST); if (!TextUtils.isEmpty(album)) { builder.append(" ").append(album); } RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder); Icon notificationIcon = mMediaManager.getMediaIcon(); if (notificationIcon != null) { IconCompat icon = IconCompat.createFromIcon(notificationIcon); mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE); } listBuilder.addRow(mediaBuilder); } } protected void addPrimaryActionLocked(ListBuilder builder) { // Add simple action because API requires it; Keyguard handles presenting // its own slices so this action + icon are actually never used. IconCompat icon = IconCompat.createWithResource(getContext(), Loading @@ -167,7 +231,7 @@ public class KeyguardSliceProvider extends SliceProvider implements builder.addRow(primaryActionRow); } protected void addNextAlarm(ListBuilder builder) { protected void addNextAlarmLocked(ListBuilder builder) { if (TextUtils.isEmpty(mNextAlarm)) { return; } Loading @@ -183,7 +247,7 @@ public class KeyguardSliceProvider extends SliceProvider implements * Add zen mode (DND) icon to slice if it's enabled. * @param builder The slice builder. */ protected void addZenMode(ListBuilder builder) { protected void addZenModeLocked(ListBuilder builder) { if (!isDndOn()) { return; } Loading Loading @@ -214,22 +278,23 @@ public class KeyguardSliceProvider extends SliceProvider implements mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0); KeyguardSliceProvider.sInstance = this; registerClockUpdate(); updateClock(); updateClockLocked(); return true; } @Override public void onZenChanged(int zen) { mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } @Override public void onConfigChanged(ZenModeConfig config) { mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } private void updateNextAlarm() { if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { synchronized (this) { if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { String pattern = android.text.format.DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm"; mNextAlarm = android.text.format.DateFormat.format(pattern, Loading @@ -237,10 +302,11 @@ public class KeyguardSliceProvider extends SliceProvider implements } else { mNextAlarm = ""; } mContentResolver.notifyChange(mSliceUri, null /* observer */); } notifyChange(); } private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { private boolean withinNHoursLocked(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { if (alarmClockInfo == null) { return false; } Loading @@ -254,6 +320,7 @@ public class KeyguardSliceProvider extends SliceProvider implements * changing the date/time via the settings app. */ private void registerClockUpdate() { synchronized (this) { if (mRegistered) { return; } Loading @@ -266,21 +333,24 @@ public class KeyguardSliceProvider extends SliceProvider implements getKeyguardUpdateMonitor().registerCallback(mKeyguardUpdateMonitorCallback); mRegistered = true; } } @VisibleForTesting boolean isRegistered() { synchronized (this) { return mRegistered; } } protected void updateClock() { final String text = getFormattedDate(); protected void updateClockLocked() { final String text = getFormattedDateLocked(); if (!text.equals(mLastText)) { mLastText = text; mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } } protected String getFormattedDate() { protected String getFormattedDateLocked() { if (mDateFormat == null) { final Locale l = Locale.getDefault(); DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); Loading @@ -292,12 +362,13 @@ public class KeyguardSliceProvider extends SliceProvider implements } @VisibleForTesting void cleanDateFormat() { void cleanDateFormatLocked() { mDateFormat = null; } @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { synchronized (this) { mNextAlarmInfo = nextAlarm; mAlarmManager.cancel(mUpdateNextAlarm); Loading @@ -307,6 +378,7 @@ public class KeyguardSliceProvider extends SliceProvider implements mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm", mUpdateNextAlarm, mHandler); } } updateNextAlarm(); } Loading @@ -314,4 +386,16 @@ public class KeyguardSliceProvider extends SliceProvider implements protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { return KeyguardUpdateMonitor.getInstance(getContext()); } @Override public void onMetadataChanged(MediaMetadata metadata) { synchronized (this) { mMediaMetaData = metadata; } notifyChange(); } protected void notifyChange() { mContentResolver.notifyChange(mSliceUri, null /* observer */); } } packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +50 −16 Original line number Diff line number Diff line Loading @@ -25,11 +25,10 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; Loading @@ -47,6 +46,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; Loading Loading @@ -94,14 +94,11 @@ public class NotificationMediaManager implements Dumpable { @Nullable private LockscreenWallpaper mLockscreenWallpaper; protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); protected final PorterDuffXfermode mSrcOverXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); private final Handler mHandler = Dependency.get(MAIN_HANDLER); private final Context mContext; private final MediaSessionManager mMediaSessionManager; private final ArrayList<MediaListener> mMediaListeners; protected NotificationPresenter mPresenter; private MediaController mMediaController; Loading @@ -122,7 +119,7 @@ public class NotificationMediaManager implements Dumpable { if (state != null) { if (!isPlaybackActive(state.getState())) { clearCurrentMediaNotification(); mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } } } Loading @@ -134,7 +131,7 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } mMediaMetadata = metadata; mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } }; Loading Loading @@ -164,6 +161,7 @@ public class NotificationMediaManager implements Dumpable { @Inject public NotificationMediaManager(Context context) { mContext = context; mMediaListeners = new ArrayList<>(); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); // TODO: use MediaSessionManager.SessionListener to hook us up to future updates Loading @@ -177,7 +175,7 @@ public class NotificationMediaManager implements Dumpable { public void onNotificationRemoved(String key) { if (key.equals(mMediaNotificationKey)) { clearCurrentMediaNotification(); mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */); } } Loading @@ -189,20 +187,44 @@ public class NotificationMediaManager implements Dumpable { return mMediaMetadata; } public Icon getMediaIcon() { if (mMediaNotificationKey == null) { return null; } NotificationEntryManager manager = getEntryManager(); synchronized (manager.getNotificationData()) { Entry entry = manager.getNotificationData().get(mMediaNotificationKey); if (entry == null || entry.expandedIcon == null) { return null; } return entry.expandedIcon.getSourceIcon(); } } public void addCallback(MediaListener callback) { mMediaListeners.add(callback); callback.onMetadataChanged(mMediaMetadata); } public void removeCallback(MediaListener callback) { mMediaListeners.remove(callback); } public void findAndUpdateMediaNotifications() { boolean metaDataChanged = false; NotificationEntryManager manager = getEntryManager(); synchronized (manager.getNotificationData()) { ArrayList<NotificationData.Entry> activeNotifications = manager ArrayList<Entry> activeNotifications = manager .getNotificationData().getActiveNotifications(); final int N = activeNotifications.size(); // Promote the media notification with a controller in 'playing' state, if any. NotificationData.Entry mediaNotification = null; Entry mediaNotification = null; MediaController controller = null; for (int i = 0; i < N; i++) { final NotificationData.Entry entry = activeNotifications.get(i); final Entry entry = activeNotifications.get(i); if (entry.isMediaNotification()) { final MediaSession.Token token = Loading Loading @@ -242,7 +264,7 @@ public class NotificationMediaManager implements Dumpable { final String pkg = aController.getPackageName(); for (int i = 0; i < N; i++) { final NotificationData.Entry entry = activeNotifications.get(i); final Entry entry = activeNotifications.get(i); if (entry.notification.getPackageName().equals(pkg)) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: found controller matching " Loading Loading @@ -286,9 +308,7 @@ public class NotificationMediaManager implements Dumpable { getEntryManager().updateNotifications(); } if (mPresenter != null) { mPresenter.updateMediaMetaData(metaDataChanged, true); } dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); } public void clearCurrentMediaNotification() { Loading @@ -296,6 +316,16 @@ public class NotificationMediaManager implements Dumpable { clearCurrentMediaNotificationSession(); } private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) { if (mPresenter != null) { mPresenter.updateMediaMetaData(changed, allowEnterAnimation); } ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).onMetadataChanged(mMediaMetadata); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mMediaSessionManager="); Loading Loading @@ -552,4 +582,8 @@ public class NotificationMediaManager implements Dumpable { mBackdropFront.setImageDrawable(null); } }; public interface MediaListener { void onMetadataChanged(MediaMetadata metadata); } } packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +7 −0 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ import com.android.systemui.doze.DozeReceiver; import com.android.systemui.doze.LockScreenWakeUpController; import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; Loading Loading @@ -637,6 +638,12 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBarController = Dependency.get(DisplayNavigationBarController.class); mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance(); if (sliceProvider != null) { sliceProvider.initDependencies(); } else { Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies"); } mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); Loading packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +32 −6 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import android.app.AlarmManager; import android.content.ContentResolver; import android.media.MediaMetadata; import android.net.Uri; import android.provider.Settings; import android.support.test.filters.SmallTest; Loading @@ -42,6 +43,7 @@ import androidx.slice.core.SliceQuery; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationMediaManager; import org.junit.Assert; import org.junit.Before; Loading @@ -63,6 +65,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { private ContentResolver mContentResolver; @Mock private AlarmManager mAlarmManager; @Mock private NotificationMediaManager mNotificationMediaManager; private TestableKeyguardSliceProvider mProvider; private boolean mIsZenMode; Loading @@ -72,6 +76,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mIsZenMode = false; mProvider = new TestableKeyguardSliceProvider(); mProvider.attachInfo(getContext(), null); mProvider.initDependencies(); SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST))); } Loading @@ -90,6 +95,16 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { Assert.assertNotNull("Slice must provide a title.", text); } @Test public void onBindSlice_readsMedia() { MediaMetadata metadata = mock(MediaMetadata.class); mProvider.onMetadataChanged(metadata); mProvider.onBindSlice(mProvider.getUri()); verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE)); verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST)); verify(mNotificationMediaManager).getMediaIcon(); } @Test public void cleansDateFormat() { mProvider.mKeyguardUpdateMonitorCallback.onTimeZoneChanged(null); Loading Loading @@ -136,14 +151,20 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { public void addZenMode_addedToSlice() { ListBuilder listBuilder = spy(new ListBuilder(getContext(), mProvider.getUri(), ListBuilder.INFINITY)); mProvider.addZenMode(listBuilder); mProvider.addZenModeLocked(listBuilder); verify(listBuilder, never()).addRow(any(ListBuilder.RowBuilder.class)); mIsZenMode = true; mProvider.addZenMode(listBuilder); mProvider.addZenModeLocked(listBuilder); verify(listBuilder).addRow(any(ListBuilder.RowBuilder.class)); } @Test public void onMetadataChanged_updatesSlice() { mProvider.onMetadataChanged(mock(MediaMetadata.class)); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); } private class TestableKeyguardSliceProvider extends KeyguardSliceProvider { int mCleanDateFormatInvokations; private int mCounter; Loading @@ -166,8 +187,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @Override void cleanDateFormat() { super.cleanDateFormat(); void cleanDateFormatLocked() { super.cleanDateFormatLocked(); mCleanDateFormatInvokations++; } Loading @@ -177,8 +198,13 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @Override protected String getFormattedDate() { return super.getFormattedDate() + mCounter++; protected String getFormattedDateLocked() { return super.getFormattedDateLocked() + mCounter++; } @Override public void initDependencies() { mMediaManager = mNotificationMediaManager; } } Loading Loading
packages/SystemUI/res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -2333,4 +2333,7 @@ <!-- Name for device services grouping system uid apps in Ongoing Privacy Dialog [CHAR_LIMIT=NONE] --> <string name="device_services">Device Services</string> <!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] --> <string name="music_controls_no_title">No title</string> </resources>
packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +134 −50 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.keyguard; import android.annotation.AnyThread; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; Loading @@ -24,14 +25,20 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Typeface; import android.graphics.drawable.Icon; import android.icu.text.DateFormat; import android.icu.text.DisplayContext; import android.media.MediaMetadata; import android.net.Uri; import android.os.Handler; import android.os.Trace; import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.StyleSpan; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; Loading @@ -43,7 +50,9 @@ import androidx.slice.builders.SliceAction; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; Loading @@ -58,13 +67,17 @@ import java.util.concurrent.TimeUnit; * Simple Slice provider that shows the current date. */ public class KeyguardSliceProvider extends SliceProvider implements NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback { NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback, NotificationMediaManager.MediaListener { private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD); public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main"; public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date"; public static final String KEYGUARD_NEXT_ALARM_URI = "content://com.android.systemui.keyguard/alarm"; public static final String KEYGUARD_DND_URI = "content://com.android.systemui.keyguard/dnd"; public static final String KEYGUARD_MEDIA_URI = "content://com.android.systemui.keyguard/media"; public static final String KEYGUARD_ACTION_URI = "content://com.android.systemui.keyguard/action"; Loading @@ -80,6 +93,7 @@ public class KeyguardSliceProvider extends SliceProvider implements protected final Uri mDateUri; protected final Uri mAlarmUri; protected final Uri mDndUri; protected final Uri mMediaUri; private final Date mCurrentTime = new Date(); private final Handler mHandler; private final AlarmManager.OnAlarmListener mUpdateNextAlarm = this::updateNextAlarm; Loading @@ -94,6 +108,8 @@ public class KeyguardSliceProvider extends SliceProvider implements protected ContentResolver mContentResolver; private AlarmManager.AlarmClockInfo mNextAlarmInfo; private PendingIntent mPendingIntent; protected NotificationMediaManager mMediaManager; protected MediaMetadata mMediaMetaData; /** * Receiver responsible for time ticking and updating the date format. Loading @@ -104,9 +120,13 @@ public class KeyguardSliceProvider extends SliceProvider implements public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (Intent.ACTION_DATE_CHANGED.equals(action)) { mHandler.post(KeyguardSliceProvider.this::updateClock); synchronized (this) { updateClockLocked(); } } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); synchronized (this) { cleanDateFormatLocked(); } } } }; Loading @@ -116,12 +136,16 @@ public class KeyguardSliceProvider extends SliceProvider implements new KeyguardUpdateMonitorCallback() { @Override public void onTimeChanged() { mHandler.post(KeyguardSliceProvider.this::updateClock); synchronized (this) { updateClockLocked(); } } @Override public void onTimeZoneChanged(TimeZone timeZone) { mHandler.post(KeyguardSliceProvider.this::cleanDateFormat); synchronized (this) { cleanDateFormatLocked(); } } }; Loading @@ -140,22 +164,62 @@ public class KeyguardSliceProvider extends SliceProvider implements mDateUri = Uri.parse(KEYGUARD_DATE_URI); mAlarmUri = Uri.parse(KEYGUARD_NEXT_ALARM_URI); mDndUri = Uri.parse(KEYGUARD_DND_URI); mMediaUri = Uri.parse(KEYGUARD_MEDIA_URI); } public void initDependencies() { mMediaManager = Dependency.get(NotificationMediaManager.class); mMediaManager.addCallback(this); } @AnyThread @Override public Slice onBindSlice(Uri sliceUri) { Trace.beginSection("KeyguardSliceProvider#onBindSlice"); Slice slice; synchronized (this) { ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); if (mMediaMetaData != null) { addMediaLocked(builder); } else { builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); addNextAlarm(builder); addZenMode(builder); addPrimaryAction(builder); Slice slice = builder.build(); addNextAlarmLocked(builder); addZenModeLocked(builder); } addPrimaryActionLocked(builder); slice = builder.build(); } Trace.endSection(); return slice; } protected void addPrimaryAction(ListBuilder builder) { protected void addMediaLocked(ListBuilder listBuilder) { if (mMediaMetaData != null) { SpannableStringBuilder builder = new SpannableStringBuilder(); CharSequence title = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_TITLE); if (TextUtils.isEmpty(title)) { title = getContext().getResources().getString(R.string.music_controls_no_title); } builder.append(title); builder.setSpan(BOLD_STYLE, 0, title.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); CharSequence album = mMediaMetaData.getText(MediaMetadata.METADATA_KEY_ARTIST); if (!TextUtils.isEmpty(album)) { builder.append(" ").append(album); } RowBuilder mediaBuilder = new RowBuilder(mMediaUri).setTitle(builder); Icon notificationIcon = mMediaManager.getMediaIcon(); if (notificationIcon != null) { IconCompat icon = IconCompat.createFromIcon(notificationIcon); mediaBuilder.addEndItem(icon, ListBuilder.ICON_IMAGE); } listBuilder.addRow(mediaBuilder); } } protected void addPrimaryActionLocked(ListBuilder builder) { // Add simple action because API requires it; Keyguard handles presenting // its own slices so this action + icon are actually never used. IconCompat icon = IconCompat.createWithResource(getContext(), Loading @@ -167,7 +231,7 @@ public class KeyguardSliceProvider extends SliceProvider implements builder.addRow(primaryActionRow); } protected void addNextAlarm(ListBuilder builder) { protected void addNextAlarmLocked(ListBuilder builder) { if (TextUtils.isEmpty(mNextAlarm)) { return; } Loading @@ -183,7 +247,7 @@ public class KeyguardSliceProvider extends SliceProvider implements * Add zen mode (DND) icon to slice if it's enabled. * @param builder The slice builder. */ protected void addZenMode(ListBuilder builder) { protected void addZenModeLocked(ListBuilder builder) { if (!isDndOn()) { return; } Loading Loading @@ -214,22 +278,23 @@ public class KeyguardSliceProvider extends SliceProvider implements mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0); KeyguardSliceProvider.sInstance = this; registerClockUpdate(); updateClock(); updateClockLocked(); return true; } @Override public void onZenChanged(int zen) { mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } @Override public void onConfigChanged(ZenModeConfig config) { mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } private void updateNextAlarm() { if (withinNHours(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { synchronized (this) { if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) { String pattern = android.text.format.DateFormat.is24HourFormat(getContext(), ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm"; mNextAlarm = android.text.format.DateFormat.format(pattern, Loading @@ -237,10 +302,11 @@ public class KeyguardSliceProvider extends SliceProvider implements } else { mNextAlarm = ""; } mContentResolver.notifyChange(mSliceUri, null /* observer */); } notifyChange(); } private boolean withinNHours(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { private boolean withinNHoursLocked(AlarmManager.AlarmClockInfo alarmClockInfo, int hours) { if (alarmClockInfo == null) { return false; } Loading @@ -254,6 +320,7 @@ public class KeyguardSliceProvider extends SliceProvider implements * changing the date/time via the settings app. */ private void registerClockUpdate() { synchronized (this) { if (mRegistered) { return; } Loading @@ -266,21 +333,24 @@ public class KeyguardSliceProvider extends SliceProvider implements getKeyguardUpdateMonitor().registerCallback(mKeyguardUpdateMonitorCallback); mRegistered = true; } } @VisibleForTesting boolean isRegistered() { synchronized (this) { return mRegistered; } } protected void updateClock() { final String text = getFormattedDate(); protected void updateClockLocked() { final String text = getFormattedDateLocked(); if (!text.equals(mLastText)) { mLastText = text; mContentResolver.notifyChange(mSliceUri, null /* observer */); notifyChange(); } } protected String getFormattedDate() { protected String getFormattedDateLocked() { if (mDateFormat == null) { final Locale l = Locale.getDefault(); DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l); Loading @@ -292,12 +362,13 @@ public class KeyguardSliceProvider extends SliceProvider implements } @VisibleForTesting void cleanDateFormat() { void cleanDateFormatLocked() { mDateFormat = null; } @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { synchronized (this) { mNextAlarmInfo = nextAlarm; mAlarmManager.cancel(mUpdateNextAlarm); Loading @@ -307,6 +378,7 @@ public class KeyguardSliceProvider extends SliceProvider implements mAlarmManager.setExact(AlarmManager.RTC, triggerAt, "lock_screen_next_alarm", mUpdateNextAlarm, mHandler); } } updateNextAlarm(); } Loading @@ -314,4 +386,16 @@ public class KeyguardSliceProvider extends SliceProvider implements protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() { return KeyguardUpdateMonitor.getInstance(getContext()); } @Override public void onMetadataChanged(MediaMetadata metadata) { synchronized (this) { mMediaMetaData = metadata; } notifyChange(); } protected void notifyChange() { mContentResolver.notifyChange(mSliceUri, null /* observer */); } }
packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +50 −16 Original line number Diff line number Diff line Loading @@ -25,11 +25,10 @@ import android.annotation.Nullable; import android.app.Notification; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; Loading @@ -47,6 +46,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData.Entry; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.BiometricUnlockController; import com.android.systemui.statusbar.phone.LockscreenWallpaper; Loading Loading @@ -94,14 +94,11 @@ public class NotificationMediaManager implements Dumpable { @Nullable private LockscreenWallpaper mLockscreenWallpaper; protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); protected final PorterDuffXfermode mSrcOverXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); private final Handler mHandler = Dependency.get(MAIN_HANDLER); private final Context mContext; private final MediaSessionManager mMediaSessionManager; private final ArrayList<MediaListener> mMediaListeners; protected NotificationPresenter mPresenter; private MediaController mMediaController; Loading @@ -122,7 +119,7 @@ public class NotificationMediaManager implements Dumpable { if (state != null) { if (!isPlaybackActive(state.getState())) { clearCurrentMediaNotification(); mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } } } Loading @@ -134,7 +131,7 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } mMediaMetadata = metadata; mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } }; Loading Loading @@ -164,6 +161,7 @@ public class NotificationMediaManager implements Dumpable { @Inject public NotificationMediaManager(Context context) { mContext = context; mMediaListeners = new ArrayList<>(); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); // TODO: use MediaSessionManager.SessionListener to hook us up to future updates Loading @@ -177,7 +175,7 @@ public class NotificationMediaManager implements Dumpable { public void onNotificationRemoved(String key) { if (key.equals(mMediaNotificationKey)) { clearCurrentMediaNotification(); mPresenter.updateMediaMetaData(true, true); dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */); } } Loading @@ -189,20 +187,44 @@ public class NotificationMediaManager implements Dumpable { return mMediaMetadata; } public Icon getMediaIcon() { if (mMediaNotificationKey == null) { return null; } NotificationEntryManager manager = getEntryManager(); synchronized (manager.getNotificationData()) { Entry entry = manager.getNotificationData().get(mMediaNotificationKey); if (entry == null || entry.expandedIcon == null) { return null; } return entry.expandedIcon.getSourceIcon(); } } public void addCallback(MediaListener callback) { mMediaListeners.add(callback); callback.onMetadataChanged(mMediaMetadata); } public void removeCallback(MediaListener callback) { mMediaListeners.remove(callback); } public void findAndUpdateMediaNotifications() { boolean metaDataChanged = false; NotificationEntryManager manager = getEntryManager(); synchronized (manager.getNotificationData()) { ArrayList<NotificationData.Entry> activeNotifications = manager ArrayList<Entry> activeNotifications = manager .getNotificationData().getActiveNotifications(); final int N = activeNotifications.size(); // Promote the media notification with a controller in 'playing' state, if any. NotificationData.Entry mediaNotification = null; Entry mediaNotification = null; MediaController controller = null; for (int i = 0; i < N; i++) { final NotificationData.Entry entry = activeNotifications.get(i); final Entry entry = activeNotifications.get(i); if (entry.isMediaNotification()) { final MediaSession.Token token = Loading Loading @@ -242,7 +264,7 @@ public class NotificationMediaManager implements Dumpable { final String pkg = aController.getPackageName(); for (int i = 0; i < N; i++) { final NotificationData.Entry entry = activeNotifications.get(i); final Entry entry = activeNotifications.get(i); if (entry.notification.getPackageName().equals(pkg)) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: found controller matching " Loading Loading @@ -286,9 +308,7 @@ public class NotificationMediaManager implements Dumpable { getEntryManager().updateNotifications(); } if (mPresenter != null) { mPresenter.updateMediaMetaData(metaDataChanged, true); } dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */); } public void clearCurrentMediaNotification() { Loading @@ -296,6 +316,16 @@ public class NotificationMediaManager implements Dumpable { clearCurrentMediaNotificationSession(); } private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) { if (mPresenter != null) { mPresenter.updateMediaMetaData(changed, allowEnterAnimation); } ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners); for (int i = 0; i < callbacks.size(); i++) { callbacks.get(i).onMetadataChanged(mMediaMetadata); } } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mMediaSessionManager="); Loading Loading @@ -552,4 +582,8 @@ public class NotificationMediaManager implements Dumpable { mBackdropFront.setImageDrawable(null); } }; public interface MediaListener { void onMetadataChanged(MediaMetadata metadata); } }
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +7 −0 Original line number Diff line number Diff line Loading @@ -151,6 +151,7 @@ import com.android.systemui.doze.DozeReceiver; import com.android.systemui.doze.LockScreenWakeUpController; import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; Loading Loading @@ -637,6 +638,12 @@ public class StatusBar extends SystemUI implements DemoMode, mNavigationBarController = Dependency.get(DisplayNavigationBarController.class); mBubbleController = Dependency.get(BubbleController.class); mBubbleController.setExpandListener(mBubbleExpandListener); KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance(); if (sliceProvider != null) { sliceProvider.initDependencies(); } else { Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies"); } mColorExtractor.addOnColorsChangedListener(this); mStatusBarStateController.addCallback(this, StatusBarStateController.RANK_STATUS_BAR); Loading
packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +32 −6 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import android.app.AlarmManager; import android.content.ContentResolver; import android.media.MediaMetadata; import android.net.Uri; import android.provider.Settings; import android.support.test.filters.SmallTest; Loading @@ -42,6 +43,7 @@ import androidx.slice.core.SliceQuery; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationMediaManager; import org.junit.Assert; import org.junit.Before; Loading @@ -63,6 +65,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { private ContentResolver mContentResolver; @Mock private AlarmManager mAlarmManager; @Mock private NotificationMediaManager mNotificationMediaManager; private TestableKeyguardSliceProvider mProvider; private boolean mIsZenMode; Loading @@ -72,6 +76,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { mIsZenMode = false; mProvider = new TestableKeyguardSliceProvider(); mProvider.attachInfo(getContext(), null); mProvider.initDependencies(); SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST))); } Loading @@ -90,6 +95,16 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { Assert.assertNotNull("Slice must provide a title.", text); } @Test public void onBindSlice_readsMedia() { MediaMetadata metadata = mock(MediaMetadata.class); mProvider.onMetadataChanged(metadata); mProvider.onBindSlice(mProvider.getUri()); verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_TITLE)); verify(metadata).getText(eq(MediaMetadata.METADATA_KEY_ARTIST)); verify(mNotificationMediaManager).getMediaIcon(); } @Test public void cleansDateFormat() { mProvider.mKeyguardUpdateMonitorCallback.onTimeZoneChanged(null); Loading Loading @@ -136,14 +151,20 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { public void addZenMode_addedToSlice() { ListBuilder listBuilder = spy(new ListBuilder(getContext(), mProvider.getUri(), ListBuilder.INFINITY)); mProvider.addZenMode(listBuilder); mProvider.addZenModeLocked(listBuilder); verify(listBuilder, never()).addRow(any(ListBuilder.RowBuilder.class)); mIsZenMode = true; mProvider.addZenMode(listBuilder); mProvider.addZenModeLocked(listBuilder); verify(listBuilder).addRow(any(ListBuilder.RowBuilder.class)); } @Test public void onMetadataChanged_updatesSlice() { mProvider.onMetadataChanged(mock(MediaMetadata.class)); verify(mContentResolver).notifyChange(eq(mProvider.getUri()), eq(null)); } private class TestableKeyguardSliceProvider extends KeyguardSliceProvider { int mCleanDateFormatInvokations; private int mCounter; Loading @@ -166,8 +187,8 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @Override void cleanDateFormat() { super.cleanDateFormat(); void cleanDateFormatLocked() { super.cleanDateFormatLocked(); mCleanDateFormatInvokations++; } Loading @@ -177,8 +198,13 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @Override protected String getFormattedDate() { return super.getFormattedDate() + mCounter++; protected String getFormattedDateLocked() { return super.getFormattedDateLocked() + mCounter++; } @Override public void initDependencies() { mMediaManager = mNotificationMediaManager; } } Loading