1/*
2 * Copyright (C) 2016 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
17package com.android.settings.notification;
18
19import static android.app.NotificationManager.IMPORTANCE_LOW;
20import static android.app.NotificationManager.IMPORTANCE_NONE;
21import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
22
23import android.app.Activity;
24import android.app.NotificationChannel;
25import android.app.NotificationChannelGroup;
26import android.app.NotificationManager;
27import android.content.Intent;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.AsyncTask;
31import android.provider.Settings;
32import android.support.v7.preference.Preference;
33import android.text.TextUtils;
34import android.text.BidiFormatter;
35import android.text.SpannableStringBuilder;
36import android.util.ArrayMap;
37import android.util.Log;
38import android.view.LayoutInflater;
39import android.view.View;
40import android.widget.Switch;
41
42import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
43import com.android.settings.AppHeader;
44import com.android.settings.R;
45import com.android.settings.RingtonePreference;
46import com.android.settings.Utils;
47import com.android.settings.applications.AppHeaderController;
48import com.android.settings.applications.AppInfoBase;
49import com.android.settings.applications.LayoutPreference;
50import com.android.settings.overlay.FeatureFactory;
51import com.android.settings.widget.FooterPreference;
52import com.android.settings.widget.SwitchBar;
53import com.android.settingslib.RestrictedSwitchPreference;
54
55public class ChannelNotificationSettings extends NotificationSettingsBase {
56    private static final String TAG = "ChannelSettings";
57
58    private static final String KEY_LIGHTS = "lights";
59    private static final String KEY_VIBRATE = "vibrate";
60    private static final String KEY_RINGTONE = "ringtone";
61    private static final String KEY_IMPORTANCE = "importance";
62
63    private Preference mImportance;
64    private RestrictedSwitchPreference mLights;
65    private RestrictedSwitchPreference mVibrate;
66    private NotificationSoundPreference mRingtone;
67    private FooterPreference mFooter;
68    private NotificationChannelGroup mChannelGroup;
69    private AppHeaderController mHeaderPref;
70
71    @Override
72    public int getMetricsCategory() {
73        return MetricsEvent.NOTIFICATION_TOPIC_NOTIFICATION;
74    }
75
76    @Override
77    public void onResume() {
78        super.onResume();
79        if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) {
80            Log.w(TAG, "Missing package or uid or packageinfo or channel");
81            finish();
82            return;
83        }
84
85        if (getPreferenceScreen() != null) {
86            getPreferenceScreen().removeAll();
87        }
88        addPreferencesFromResource(R.xml.notification_settings);
89        setupBlock();
90        addHeaderPref();
91        addAppLinkPref();
92        addFooterPref();
93
94        if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
95            populateDefaultChannelPrefs();
96            mShowLegacyChannelConfig = true;
97        } else {
98            populateUpgradedChannelPrefs();
99
100            if (mChannel.getGroup() != null) {
101                // Go look up group name
102                new AsyncTask<Void, Void, Void>() {
103                    @Override
104                    protected Void doInBackground(Void... unused) {
105                        if (mChannel.getGroup() != null) {
106                            mChannelGroup = mBackend.getGroup(mChannel.getGroup(), mPkg, mUid);
107                        }
108                        return null;
109                    }
110
111                    @Override
112                    protected void onPostExecute(Void unused) {
113                        if (getHost() == null || mChannelGroup == null) {
114                            return;
115                        }
116                        setChannelGroupLabel(mChannelGroup.getName());
117                    }
118                }.execute();
119            }
120        }
121
122        updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
123    }
124
125    private void populateUpgradedChannelPrefs() {
126        addPreferencesFromResource(R.xml.upgraded_channel_notification_settings);
127        setupBadge();
128        setupPriorityPref(mChannel.canBypassDnd());
129        setupVisOverridePref(mChannel.getLockscreenVisibility());
130        setupLights();
131        setupVibrate();
132        setupRingtone();
133        setupImportance();
134    }
135
136    private void addHeaderPref() {
137        ArrayMap<String, NotificationBackend.AppRow> rows = new ArrayMap<String, NotificationBackend.AppRow>();
138        rows.put(mAppRow.pkg, mAppRow);
139        collectConfigActivities(rows);
140        final Activity activity = getActivity();
141        mHeaderPref = FeatureFactory.getFactory(activity)
142                .getApplicationFeatureProvider(activity)
143                .newAppHeaderController(this /* fragment */, null /* appHeader */);
144        final Preference pref = mHeaderPref
145                .setIcon(mAppRow.icon)
146                .setLabel(mChannel.getName())
147                .setSummary(mAppRow.label)
148                .setPackageName(mAppRow.pkg)
149                .setUid(mAppRow.uid)
150                .setButtonActions(AppHeaderController.ActionType.ACTION_APP_INFO,
151                        AppHeaderController.ActionType.ACTION_NOTIF_PREFERENCE)
152                .done(activity, getPrefContext());
153        getPreferenceScreen().addPreference(pref);
154    }
155
156    private void setChannelGroupLabel(CharSequence groupName) {
157        final SpannableStringBuilder summary = new SpannableStringBuilder();
158        BidiFormatter bidi = BidiFormatter.getInstance();
159        summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
160        if (groupName != null) {
161            summary.append(bidi.unicodeWrap(mContext.getText(
162                    R.string.notification_header_divider_symbol_with_spaces)));
163            summary.append(bidi.unicodeWrap(groupName.toString()));
164        }
165        final Activity activity = getActivity();
166        mHeaderPref.setSummary(summary.toString());
167        mHeaderPref.done(activity, getPrefContext());
168    }
169
170    private void addFooterPref() {
171        if (!TextUtils.isEmpty(mChannel.getDescription())) {
172            FooterPreference descPref = new FooterPreference(getPrefContext());
173            descPref.setOrder(ORDER_LAST);
174            descPref.setSelectable(false);
175            descPref.setTitle(mChannel.getDescription());
176            getPreferenceScreen().addPreference(descPref);
177        }
178    }
179
180    protected void setupBadge() {
181        mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
182        mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
183        mBadge.setEnabled(mAppRow.showBadge);
184        mBadge.setChecked(mChannel.canShowBadge());
185
186        mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
187            @Override
188            public boolean onPreferenceChange(Preference preference, Object newValue) {
189                final boolean value = (Boolean) newValue;
190                mChannel.setShowBadge(value);
191                mChannel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
192                mBackend.updateChannel(mPkg, mUid, mChannel);
193                return true;
194            }
195        });
196    }
197
198    private void setupLights() {
199        mLights = (RestrictedSwitchPreference) findPreference(KEY_LIGHTS);
200        mLights.setDisabledByAdmin(mSuspendedAppsAdmin);
201        mLights.setChecked(mChannel.shouldShowLights());
202        mLights.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
203            @Override
204            public boolean onPreferenceChange(Preference preference, Object newValue) {
205                final boolean lights = (Boolean) newValue;
206                mChannel.enableLights(lights);
207                mChannel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
208                mBackend.updateChannel(mPkg, mUid, mChannel);
209                return true;
210            }
211        });
212    }
213
214    private void setupVibrate() {
215        mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
216        mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin);
217        mVibrate.setEnabled(!mVibrate.isDisabledByAdmin() && isChannelConfigurable(mChannel));
218        mVibrate.setChecked(mChannel.shouldVibrate());
219        mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
220            @Override
221            public boolean onPreferenceChange(Preference preference, Object newValue) {
222                final boolean vibrate = (Boolean) newValue;
223                mChannel.enableVibration(vibrate);
224                mChannel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
225                mBackend.updateChannel(mPkg, mUid, mChannel);
226                return true;
227            }
228        });
229    }
230
231    private void setupRingtone() {
232        mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE);
233        mRingtone.setRingtone(mChannel.getSound());
234        mRingtone.setEnabled(isChannelConfigurable(mChannel));
235        mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
236            @Override
237            public boolean onPreferenceChange(Preference preference, Object newValue) {
238                mChannel.setSound((Uri) newValue, mChannel.getAudioAttributes());
239                mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
240                mBackend.updateChannel(mPkg, mUid, mChannel);
241                return false;
242            }
243        });
244    }
245
246    private void setupBlock() {
247        View switchBarContainer = LayoutInflater.from(
248                getPrefContext()).inflate(R.layout.styled_switch_bar, null);
249        mSwitchBar = switchBarContainer.findViewById(R.id.switch_bar);
250        mSwitchBar.show();
251        mSwitchBar.setDisabledByAdmin(mSuspendedAppsAdmin);
252        mSwitchBar.setChecked(mChannel.getImportance() != NotificationManager.IMPORTANCE_NONE);
253        mSwitchBar.addOnSwitchChangeListener(new SwitchBar.OnSwitchChangeListener() {
254            @Override
255            public void onSwitchChanged(Switch switchView, boolean isChecked) {
256                int importance = 0;
257                if (mShowLegacyChannelConfig) {
258                    importance = isChecked ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE;
259                    mImportanceToggle.setChecked(importance == IMPORTANCE_UNSPECIFIED);
260                } else {
261                    importance = isChecked ? IMPORTANCE_LOW : IMPORTANCE_NONE;
262                    mImportance.setSummary(getImportanceSummary(importance));
263                }
264                mChannel.setImportance(importance);
265                mChannel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
266                mBackend.updateChannel(mPkg, mUid, mChannel);
267                updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
268            }
269        });
270
271        mBlockBar = new LayoutPreference(getPrefContext(), switchBarContainer);
272        mBlockBar.setOrder(ORDER_FIRST);
273        mBlockBar.setKey(KEY_BLOCK);
274        getPreferenceScreen().addPreference(mBlockBar);
275
276        if (!isChannelBlockable(mAppRow.systemApp, mChannel)) {
277            setVisible(mBlockBar, false);
278        }
279
280        setupBlockDesc(R.string.channel_notifications_off_desc);
281    }
282
283    private void setupImportance() {
284        mImportance = findPreference(KEY_IMPORTANCE);
285        Bundle channelArgs = new Bundle();
286        channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
287        channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
288        channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
289        channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
290        mImportance.setEnabled(mSuspendedAppsAdmin == null && isChannelConfigurable(mChannel));
291        // Set up intent to show importance selection only if this setting is enabled.
292        if (mImportance.isEnabled()) {
293            Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
294                    ChannelImportanceSettings.class.getName(),
295                    channelArgs, null, R.string.notification_importance_title, null,
296                    false, getMetricsCategory());
297            mImportance.setIntent(channelIntent);
298        }
299        mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
300    }
301
302    private String getImportanceSummary(int importance) {
303        String title;
304        String summary = null;
305        switch (importance) {
306            case IMPORTANCE_UNSPECIFIED:
307                title = getContext().getString(R.string.notification_importance_unspecified);
308                break;
309            case NotificationManager.IMPORTANCE_MIN:
310                title = getContext().getString(R.string.notification_importance_min_title);
311                summary = getContext().getString(R.string.notification_importance_min);
312                break;
313            case NotificationManager.IMPORTANCE_LOW:
314                title = getContext().getString(R.string.notification_importance_low_title);
315                summary = getContext().getString(R.string.notification_importance_low);
316                break;
317            case NotificationManager.IMPORTANCE_DEFAULT:
318                title = getContext().getString(R.string.notification_importance_default_title);
319                if (hasValidSound()) {
320                    summary = getContext().getString(R.string.notification_importance_default);
321                }
322                break;
323            case NotificationManager.IMPORTANCE_HIGH:
324            case NotificationManager.IMPORTANCE_MAX:
325                title = getContext().getString(R.string.notification_importance_high_title);
326                if (hasValidSound()) {
327                    summary = getContext().getString(R.string.notification_importance_high);
328                }
329                break;
330            default:
331                return "";
332        }
333
334        if (summary != null) {
335            return getContext().getString(R.string.notification_importance_divider, title, summary);
336        } else {
337            return title;
338        }
339    }
340
341    @Override
342    public boolean onPreferenceTreeClick(Preference preference) {
343        if (preference instanceof RingtonePreference) {
344            mRingtone.onPrepareRingtonePickerIntent(mRingtone.getIntent());
345            startActivityForResult(preference.getIntent(), 200);
346            return true;
347        }
348        return super.onPreferenceTreeClick(preference);
349    }
350
351    @Override
352    public void onActivityResult(int requestCode, int resultCode, Intent data) {
353        if (mRingtone != null) {
354            mRingtone.onActivityResult(requestCode, resultCode, data);
355        }
356        mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
357    }
358
359    boolean canPulseLight() {
360        if (!getResources()
361                .getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed)) {
362            return false;
363        }
364        return Settings.System.getInt(getContentResolver(),
365                Settings.System.NOTIFICATION_LIGHT_PULSE, 0) == 1;
366    }
367
368    boolean hasValidSound() {
369        return mChannel.getSound() != null && !Uri.EMPTY.equals(mChannel.getSound());
370    }
371
372    void updateDependents(boolean banned) {
373        if (mShowLegacyChannelConfig) {
374            setVisible(mImportanceToggle, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
375        } else {
376            setVisible(mImportance, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
377            setVisible(mLights, checkCanBeVisible(
378                    NotificationManager.IMPORTANCE_DEFAULT) && canPulseLight());
379            setVisible(mVibrate, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
380            setVisible(mRingtone, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT));
381        }
382        setVisible(mBadge, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
383        setVisible(mPriority, checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT)
384                || (checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
385                && mDndVisualEffectsSuppressed));
386        setVisible(mVisibilityOverride, checkCanBeVisible(NotificationManager.IMPORTANCE_LOW)
387                && isLockScreenSecure());
388        setVisible(mBlockedDesc, mChannel.getImportance() == IMPORTANCE_NONE);
389        if (mAppLink != null) {
390            setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
391        }
392        if (mFooter != null) {
393            setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
394        }
395    }
396}
397