Loading core/java/android/app/Notification.java +9 −6 Original line number Diff line number Diff line Loading @@ -7670,14 +7670,17 @@ public class Notification implements Parcelable } extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); // If the icon contains a bitmap, use the old extra so that listeners which look for // that extra can still find the picture. Don't include the new extra in that case, // to avoid duplicating data. if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { if (mPictureIcon == null) { extras.remove(EXTRA_PICTURE_ICON); extras.remove(EXTRA_PICTURE); } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) { // If the icon contains a bitmap, use the old extra so that listeners which look // for that extra can still find the picture. Don't include the new extra in // that case, to avoid duplicating data. extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); extras.putParcelable(EXTRA_PICTURE_ICON, null); extras.remove(EXTRA_PICTURE_ICON); } else { extras.putParcelable(EXTRA_PICTURE, null); extras.remove(EXTRA_PICTURE); extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); } } Loading core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -8242,6 +8242,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.notification.NotificationBitmapJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.healthconnect.HealthConnectDailyService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> Loading core/tests/coretests/src/android/app/NotificationTest.java +46 −0 Original line number Diff line number Diff line Loading @@ -1230,6 +1230,52 @@ public class NotificationTest { assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2)); } @Test public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() { Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(bitmap); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() { Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(icon); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); } @Test public void testMessagingChange_text() { Notification.Builder nM1 = new Notification.Builder(mContext, "test") Loading services/core/java/com/android/server/notification/NotificationBitmapJobService.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.job.JobScheduler.RESULT_SUCCESS; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.CancellationSignal; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import java.util.Calendar; /** * This service runs everyday at 2am local time to remove expired bitmaps. */ public class NotificationBitmapJobService extends JobService { static final String TAG = "NotificationBitmapJob"; static final int BASE_JOB_ID = 290381858; // Feature bug id static void scheduleJob(Context context) { if (context == null) { return; } try { JobScheduler jobScheduler = context.getSystemService(JobScheduler.class).forNamespace(TAG); ComponentName component = new ComponentName(context, NotificationBitmapJobService.class); JobInfo jobInfo = new JobInfo.Builder(BASE_JOB_ID, component) .setRequiresDeviceIdle(true) .setMinimumLatency(getRunAfterMs()) .build(); final int result = jobScheduler.schedule(jobInfo); if (result != RESULT_SUCCESS) { Slog.e(TAG, "Failed to schedule bitmap removal job"); } } catch (Throwable e) { Slog.wtf(TAG, "Failed bitmap removal job", e); } } /** * @return Milliseconds until the next time the job should run. */ private static long getRunAfterMs() { Calendar cal = Calendar.getInstance(); // Uses local time zone final long now = cal.getTimeInMillis(); cal.set(Calendar.HOUR_OF_DAY, 2); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MILLISECOND, 0); final long today2AM = cal.getTimeInMillis(); cal.add(Calendar.DAY_OF_YEAR, 1); final long tomorrow2AM = cal.getTimeInMillis(); return getTimeUntilRemoval(now, today2AM, tomorrow2AM); } @VisibleForTesting static long getTimeUntilRemoval(long now, long today2AM, long tomorrow2AM) { if (now < today2AM) { return today2AM - now; } return tomorrow2AM - now; } @Override public boolean onStartJob(JobParameters params) { new Thread(() -> { NotificationManagerInternal nmInternal = LocalServices.getService(NotificationManagerInternal.class); nmInternal.removeBitmaps(); // Schedule the next job here, since we cannot use setPeriodic and setMinimumLatency // together when creating JobInfo. scheduleJob(/* context= */ this); jobFinished(params, /* wantsReschedule= */ false); }).start(); return true; // This service continues to run in a separate thread. } @Override public boolean onStopJob(JobParameters params) { // No need to keep this job alive since the original thread is going to keep running and // call scheduleJob at the end of its execution. return false; } @Override @VisibleForTesting protected void attachBaseContext(Context base) { super.attachBaseContext(base); } } No newline at end of file services/core/java/com/android/server/notification/NotificationManagerInternal.java +2 −0 Original line number Diff line number Diff line Loading @@ -52,4 +52,6 @@ public interface NotificationManagerInternal { void sendReviewPermissionsNotification(); void cleanupHistoryFiles(); void removeBitmaps(); } Loading
core/java/android/app/Notification.java +9 −6 Original line number Diff line number Diff line Loading @@ -7670,14 +7670,17 @@ public class Notification implements Parcelable } extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); // If the icon contains a bitmap, use the old extra so that listeners which look for // that extra can still find the picture. Don't include the new extra in that case, // to avoid duplicating data. if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { if (mPictureIcon == null) { extras.remove(EXTRA_PICTURE_ICON); extras.remove(EXTRA_PICTURE); } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) { // If the icon contains a bitmap, use the old extra so that listeners which look // for that extra can still find the picture. Don't include the new extra in // that case, to avoid duplicating data. extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); extras.putParcelable(EXTRA_PICTURE_ICON, null); extras.remove(EXTRA_PICTURE_ICON); } else { extras.putParcelable(EXTRA_PICTURE, null); extras.remove(EXTRA_PICTURE); extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); } } Loading
core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -8242,6 +8242,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.notification.NotificationBitmapJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.healthconnect.HealthConnectDailyService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> Loading
core/tests/coretests/src/android/app/NotificationTest.java +46 −0 Original line number Diff line number Diff line Loading @@ -1230,6 +1230,52 @@ public class NotificationTest { assertTrue(Notification.areStyledNotificationsVisiblyDifferent(nBigPic1, nBigPic2)); } @Test public void testBigPictureStyle_setExtras_pictureIconNull_noPictureIconKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconNull_noPictureKey() { Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture((Bitmap) null); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconTypeBitmap_noPictureIconKey() { Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(bitmap); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE_ICON)).isFalse(); } @Test public void testBigPictureStyle_setExtras_pictureIconTypeIcon_noPictureKey() { Icon icon = Icon.createWithResource(mContext, R.drawable.btn_plus); Notification.BigPictureStyle bpStyle = new Notification.BigPictureStyle(); bpStyle.bigPicture(icon); Bundle extras = new Bundle(); bpStyle.addExtras(extras); assertThat(extras.containsKey(EXTRA_PICTURE)).isFalse(); } @Test public void testMessagingChange_text() { Notification.Builder nM1 = new Notification.Builder(mContext, "test") Loading
services/core/java/com/android/server/notification/NotificationBitmapJobService.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.job.JobScheduler.RESULT_SUCCESS; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.CancellationSignal; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import java.util.Calendar; /** * This service runs everyday at 2am local time to remove expired bitmaps. */ public class NotificationBitmapJobService extends JobService { static final String TAG = "NotificationBitmapJob"; static final int BASE_JOB_ID = 290381858; // Feature bug id static void scheduleJob(Context context) { if (context == null) { return; } try { JobScheduler jobScheduler = context.getSystemService(JobScheduler.class).forNamespace(TAG); ComponentName component = new ComponentName(context, NotificationBitmapJobService.class); JobInfo jobInfo = new JobInfo.Builder(BASE_JOB_ID, component) .setRequiresDeviceIdle(true) .setMinimumLatency(getRunAfterMs()) .build(); final int result = jobScheduler.schedule(jobInfo); if (result != RESULT_SUCCESS) { Slog.e(TAG, "Failed to schedule bitmap removal job"); } } catch (Throwable e) { Slog.wtf(TAG, "Failed bitmap removal job", e); } } /** * @return Milliseconds until the next time the job should run. */ private static long getRunAfterMs() { Calendar cal = Calendar.getInstance(); // Uses local time zone final long now = cal.getTimeInMillis(); cal.set(Calendar.HOUR_OF_DAY, 2); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.MILLISECOND, 0); final long today2AM = cal.getTimeInMillis(); cal.add(Calendar.DAY_OF_YEAR, 1); final long tomorrow2AM = cal.getTimeInMillis(); return getTimeUntilRemoval(now, today2AM, tomorrow2AM); } @VisibleForTesting static long getTimeUntilRemoval(long now, long today2AM, long tomorrow2AM) { if (now < today2AM) { return today2AM - now; } return tomorrow2AM - now; } @Override public boolean onStartJob(JobParameters params) { new Thread(() -> { NotificationManagerInternal nmInternal = LocalServices.getService(NotificationManagerInternal.class); nmInternal.removeBitmaps(); // Schedule the next job here, since we cannot use setPeriodic and setMinimumLatency // together when creating JobInfo. scheduleJob(/* context= */ this); jobFinished(params, /* wantsReschedule= */ false); }).start(); return true; // This service continues to run in a separate thread. } @Override public boolean onStopJob(JobParameters params) { // No need to keep this job alive since the original thread is going to keep running and // call scheduleJob at the end of its execution. return false; } @Override @VisibleForTesting protected void attachBaseContext(Context base) { super.attachBaseContext(base); } } No newline at end of file
services/core/java/com/android/server/notification/NotificationManagerInternal.java +2 −0 Original line number Diff line number Diff line Loading @@ -52,4 +52,6 @@ public interface NotificationManagerInternal { void sendReviewPermissionsNotification(); void cleanupHistoryFiles(); void removeBitmaps(); }