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

Commit 1e2cfcb8 authored by Julia Reynolds's avatar Julia Reynolds Committed by Automerger Merge Worker
Browse files

Merge "Clean up history files via job vs multiple alarms" into tm-qpr-dev am:...

Merge "Clean up history files via job vs multiple alarms" into tm-qpr-dev am: f552a187 am: d1c19c2b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19263269



Change-Id: I76fc5e15bcb0e1773e8b7a27af3ec2b41250ee69
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents f784c768 d1c19c2b
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -7081,6 +7081,10 @@
                 android:permission="android.permission.BIND_JOB_SERVICE">
        </service>

        <service android:name="com.android.server.notification.NotificationHistoryJobService"
                 android:permission="android.permission.BIND_JOB_SERVICE" >
        </service>

        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
            android:exported="false">
            <intent-filter>
+12 −85
Original line number Diff line number Diff line
@@ -16,15 +16,8 @@

package com.android.server.notification;

import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Slog;
@@ -61,18 +54,9 @@ public class NotificationHistoryDatabase {
    private static final String TAG = "NotiHistoryDatabase";
    private static final boolean DEBUG = NotificationManagerService.DBG;
    private static final int HISTORY_RETENTION_DAYS = 1;
    private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000;
    private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20;
    private static final long INVALID_FILE_TIME_MS = -1;

    private static final String ACTION_HISTORY_DELETION =
            NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP";
    private static final int REQUEST_CODE_DELETION = 1;
    private static final String SCHEME_DELETION = "delete";
    private static final String EXTRA_KEY = "key";

    private final Context mContext;
    private final AlarmManager mAlarmManager;
    private final Object mLock = new Object();
    private final Handler mFileWriteHandler;
    @VisibleForTesting
@@ -88,9 +72,7 @@ public class NotificationHistoryDatabase {
    @VisibleForTesting
    NotificationHistory mBuffer;

    public NotificationHistoryDatabase(Context context, Handler fileWriteHandler, File dir) {
        mContext = context;
        mAlarmManager = context.getSystemService(AlarmManager.class);
    public NotificationHistoryDatabase(Handler fileWriteHandler, File dir) {
        mCurrentVersion = DEFAULT_CURRENT_VERSION;
        mFileWriteHandler = fileWriteHandler;
        mVersionFile = new File(dir, "version");
@@ -98,11 +80,6 @@ public class NotificationHistoryDatabase {
        mHistoryFiles = new ArrayList<>();
        mBuffer = new NotificationHistory();
        mWriteBufferRunnable = new WriteBufferRunnable();

        IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
        deletionFilter.addDataScheme(SCHEME_DELETION);
        mContext.registerReceiver(mFileCleanupReceiver, deletionFilter,
                Context.RECEIVER_EXPORTED_UNAUDITED);
    }

    public void init() {
@@ -118,7 +95,7 @@ public class NotificationHistoryDatabase {

            checkVersionAndBuildLocked();
            indexFilesLocked();
            prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
            prune();
        }
    }

@@ -247,7 +224,14 @@ public class NotificationHistoryDatabase {
    }

    /**
     * Remove any files that are too old and schedule jobs to clean up the rest
     * Remove any files that are too old.
     */
    void prune() {
        prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
    }

    /**
     * Remove any files that are too old.
     */
    void prune(final int retentionDays, final long currentTimeMillis) {
        synchronized (mLock) {
@@ -266,10 +250,6 @@ public class NotificationHistoryDatabase {

                if (creationTime <= retentionBoundary.getTimeInMillis()) {
                    deleteFile(currentOldestFile);
                } else {
                    // all remaining files are newer than the cut off; schedule jobs to delete
                    scheduleDeletion(
                            currentOldestFile.getBaseFile(), creationTime, retentionDays);
                }
            }
        }
@@ -307,26 +287,6 @@ public class NotificationHistoryDatabase {
        removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
    }

    private void scheduleDeletion(File file, long creationTime, int retentionDays) {
        final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
        scheduleDeletion(file, deletionTime);
    }

    private void scheduleDeletion(File file, long deletionTime) {
        if (DEBUG) {
            Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime);
        }
        final PendingIntent pi = PendingIntent.getBroadcast(mContext,
                REQUEST_CODE_DELETION,
                new Intent(ACTION_HISTORY_DELETION)
                        .setData(new Uri.Builder().scheme(SCHEME_DELETION)
                                .appendPath(file.getAbsolutePath()).build())
                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
                        .putExtra(EXTRA_KEY, file.getAbsolutePath()),
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi);
    }

    private void writeLocked(AtomicFile file, NotificationHistory notifications)
            throws IOException {
        FileOutputStream fos = file.startWrite();
@@ -356,12 +316,6 @@ public class NotificationHistoryDatabase {
        }
    }

    public void unregisterFileCleanupReceiver() {
        if(mContext != null) {
            mContext.unregisterReceiver(mFileCleanupReceiver);
        }
    }

    private static long safeParseLong(String fileName) {
        // AtomicFile will create copies of the numeric files with ".new" and ".bak"
        // over the course of its processing. If these files still exist on boot we need to clean
@@ -373,40 +327,15 @@ public class NotificationHistoryDatabase {
        }
    }

    private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action == null) {
                return;
            }
            if (ACTION_HISTORY_DELETION.equals(action)) {
                try {
                    synchronized (mLock) {
                        final String filePath = intent.getStringExtra(EXTRA_KEY);
                        AtomicFile fileToDelete = new AtomicFile(new File(filePath));
                        if (DEBUG) {
                            Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
                        }
                        fileToDelete.delete();
                        removeFilePathFromHistory(filePath);
                    }
                } catch (Exception e) {
                    Slog.e(TAG, "Failed to delete notification history file", e);
                }
            }
        }
    };

    final class WriteBufferRunnable implements Runnable {

        @Override
        public void run() {
            long time = System.currentTimeMillis();
            run(time, new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
            run(new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
        }

        void run(long time, AtomicFile file) {
        void run(AtomicFile file) {
            synchronized (mLock) {
                if (DEBUG) Slog.d(TAG, "WriteBufferRunnable "
                        + file.getBaseFile().getAbsolutePath());
@@ -414,8 +343,6 @@ public class NotificationHistoryDatabase {
                    writeLocked(file, mBuffer);
                    mHistoryFiles.add(0, file);
                    mBuffer = new NotificationHistory();

                    scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS);
                } catch (IOException e) {
                    Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e);
                }
+1 −1
Original line number Diff line number Diff line
@@ -35,6 +35,6 @@ public class NotificationHistoryDatabaseFactory {
        if(sTestingNotificationHistoryDb != null) {
            return sTestingNotificationHistoryDb;
        }
        return new NotificationHistoryDatabase(context, handler, rootDir);
        return new NotificationHistoryDatabase(handler, rootDir);
    }
}
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.server.LocalServices;

import java.util.concurrent.TimeUnit;

/**
 * This service runs every twenty minutes to ensure the retention policy for notification history
 * data.
 */
public class NotificationHistoryJobService extends JobService {
    private final static String TAG = "NotificationHistoryJob";
    private static final long JOB_RUN_INTERVAL = TimeUnit.MINUTES.toMillis(20);

    static final int BASE_JOB_ID = 237039804;

    static void scheduleJob(Context context) {
        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
        if (jobScheduler.getPendingJob(BASE_JOB_ID) == null) {
            ComponentName component =
                    new ComponentName(context, NotificationHistoryJobService.class);
            JobInfo newJob = new JobInfo.Builder(BASE_JOB_ID, component)
                    .setRequiresDeviceIdle(false)
                    .setPeriodic(JOB_RUN_INTERVAL)
                    .build();
            if (jobScheduler.schedule(newJob) != RESULT_SUCCESS) {
                Slog.w(TAG, "Failed to schedule history cleanup job");
            }
        }
    }

    private CancellationSignal mSignal;

    @Override
    public boolean onStartJob(JobParameters params) {
        mSignal = new CancellationSignal();
        new Thread(() -> {
            NotificationManagerInternal nmInternal =
                    LocalServices.getService(NotificationManagerInternal.class);
            nmInternal.cleanupHistoryFiles();
            jobFinished(params, mSignal.isCanceled());
        }).start();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        if (mSignal != null) {
            mSignal.cancel();
        }
        return false;
    }
}
+24 −1
Original line number Diff line number Diff line
@@ -84,6 +84,12 @@ public class NotificationHistoryManager {
    }

    void onBootPhaseAppsCanStart() {
        try {
            Slog.d("julia", "trying to schedule job");
            NotificationHistoryJobService.scheduleJob(mContext);
        } catch (Throwable e) {
            Slog.e(TAG, "Failed to schedule cleanup job", e);
        }
        mSettingsObserver.observe();
    }

@@ -151,6 +157,24 @@ public class NotificationHistoryManager {
        }
    }

    public void cleanupHistoryFiles() {
        synchronized (mLock) {
            int n = mUserUnlockedStates.size();
            for (int i = 0;  i < n; i++) {
                // cleanup old files for currently unlocked users. User are additionally cleaned
                // on unlock in NotificationHistoryDatabase.init().
                if (mUserUnlockedStates.valueAt(i)) {
                    final NotificationHistoryDatabase userHistory =
                            mUserState.get(mUserUnlockedStates.keyAt(i));
                    if (userHistory == null) {
                        continue;
                    }
                    userHistory.prune();
                }
            }
        }
    }

    public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
        synchronized (mLock) {
            int userId = UserHandle.getUserId(uid);
@@ -288,7 +312,6 @@ public class NotificationHistoryManager {

    private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) {
        userHistory.disableHistory();
        userHistory.unregisterFileCleanupReceiver();

        mUserPendingHistoryDisables.put(userId, false);
        mHistoryEnabled.put(userId, false);
Loading