• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }