Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9f7a3bc8 authored by Lyn's avatar Lyn
Browse files

Job service to expire bitmaps

Bug: 290381858
Test: NotificationTest
Test: NotificationManagerServiceTest
Test: NotificationBitmapJobServiceTest

Test: adb shell device_config put systemui com.android.server.notification.expire_bitmaps true
      send big picture notifications with bitmaps
      wait 24 hours
      => see that bitmaps are removed

Test: do the above with shorter job interval
      => see that removal is silent (no sound, no vibration, no HUN,
         device does not wakeup if dozing)

Change-Id: I06c8a1006fd7030835e41fbf7e06f345e8595150
parent 8b9cd666
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -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);
            }
        }
+4 −0
Original line number Diff line number Diff line
@@ -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>
+46 −0
Original line number Diff line number Diff line
@@ -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")
+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
+2 −0
Original line number Diff line number Diff line
@@ -52,4 +52,6 @@ public interface NotificationManagerInternal {
    void sendReviewPermissionsNotification();

    void cleanupHistoryFiles();

    void removeBitmaps();
}
Loading