1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.notification.modes;
18 
19 import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
20 
21 import android.content.Context;
22 import android.os.Bundle;
23 import android.util.ArraySet;
24 
25 import androidx.annotation.NonNull;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.core.text.BidiFormatter;
28 import androidx.fragment.app.Fragment;
29 import androidx.preference.Preference;
30 
31 import com.android.settings.core.SubSettingLauncher;
32 import com.android.settings.notification.NotificationBackend;
33 import com.android.settingslib.applications.ApplicationsState;
34 
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 
41 /**
42  * Preference with a link and summary about what apps can break through the mode
43  */
44 class ZenModeAppsLinkPreferenceController extends AbstractZenModePreferenceController {
45 
46     private static final String TAG = "ZenModeAppsLinkPreferenceController";
47 
48     private final ZenModeSummaryHelper mSummaryHelper;
49     private ApplicationsState.Session mAppSession;
50     private NotificationBackend mNotificationBackend = new NotificationBackend();
51     private ZenMode mZenMode;
52     private Preference mPreference;
53 
ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host, ApplicationsState applicationsState, ZenModesBackend backend)54     ZenModeAppsLinkPreferenceController(Context context, String key, Fragment host,
55             ApplicationsState applicationsState, ZenModesBackend backend) {
56         super(context, key, backend);
57         mSummaryHelper = new ZenModeSummaryHelper(mContext, mBackend);
58         if (applicationsState != null && host != null) {
59             mAppSession = applicationsState.newSession(mAppSessionCallbacks, host.getLifecycle());
60         }
61     }
62 
63     @Override
updateState(Preference preference, @NonNull ZenMode zenMode)64     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
65         Bundle bundle = new Bundle();
66         bundle.putString(MODE_ID, zenMode.getId());
67         // TODO(b/332937635): Update metrics category
68         preference.setIntent(new SubSettingLauncher(mContext)
69                 .setDestination(ZenModeAppsFragment.class.getName())
70                 .setSourceMetricsCategory(0)
71                 .setArguments(bundle)
72                 .toIntent());
73         mZenMode = zenMode;
74         mPreference = preference;
75         triggerUpdateAppsBypassingDndSummaryText();
76     }
77 
triggerUpdateAppsBypassingDndSummaryText()78     private void triggerUpdateAppsBypassingDndSummaryText() {
79         if (mAppSession == null) {
80             return;
81         }
82 
83         ApplicationsState.AppFilter filter = android.multiuser.Flags.enablePrivateSpaceFeatures()
84                 && android.multiuser.Flags.handleInterleavedSettingsForPrivateSpace()
85                 ? ApplicationsState.FILTER_ENABLED_NOT_QUIET
86                 : ApplicationsState.FILTER_ALL_ENABLED;
87         // We initiate a rebuild in the background here. Once the rebuild is completed,
88         // the onRebuildComplete() callback will be invoked, which will trigger the summary text
89         // to be initialized.
90         mAppSession.rebuild(filter, ApplicationsState.ALPHA_COMPARATOR, false);
91     }
92 
updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps)93     private void updateAppsBypassingDndSummaryText(List<ApplicationsState.AppEntry> apps) {
94         Set<String> appNames = getAppsBypassingDnd(apps);
95         mPreference.setSummary(mSummaryHelper.getAppsSummary(mZenMode, appNames));
96     }
97 
98     @VisibleForTesting
getAppsBypassingDnd(@onNull List<ApplicationsState.AppEntry> apps)99     ArraySet<String> getAppsBypassingDnd(@NonNull List<ApplicationsState.AppEntry> apps) {
100         ArraySet<String> appsBypassingDnd = new ArraySet<>();
101 
102         Map<String, String> pkgLabelMap = new HashMap<String, String>();
103         for (ApplicationsState.AppEntry entry : apps) {
104             if (entry.info != null) {
105                 pkgLabelMap.put(entry.info.packageName, entry.label);
106             }
107         }
108         for (String pkg : mNotificationBackend.getPackagesBypassingDnd(mContext.getUserId(),
109                 /* includeConversationChannels= */ false)) {
110             // Settings may hide some packages from the user, so if they're not present here
111             // we skip displaying them, even if they bypass dnd.
112             if (pkgLabelMap.get(pkg) == null) {
113                 continue;
114             }
115             appsBypassingDnd.add(BidiFormatter.getInstance().unicodeWrap(pkgLabelMap.get(pkg)));
116         }
117         return appsBypassingDnd;
118     }
119 
120     @VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks =
121             new ApplicationsState.Callbacks() {
122 
123                 @Override
124                 public void onRunningStateChanged(boolean running) { }
125 
126                 @Override
127                 public void onPackageListChanged() {
128                     triggerUpdateAppsBypassingDndSummaryText();
129                 }
130 
131                 @Override
132                 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
133                     updateAppsBypassingDndSummaryText(apps);
134                 }
135 
136                 @Override
137                 public void onPackageIconChanged() { }
138 
139                 @Override
140                 public void onPackageSizeChanged(String packageName) { }
141 
142                 @Override
143                 public void onAllSizesComputed() { }
144 
145                 @Override
146                 public void onLauncherInfoChanged() { }
147 
148                 @Override
149                 public void onLoadEntriesCompleted() {
150                     triggerUpdateAppsBypassingDndSummaryText();
151                 }
152             };
153 }
154