1 /* 2 * Copyright (C) 2015 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.zen; 18 19 import android.app.AutomaticZenRule; 20 import android.app.Dialog; 21 import android.app.Flags; 22 import android.app.NotificationManager; 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.ComponentInfo; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.graphics.drawable.Drawable; 32 import android.os.AsyncTask; 33 import android.os.Bundle; 34 import android.service.notification.SystemZenRules; 35 import android.service.notification.ZenModeConfig; 36 import android.util.Log; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextView; 42 43 import androidx.appcompat.app.AlertDialog; 44 import androidx.fragment.app.Fragment; 45 46 import com.android.settings.R; 47 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 48 import com.android.settings.utils.ZenServiceListing; 49 50 import java.lang.ref.WeakReference; 51 import java.text.Collator; 52 import java.util.Comparator; 53 import java.util.List; 54 import java.util.Set; 55 import java.util.TreeSet; 56 57 public class ZenRuleSelectionDialog extends InstrumentedDialogFragment { 58 private static final String TAG = "ZenRuleSelectionDialog"; 59 private static final boolean DEBUG = ZenModeSettings.DEBUG; 60 61 private static ZenServiceListing mServiceListing; 62 protected static PositiveClickListener mPositiveClickListener; 63 64 private static Context mContext; 65 private static PackageManager mPm; 66 private static NotificationManager mNm; 67 private LinearLayout mRuleContainer; 68 69 /** 70 * The interface we expect a listener to implement. 71 */ 72 public interface PositiveClickListener { onSystemRuleSelected(ZenRuleInfo ruleInfo, Fragment parent)73 void onSystemRuleSelected(ZenRuleInfo ruleInfo, Fragment parent); onExternalRuleSelected(ZenRuleInfo ruleInfo, Fragment parent)74 void onExternalRuleSelected(ZenRuleInfo ruleInfo, Fragment parent); 75 } 76 77 @Override getMetricsCategory()78 public int getMetricsCategory() { 79 return SettingsEnums.NOTIFICATION_ZEN_MODE_RULE_SELECTION_DIALOG; 80 } 81 show(Context context, Fragment parent, PositiveClickListener listener, ZenServiceListing serviceListing)82 public static void show(Context context, Fragment parent, PositiveClickListener 83 listener, ZenServiceListing serviceListing) { 84 mPositiveClickListener = listener; 85 mContext = context; 86 mPm = mContext.getPackageManager(); 87 mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 88 mServiceListing = serviceListing; 89 90 ZenRuleSelectionDialog dialog = new ZenRuleSelectionDialog(); 91 dialog.setTargetFragment(parent, 0); 92 dialog.show(parent.getFragmentManager(), TAG); 93 } 94 95 @Override onCreateDialog(Bundle savedInstanceState)96 public Dialog onCreateDialog(Bundle savedInstanceState) { 97 final View v = LayoutInflater.from(getContext()).inflate(R.layout.zen_rule_type_selection, 98 null, false); 99 100 mRuleContainer = (LinearLayout) v.findViewById(R.id.rule_container); 101 if (mServiceListing != null) { 102 bindType(defaultNewEvent()); 103 bindType(defaultNewSchedule()); 104 mServiceListing.addZenCallback(mServiceListingCallback); 105 mServiceListing.reloadApprovedServices(); 106 } 107 return new AlertDialog.Builder(getContext()) 108 .setTitle(R.string.zen_mode_choose_rule_type) 109 .setView(v) 110 .setNegativeButton(R.string.cancel, null) 111 .create(); 112 } 113 114 @Override onDismiss(DialogInterface dialog)115 public void onDismiss(DialogInterface dialog) { 116 super.onDismiss(dialog); 117 if (mServiceListing != null) { 118 mServiceListing.removeZenCallback(mServiceListingCallback); 119 } 120 } 121 122 // Returns whether the rule's configuration activity exists and is valid. isRuleActivityValid(final ZenRuleInfo ri)123 private boolean isRuleActivityValid(final ZenRuleInfo ri) { 124 Intent intent = new Intent().setComponent(ri.configurationActivity); 125 List<ResolveInfo> results = mPm.queryIntentActivities( 126 intent, PackageManager.ResolveInfoFlags.of(0)); 127 return intent.resolveActivity(mPm) != null && results.size() > 0; 128 } 129 bindType(final ZenRuleInfo ri)130 private void bindType(final ZenRuleInfo ri) { 131 try { 132 ApplicationInfo info = mPm.getApplicationInfo(ri.packageName, 0); 133 final LinearLayout v = (LinearLayout) LayoutInflater.from(mContext).inflate( 134 R.layout.zen_rule_type, null, false); 135 136 ImageView iconView = v.findViewById(R.id.icon); 137 ((TextView) v.findViewById(R.id.title)).setText(ri.title); 138 if (!ri.isSystem) { 139 // Omit rule if the externally provided rule activity is not valid. 140 if (!isRuleActivityValid(ri)) { 141 Log.w(TAG, "rule configuration activity invalid: " + ri.configurationActivity); 142 return; 143 } 144 LoadIconTask task = new LoadIconTask(iconView); 145 task.execute(info); 146 147 TextView subtitle = (TextView) v.findViewById(R.id.subtitle); 148 subtitle.setText(info.loadLabel(mPm)); 149 subtitle.setVisibility(View.VISIBLE); 150 } else { 151 if (ZenModeConfig.isValidScheduleConditionId(ri.defaultConditionId)) { 152 iconView.setImageDrawable(mContext.getDrawable(R.drawable.ic_timelapse)); 153 } else if (ZenModeConfig.isValidEventConditionId(ri.defaultConditionId)) { 154 iconView.setImageDrawable(mContext.getDrawable(R.drawable.ic_event)); 155 } 156 } 157 v.setOnClickListener(new View.OnClickListener() { 158 @Override 159 public void onClick(View v) { 160 dismiss(); 161 if (ri.isSystem) { 162 mPositiveClickListener.onSystemRuleSelected(ri, getTargetFragment()); 163 } else { 164 mPositiveClickListener.onExternalRuleSelected(ri, getTargetFragment()); 165 } 166 } 167 }); 168 mRuleContainer.addView(v); 169 } catch (PackageManager.NameNotFoundException e) { 170 // Omit rule. 171 } 172 } 173 defaultNewSchedule()174 private ZenRuleInfo defaultNewSchedule() { 175 final ZenModeConfig.ScheduleInfo schedule = new ZenModeConfig.ScheduleInfo(); 176 schedule.days = ZenModeConfig.ALL_DAYS; 177 schedule.startHour = 22; 178 schedule.endHour = 7; 179 final ZenRuleInfo rt = new ZenRuleInfo(); 180 rt.settingsAction = ZenModeScheduleRuleSettings.ACTION; 181 rt.title = mContext.getString(R.string.zen_schedule_rule_type_name); 182 rt.packageName = ZenModeConfig.getEventConditionProvider().getPackageName(); 183 rt.defaultConditionId = ZenModeConfig.toScheduleConditionId(schedule); 184 if (Flags.modesApi() && Flags.modesUi()) { 185 rt.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; 186 rt.defaultTriggerDescription = SystemZenRules.getTriggerDescriptionForScheduleTime( 187 mContext, schedule); 188 } 189 rt.serviceComponent = ZenModeConfig.getScheduleConditionProvider(); 190 rt.isSystem = true; 191 return rt; 192 } 193 defaultNewEvent()194 private ZenRuleInfo defaultNewEvent() { 195 final ZenModeConfig.EventInfo event = new ZenModeConfig.EventInfo(); 196 event.calName = null; // any calendar 197 event.calendarId = null; 198 event.reply = ZenModeConfig.EventInfo.REPLY_ANY_EXCEPT_NO; 199 final ZenRuleInfo rt = new ZenRuleInfo(); 200 rt.settingsAction = ZenModeEventRuleSettings.ACTION; 201 rt.title = mContext.getString(R.string.zen_event_rule_type_name); 202 rt.packageName = ZenModeConfig.getScheduleConditionProvider().getPackageName(); 203 rt.defaultConditionId = ZenModeConfig.toEventConditionId(event); 204 if (Flags.modesApi() && Flags.modesUi()) { 205 rt.type = AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; 206 rt.defaultTriggerDescription = SystemZenRules.getTriggerDescriptionForScheduleEvent( 207 mContext, event); 208 } 209 rt.serviceComponent = ZenModeConfig.getEventConditionProvider(); 210 rt.isSystem = true; 211 return rt; 212 } 213 bindExternalRules(Set<ZenRuleInfo> externalRuleTypes)214 private void bindExternalRules(Set<ZenRuleInfo> externalRuleTypes) { 215 for (ZenRuleInfo ri : externalRuleTypes) { 216 bindType(ri); 217 } 218 } 219 220 private final ZenServiceListing.Callback mServiceListingCallback = new 221 ZenServiceListing.Callback() { 222 @Override 223 public void onComponentsReloaded(Set<ComponentInfo> componentInfos) { 224 if (DEBUG) Log.d(TAG, "Reloaded: count=" + componentInfos.size()); 225 226 Set<ZenRuleInfo> externalRuleTypes = new TreeSet<>(RULE_TYPE_COMPARATOR); 227 for (ComponentInfo ci : componentInfos) { 228 final ZenRuleInfo ri = AbstractZenModeAutomaticRulePreferenceController. 229 getRuleInfo(mPm, ci); 230 if (ri != null && ri.configurationActivity != null 231 && mNm.isNotificationPolicyAccessGrantedForPackage(ri.packageName) 232 && (ri.ruleInstanceLimit <= 0 || ri.ruleInstanceLimit 233 >= (mNm.getRuleInstanceCount(ci.getComponentName()) + 1))) { 234 externalRuleTypes.add(ri); 235 } 236 } 237 bindExternalRules(externalRuleTypes); 238 } 239 }; 240 241 private static final Comparator<ZenRuleInfo> RULE_TYPE_COMPARATOR = 242 new Comparator<ZenRuleInfo>() { 243 private final Collator mCollator = Collator.getInstance(); 244 245 @Override 246 public int compare(ZenRuleInfo lhs, ZenRuleInfo rhs) { 247 int byAppName = mCollator.compare(lhs.packageLabel, rhs.packageLabel); 248 if (byAppName != 0) { 249 return byAppName; 250 } else { 251 return mCollator.compare(lhs.title, rhs.title); 252 } 253 } 254 }; 255 256 private class LoadIconTask extends AsyncTask<ApplicationInfo, Void, Drawable> { 257 private final WeakReference<ImageView> viewReference; 258 LoadIconTask(ImageView view)259 public LoadIconTask(ImageView view) { 260 viewReference = new WeakReference<>(view); 261 } 262 263 @Override doInBackground(ApplicationInfo... params)264 protected Drawable doInBackground(ApplicationInfo... params) { 265 return params[0].loadIcon(mPm); 266 } 267 268 @Override onPostExecute(Drawable icon)269 protected void onPostExecute(Drawable icon) { 270 if (icon != null) { 271 final ImageView view = viewReference.get(); 272 if (view != null) { 273 view.setImageDrawable(icon); 274 } 275 } 276 } 277 } 278 }