• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.Flags;
20 import android.app.NotificationManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.ComponentInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ServiceInfo;
28 import android.graphics.drawable.Drawable;
29 import android.service.notification.ConditionProviderService;
30 
31 import androidx.annotation.Nullable;
32 import androidx.annotation.VisibleForTesting;
33 import androidx.annotation.WorkerThread;
34 import androidx.preference.Preference;
35 
36 import com.android.settings.R;
37 import com.android.settings.core.BasePreferenceController;
38 import com.android.settingslib.Utils;
39 
40 import com.google.common.collect.ImmutableList;
41 import com.google.common.collect.ImmutableSet;
42 import com.google.common.util.concurrent.ListeningExecutorService;
43 import com.google.common.util.concurrent.MoreExecutors;
44 
45 import java.util.ArrayList;
46 import java.util.Comparator;
47 import java.util.List;
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.ExecutorService;
50 import java.util.concurrent.Executors;
51 import java.util.function.Function;
52 
53 class ZenModesListAddModePreferenceController extends BasePreferenceController {
54     private final ZenServiceListing mServiceListing;
55     private final OnAddModeListener mOnAddModeListener;
56 
57     private final ConfigurationActivityHelper mConfigurationActivityHelper;
58     private final NotificationManager mNotificationManager;
59     private final PackageManager mPackageManager;
60     private final Function<ApplicationInfo, Drawable> mAppIconRetriever;
61     private final ListeningExecutorService mBackgroundExecutor;
62     private final Executor mUiThreadExecutor;
63 
ModeType(String name, Drawable icon, @Nullable String summary, @Nullable Intent creationActivityIntent)64     record ModeType(String name, Drawable icon, @Nullable String summary,
65                     @Nullable Intent creationActivityIntent) { }
66 
67     interface OnAddModeListener {
onAvailableModeTypesForAdd(List<ModeType> types)68         void onAvailableModeTypesForAdd(List<ModeType> types);
69     }
70 
ZenModesListAddModePreferenceController(Context context, String key, OnAddModeListener onAddModeListener)71     ZenModesListAddModePreferenceController(Context context, String key,
72             OnAddModeListener onAddModeListener) {
73         this(context, key, onAddModeListener, new ZenServiceListing(context),
74                 new ConfigurationActivityHelper(context.getPackageManager()),
75                 context.getSystemService(NotificationManager.class), context.getPackageManager(),
76                 applicationInfo -> Utils.getBadgedIcon(context, applicationInfo),
77                 Executors.newCachedThreadPool(), context.getMainExecutor());
78     }
79 
80     @VisibleForTesting
ZenModesListAddModePreferenceController(Context context, String key, OnAddModeListener onAddModeListener, ZenServiceListing serviceListing, ConfigurationActivityHelper configurationActivityHelper, NotificationManager notificationManager, PackageManager packageManager, Function<ApplicationInfo, Drawable> appIconRetriever, ExecutorService backgroundExecutor, Executor uiThreadExecutor)81     ZenModesListAddModePreferenceController(Context context, String key,
82             OnAddModeListener onAddModeListener, ZenServiceListing serviceListing,
83             ConfigurationActivityHelper configurationActivityHelper,
84             NotificationManager notificationManager, PackageManager packageManager,
85             Function<ApplicationInfo, Drawable> appIconRetriever,
86             ExecutorService backgroundExecutor, Executor uiThreadExecutor) {
87         super(context, key);
88         mOnAddModeListener = onAddModeListener;
89         mServiceListing = serviceListing;
90         mConfigurationActivityHelper = configurationActivityHelper;
91         mNotificationManager = notificationManager;
92         mPackageManager = packageManager;
93         mAppIconRetriever = appIconRetriever;
94         mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor);
95         mUiThreadExecutor = uiThreadExecutor;
96     }
97 
98     @Override
getAvailabilityStatus()99     public int getAvailabilityStatus() {
100         return Flags.modesUi() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
101     }
102 
103     @Override
updateState(Preference preference)104     public void updateState(Preference preference) {
105         preference.setOnPreferenceClickListener(pref -> {
106             onClickAddMode();
107             return true;
108         });
109     }
110 
111     @VisibleForTesting
onClickAddMode()112     void onClickAddMode() {
113         FutureUtil.whenDone(
114                 mBackgroundExecutor.submit(this::getModeProviders),
115                 mOnAddModeListener::onAvailableModeTypesForAdd,
116                 mUiThreadExecutor);
117     }
118 
119     @WorkerThread
getModeProviders()120     private ImmutableList<ModeType> getModeProviders() {
121         ImmutableSet<ComponentInfo> approvedComponents = mServiceListing.loadApprovedComponents();
122 
123         ArrayList<ModeType> appProvidedModes = new ArrayList<>();
124         for (ComponentInfo ci: approvedComponents) {
125             ModeType modeType = getValidNewModeTypeFromComponent(ci);
126             if (modeType != null) {
127                 appProvidedModes.add(modeType);
128             }
129         }
130 
131         return ImmutableList.<ModeType>builder()
132                 .add(new ModeType(
133                         mContext.getString(R.string.zen_mode_new_option_custom),
134                         mContext.getDrawable(R.drawable.ic_zen_mode_new_option_custom),
135                         null, null))
136                 .addAll(appProvidedModes.stream()
137                         .sorted(Comparator.comparing(ModeType::name))
138                         .toList())
139                 .build();
140     }
141 
142     /**
143      * Returns a {@link ModeType} object corresponding to the approved {@link ComponentInfo} that
144      * specifies a creatable rule, if such a mode can actually be created (has an associated and
145      * enabled configuration activity, has not exceeded the rule instance limit, etc). Otherwise,
146      * returns {@code null}.
147      */
148     @WorkerThread
149     @Nullable
getValidNewModeTypeFromComponent(ComponentInfo ci)150     private ModeType getValidNewModeTypeFromComponent(ComponentInfo ci) {
151         if (ci.metaData == null) {
152             return null;
153         }
154 
155         String ruleType = (ci instanceof ServiceInfo)
156                 ? ci.metaData.getString(ConditionProviderService.META_DATA_RULE_TYPE)
157                 : ci.metaData.getString(NotificationManager.META_DATA_AUTOMATIC_RULE_TYPE);
158         if (ruleType == null || ruleType.trim().isEmpty()) {
159             return null;
160         }
161 
162         int ruleInstanceLimit = (ci instanceof ServiceInfo)
163                 ? ci.metaData.getInt(ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1)
164                 : ci.metaData.getInt(NotificationManager.META_DATA_RULE_INSTANCE_LIMIT, -1);
165         if (ruleInstanceLimit > 0 && mNotificationManager.getRuleInstanceCount(
166                 ci.getComponentName()) >= ruleInstanceLimit) {
167             return null; // Would exceed instance limit.
168         }
169 
170         ComponentName configurationActivity =
171                 mConfigurationActivityHelper.getConfigurationActivityFromApprovedComponent(ci);
172         if (configurationActivity == null) {
173             return null;
174         }
175 
176         String appName = ci.applicationInfo.loadLabel(mPackageManager).toString();
177         Drawable appIcon = mAppIconRetriever.apply(ci.applicationInfo);
178         Intent configActivityIntent = new Intent().setComponent(configurationActivity);
179         return new ModeType(ruleType, appIcon, appName, configActivityIntent);
180     }
181 }
182