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