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

Commit 08a9e949 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Optimize methods for bypassing apps" into main

parents cb482dd6 16404fb6
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -124,7 +124,7 @@ interface INotificationManager
    boolean onlyHasDefaultChannel(String pkg, int uid);
    boolean areChannelsBypassingDnd();
    ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
    List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels);
    ParceledListSlice getPackagesBypassingDnd(int userId);
    boolean isPackagePaused(String pkg);
    void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
    boolean isPermissionFixed(String pkg, int userId);
+98 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.app;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

import java.util.Objects;

/**
 * @hide
 */
public final class ZenBypassingApp implements Parcelable {

    @NonNull private String mPkg;
    private boolean mAllChannelsBypass;


    public ZenBypassingApp(@NonNull String pkg, boolean allChannelsBypass) {
        mPkg = pkg;
        mAllChannelsBypass = allChannelsBypass;
    }

    public ZenBypassingApp(Parcel source) {
        mPkg = source.readString();
        mAllChannelsBypass = source.readBoolean();
    }

    @NonNull
    public String getPkg() {
        return mPkg;
    }

    public boolean doAllChannelsBypass() {
        return mAllChannelsBypass;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(mPkg);
        dest.writeBoolean(mAllChannelsBypass);
    }

    public static final @android.annotation.NonNull Parcelable.Creator<ZenBypassingApp> CREATOR
            = new Parcelable.Creator<ZenBypassingApp>() {
        @Override
        public ZenBypassingApp createFromParcel(Parcel source) {
            return new ZenBypassingApp(source);
        }
        @Override
        public ZenBypassingApp[] newArray(int size) {
            return new ZenBypassingApp[size];
        }
    };

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ZenBypassingApp)) return false;
        ZenBypassingApp that = (ZenBypassingApp) o;
        return mAllChannelsBypass == that.mAllChannelsBypass && Objects.equals(mPkg,
                that.mPkg);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mPkg, mAllChannelsBypass);
    }

    @Override
    public String toString() {
        return "ZenBypassingApp{" +
                "mPkg='" + mPkg + '\'' +
                ", mAllChannelsBypass=" + mAllChannelsBypass +
                '}';
    }
}
+16 −26
Original line number Diff line number Diff line
@@ -211,6 +211,7 @@ import android.app.RemoteServiceException.BadForegroundServiceNotificationExcept
import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
import android.app.StatsManager;
import android.app.UriGrantsManager;
import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupManager;
import android.app.backup.BackupRestoreEventLogger;
@@ -238,7 +239,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -4043,7 +4043,7 @@ public class NotificationManagerService extends SystemService {
                        "canNotifyAsPackage for uid " + uid);
            }
            return areNotificationsEnabledForPackageInt(pkg, uid);
            return areNotificationsEnabledForPackageInt(uid);
        }
        /**
@@ -4864,30 +4864,20 @@ public class NotificationManagerService extends SystemService {
        }
        @Override
        public List<String> getPackagesBypassingDnd(int userId,
                boolean includeConversationChannels) {
        public ParceledListSlice<ZenBypassingApp> getPackagesBypassingDnd(int userId)
                throws RemoteException {
            checkCallerIsSystem();
            final ArraySet<String> packageNames = new ArraySet<>();
            List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, userId);
            for (PackageInfo pi : pkgs) {
                String pkg = pi.packageName;
                // If any NotificationChannel for this package is bypassing, the
                // package is considered bypassing.
                for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
                        pi.applicationInfo.uid).getList()) {
                    // Skips non-demoted conversation channels.
                    if (!includeConversationChannels
                            && !TextUtils.isEmpty(channel.getConversationId())
                            && !channel.isDemoted()) {
                        continue;
            UserHandle user = UserHandle.of(userId);
            ArrayList<ZenBypassingApp> bypassing =
                    mPreferencesHelper.getPackagesBypassingDnd(userId);
            for (int i = bypassing.size() - 1; i >= 0; i--) {
                String pkg = bypassing.get(i).getPkg();
                if (!areNotificationsEnabledForPackage(pkg, getUidForPackageAndUser(pkg, user))) {
                    bypassing.remove(i);
                }
                    packageNames.add(pkg);
            }
            }
            return new ArrayList<String>(packageNames);
            return new ParceledListSlice<>(bypassing);
        }
        @Override
@@ -7763,7 +7753,7 @@ public class NotificationManagerService extends SystemService {
        @Override
        public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
            return areNotificationsEnabledForPackageInt(pkg, uid);
            return areNotificationsEnabledForPackageInt(uid);
        }
        @Override
@@ -8742,7 +8732,7 @@ public class NotificationManagerService extends SystemService {
        }
        // blocked apps
        boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid);
        boolean isBlocked = !areNotificationsEnabledForPackageInt(uid);
        synchronized (mNotificationLock) {
            isBlocked |= isRecordBlockedLocked(r);
        }
@@ -8792,7 +8782,7 @@ public class NotificationManagerService extends SystemService {
        }
    }
    private boolean areNotificationsEnabledForPackageInt(String pkg, int uid) {
    private boolean areNotificationsEnabledForPackageInt(int uid) {
        return mPermissionHelper.hasPermission(uid);
    }
@@ -9328,7 +9318,7 @@ public class NotificationManagerService extends SystemService {
         * notifying all listeners to a background thread; false otherwise.
         */
        private boolean postNotification() {
            boolean appBanned = !areNotificationsEnabledForPackageInt(pkg, uid);
            boolean appBanned = !areNotificationsEnabledForPackageInt(uid);
            boolean isCallNotification = isCallNotification(pkg, uid);
            boolean posted = false;
            synchronized (NotificationManagerService.this.mNotificationLock) {
+30 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.ZenBypassingApp;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -1949,6 +1950,35 @@ public class PreferencesHelper implements RankingConfig {
        return new ParceledListSlice<>(channels);
    }

    /**
     * Gets all apps that can bypass DND, and a boolean indicating whether all (true) or some
     * (false) of its notification channels can currently bypass.
     */
    public @NonNull ArrayList<ZenBypassingApp> getPackagesBypassingDnd(@UserIdInt int userId) {
        ArrayList<ZenBypassingApp> bypassing = new ArrayList<>();
        synchronized (mLock) {
            for (PackagePreferences p : mPackagePreferences.values()) {
                if (p.userId != userId) {
                    continue;
                }
                int totalChannelCount = p.channels.size();
                int bypassingCount = 0;
                if  (totalChannelCount == 0) {
                    continue;
                }
                for (NotificationChannel channel : p.channels.values()) {
                    if (channelIsLiveLocked(p, channel) && channel.canBypassDnd()) {
                        bypassingCount++;
                    }
                }
                if (bypassingCount > 0) {
                    bypassing.add(new ZenBypassingApp(p.pkg, totalChannelCount == bypassingCount));
                }
            }
        }
        return bypassing;
    }

    /**
     * True for pre-O apps that only have the default channel, or pre O apps that have no
     * channels yet. This method will create the default channel for pre-O apps that don't have it.
+33 −115
Original line number Diff line number Diff line
@@ -210,6 +210,7 @@ import android.app.Person;
import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
import android.app.ZenBypassingApp;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.backup.BackupRestoreEventLogger;
import android.app.job.JobScheduler;
@@ -13173,6 +13174,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        assertFalse(captor.getValue().isPackageAllowed(new VersionedPackage("test", 1002)));
    }
    @Test
    public void getPackagesBypassingDnd_blocked()
            throws RemoteException, PackageManager.NameNotFoundException {
        NotificationChannel channel1 = new NotificationChannel("id1", "name1",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel2 = new NotificationChannel("id3", "name3",
                NotificationManager.IMPORTANCE_MAX);
        NotificationChannel channel3 = new NotificationChannel("id4", "name3",
                NotificationManager.IMPORTANCE_MAX);
        channel1.setBypassDnd(true);
        channel2.setBypassDnd(true);
        channel3.setBypassDnd(false);
        // has DND access, so can set bypassDnd attribute
        mService.mPreferencesHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
                /*has DND access*/ true, UID_N_MR1, false);
        mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel2, true, true,
                UID_P, false);
        mService.mPreferencesHelper.createNotificationChannel(PKG_P, UID_P, channel3, true, true,
                UID_P, false);
        when(mPackageManager.getPackageUid(eq(PKG_P), anyLong(), anyInt())).thenReturn(UID_P);
        when(mPackageManager.getPackageUid(eq(PKG_N_MR1), anyLong(), anyInt()))
                .thenReturn(UID_N_MR1);
        when(mPermissionHelper.hasPermission(UID_N_MR1)).thenReturn(false);
        when(mPermissionHelper.hasPermission(UID_P)).thenReturn(true);
        assertThat(mBinderService.getPackagesBypassingDnd(UserHandle.getUserId(UID_P)).getList())
                .containsExactly(new ZenBypassingApp(PKG_P, false));
    }
    @Test
    public void testGetNotificationChannelsBypassingDnd_blocked() throws RemoteException {
        mService.setPreferencesHelper(mPreferencesHelper);
@@ -13187,124 +13219,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
    @Test
    public void testGetPackagesBypassingDnd_empty() throws RemoteException {
        mService.setPreferencesHelper(mPreferencesHelper);
        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId).getList();
        assertThat(result).isEmpty();
    }
    @Test
    public void testGetPackagesBypassingDnd_excludeConversationChannels() throws RemoteException {
        mService.setPreferencesHelper(mPreferencesHelper);
        // Set packages
        PackageInfo pkg0 = new PackageInfo();
        pkg0.packageName = "pkg0";
        pkg0.applicationInfo = new ApplicationInfo();
        pkg0.applicationInfo.uid = mUid;
        PackageInfo pkg1 = new PackageInfo();
        pkg1.packageName = "pkg1";
        pkg1.applicationInfo = new ApplicationInfo();
        pkg1.applicationInfo.uid = mUid;
        PackageInfo pkg2 = new PackageInfo();
        pkg2.packageName = "pkg2";
        pkg2.applicationInfo = new ApplicationInfo();
        pkg2.applicationInfo.uid = mUid;
        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
                .thenReturn(List.of(pkg0, pkg1, pkg2));
        // Conversation channels
        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
                NotificationManager.IMPORTANCE_HIGH);
        nc0.setConversationId("parentChannel", "conversationId");
        // Demoted conversation channel
        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
                NotificationManager.IMPORTANCE_HIGH);
        nc1.setConversationId("parentChannel", "conversationId");
        nc1.setDemoted(true);
        // Non-conversation channels
        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
                NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
                NotificationManager.IMPORTANCE_HIGH);
        ParceledListSlice<NotificationChannel> pls0 =
                new ParceledListSlice(ImmutableList.of(nc0));
        ParceledListSlice<NotificationChannel> pls1 =
                new ParceledListSlice(ImmutableList.of(nc1));
        ParceledListSlice<NotificationChannel> pls2 =
                new ParceledListSlice(ImmutableList.of(nc2, nc3));
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
                .thenReturn(pls0);
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
                .thenReturn(pls1);
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
                .thenReturn(pls2);
        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, false);
        assertThat(result).containsExactly("pkg1", "pkg2");
    }
    @Test
    public void testGetPackagesBypassingDnd_includeConversationChannels() throws RemoteException {
        mService.setPreferencesHelper(mPreferencesHelper);
        // Set packages
        PackageInfo pkg0 = new PackageInfo();
        pkg0.packageName = "pkg0";
        pkg0.applicationInfo = new ApplicationInfo();
        pkg0.applicationInfo.uid = mUid;
        PackageInfo pkg1 = new PackageInfo();
        pkg1.packageName = "pkg1";
        pkg1.applicationInfo = new ApplicationInfo();
        pkg1.applicationInfo.uid = mUid;
        PackageInfo pkg2 = new PackageInfo();
        pkg2.packageName = "pkg2";
        pkg2.applicationInfo = new ApplicationInfo();
        pkg2.applicationInfo.uid = mUid;
        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
                .thenReturn(List.of(pkg0, pkg1, pkg2));
        // Conversation channels
        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
                NotificationManager.IMPORTANCE_HIGH);
        nc0.setConversationId("parentChannel", "conversationId");
        // Demoted conversation channel
        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
                NotificationManager.IMPORTANCE_HIGH);
        nc1.setConversationId("parentChannel", "conversationId");
        nc1.setDemoted(true);
        // Non-conversation channels
        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
                NotificationManager.IMPORTANCE_HIGH);
        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
                NotificationManager.IMPORTANCE_HIGH);
        ParceledListSlice<NotificationChannel> pls0 =
                new ParceledListSlice(ImmutableList.of(nc0));
        ParceledListSlice<NotificationChannel> pls1 =
                new ParceledListSlice(ImmutableList.of(nc1));
        ParceledListSlice<NotificationChannel> pls2 =
                new ParceledListSlice(ImmutableList.of(nc2, nc3));
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
                .thenReturn(pls0);
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
                .thenReturn(pls1);
        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
                .thenReturn(pls2);
        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
        assertThat(result).containsExactly("pkg0", "pkg1", "pkg2");
    }
    @Test
    public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
        // set the testable NMS to not system uid/appid
Loading