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