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

Commit 611c5bdb authored by Yuri Lin's avatar Yuri Lin Committed by Automerger Merge Worker
Browse files

Merge "Show "review notification permissions" notification" into tm-dev am: a436e429

parents 4d1e75aa a436e429
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -7056,6 +7056,10 @@
                 android:permission="android.permission.BIND_JOB_SERVICE">
        </service>

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

        <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
            android:exported="false">
            <intent-filter>
+3 −0
Original line number Diff line number Diff line
@@ -42,4 +42,7 @@ public interface NotificationManagerInternal {

    /** Does the specified package/uid have permission to post notifications? */
    boolean areNotificationsEnabledForPackage(String pkg, int uid);

    /** Send a notification to the user prompting them to review their notification permissions. */
    void sendReviewPermissionsNotification();
}
+107 −0
Original line number Diff line number Diff line
@@ -274,6 +274,7 @@ import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
@@ -442,6 +443,18 @@ public class NotificationManagerService extends SystemService {
    private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13);
    // States for the review permissions notification
    static final int REVIEW_NOTIF_STATE_UNKNOWN = -1;
    static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0;
    static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1;
    static final int REVIEW_NOTIF_STATE_DISMISSED = 2;
    static final int REVIEW_NOTIF_STATE_RESHOWN = 3;
    // Action strings for review permissions notification
    static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND";
    static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS";
    static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED";
    /**
     * Apps that post custom toasts in the background will have those blocked. Apps can
     * still post toasts created with
@@ -652,6 +665,9 @@ public class NotificationManagerService extends SystemService {
    private InstanceIdSequence mNotificationInstanceIdSequence;
    private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
    // Broadcast intent receiver for notification permissions review-related intents
    private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
    static class Archive {
        final SparseArray<Boolean> mEnabled;
        final int mBufferSize;
@@ -2416,6 +2432,11 @@ public class NotificationManagerService extends SystemService {
        IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
        getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
        mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver();
        getContext().registerReceiver(mReviewNotificationPermissionsReceiver,
                ReviewNotificationPermissionsReceiver.getFilter(),
                Context.RECEIVER_NOT_EXPORTED);
    }
    /**
@@ -2709,6 +2730,7 @@ public class NotificationManagerService extends SystemService {
            mHistoryManager.onBootPhaseAppsCanStart();
            registerDeviceConfigChange();
            migrateDefaultNAS();
            maybeShowInitialReviewPermissionsNotification();
        } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
        }
@@ -6336,6 +6358,21 @@ public class NotificationManagerService extends SystemService {
        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
            return areNotificationsEnabledForPackageInt(pkg, uid);
        }
        @Override
        public void sendReviewPermissionsNotification() {
            // This method is meant to be called from the JobService upon running the job for this
            // notification having been rescheduled; so without checking any other state, it will
            // send the notification.
            checkCallerIsSystem();
            NotificationManager nm = getContext().getSystemService(NotificationManager.class);
            nm.notify(TAG,
                    SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
                    createReviewPermissionsNotification());
            Settings.Global.putInt(getContext().getContentResolver(),
                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                    NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
        }
    };
    int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -11608,6 +11645,76 @@ public class NotificationManagerService extends SystemService {
        out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
    }
    // Creates a notification that informs the user about changes due to the migration to
    // use permissions for notifications.
    protected Notification createReviewPermissionsNotification() {
        int title = R.string.review_notification_settings_title;
        int content = R.string.review_notification_settings_text;
        // Tapping on the notification leads to the settings screen for managing app notifications,
        // using the intent reserved for system services to indicate it comes from this notification
        Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW);
        Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND);
        Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS);
        Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED);
        // Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver
        final Notification.Action remindMe = new Notification.Action.Builder(null,
                getContext().getResources().getString(
                        R.string.review_notification_settings_remind_me_action),
                PendingIntent.getBroadcast(
                        getContext(), 0, remindIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                .build();
        final Notification.Action dismiss = new Notification.Action.Builder(null,
                getContext().getResources().getString(
                        R.string.review_notification_settings_dismiss),
                PendingIntent.getBroadcast(
                        getContext(), 0, dismissIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                .build();
        return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
                .setSmallIcon(R.drawable.stat_sys_adb)
                .setContentTitle(getContext().getResources().getString(title))
                .setContentText(getContext().getResources().getString(content))
                .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                .setStyle(new Notification.BigTextStyle())
                .setFlag(Notification.FLAG_NO_CLEAR, true)
                .setAutoCancel(true)
                .addAction(remindMe)
                .addAction(dismiss)
                .setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
                .build();
    }
    protected void maybeShowInitialReviewPermissionsNotification() {
        int currentState = Settings.Global.getInt(getContext().getContentResolver(),
                Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                REVIEW_NOTIF_STATE_UNKNOWN);
        // now check the last known state of the notification -- this determination of whether the
        // user is in the correct target audience occurs elsewhere, and will have written the
        // REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future.
        //
        // alternatively, if the user has rescheduled the notification (so it has been shown
        // again) but not yet interacted with the new notification, then show it again on boot,
        // as this state indicates that the user had the notification open before rebooting.
        //
        // sending the notification here does not record a new state for the notification;
        // that will be written by parts of the system further down the line if at any point
        // the user interacts with the notification.
        if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW
                || currentState == REVIEW_NOTIF_STATE_RESHOWN) {
            NotificationManager nm = getContext().getSystemService(NotificationManager.class);
            nm.notify(TAG,
                    SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS,
                    createReviewPermissionsNotification());
        }
    }
    /**
     * Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too
     * aggressive and annoying the user.
+15 −3
Original line number Diff line number Diff line
@@ -96,6 +96,10 @@ public class PreferencesHelper implements RankingConfig {
    private final int XML_VERSION;
    /** What version to check to do the upgrade for bubbles. */
    private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
    /** The first xml version with notification permissions enabled. */
    private static final int XML_VERSION_NOTIF_PERMISSION = 3;
    /** The first xml version that notifies users to review their notification permissions */
    private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
    @VisibleForTesting
    static final int UNKNOWN_UID = UserHandle.USER_NULL;
    private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
@@ -206,7 +210,7 @@ public class PreferencesHelper implements RankingConfig {
        mStatsEventBuilderFactory = statsEventBuilderFactory;

        if (mPermissionHelper.isMigrationEnabled()) {
            XML_VERSION = 3;
            XML_VERSION = 4;
        } else {
            XML_VERSION = 2;
        }
@@ -226,8 +230,16 @@ public class PreferencesHelper implements RankingConfig {

        final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
        boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
        boolean migrateToPermission =
                (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
        boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION)
                && mPermissionHelper.isMigrationEnabled();
        if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) {
            // make a note that we should show the notification at some point.
            // it shouldn't be possible for the user to already have seen it, as the XML version
            // would be newer then.
            Settings.Global.putInt(mContext.getContentResolver(),
                    Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
                    NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
        }
        ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
        synchronized (mPackagePreferences) {
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+79 −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 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 com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;

/**
 * JobService implementation for scheduling the notification informing users about
 * notification permissions updates and taking them to review their existing permissions.
 * @hide
 */
public class ReviewNotificationPermissionsJobService extends JobService {
    public static final String TAG = "ReviewNotificationPermissionsJobService";

    @VisibleForTesting
    protected static final int JOB_ID = 225373531;

    /**
     *  Schedule a new job that will show a notification the specified amount of time in the future.
     */
    public static void scheduleJob(Context context, long rescheduleTimeMillis) {
        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
        // if the job already exists for some reason, cancel & reschedule
        if (jobScheduler.getPendingJob(JOB_ID) != null) {
            jobScheduler.cancel(JOB_ID);
        }
        ComponentName component = new ComponentName(
                context, ReviewNotificationPermissionsJobService.class);
        JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
                .setPersisted(true) // make sure it'll still get rescheduled after reboot
                .setMinimumLatency(rescheduleTimeMillis)  // run after specified amount of time
                .build();
        jobScheduler.schedule(newJob);
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        // While jobs typically should be run on different threads, this
        // job only posts a notification, which is not a long-running operation
        // as notification posting is asynchronous.
        NotificationManagerInternal nmi =
                LocalServices.getService(NotificationManagerInternal.class);
        nmi.sendReviewPermissionsNotification();

        // once the notification is posted, the job is done, so no need to
        // keep it alive afterwards
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // If we're interrupted for some reason, try again (though this may not
        // ever happen due to onStartJob not leaving a job running after being
        // called)
        return true;
    }
}
Loading