Home | History | Annotate | Download | only in tiles
      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.systemui.qs.tiles;
     18 
     19 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
     20 import static android.provider.Settings.Global.ZEN_MODE_OFF;
     21 
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.SharedPreferences;
     27 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     28 import android.content.pm.ApplicationInfo;
     29 import android.content.pm.PackageManager;
     30 import android.os.UserManager;
     31 import android.provider.Settings;
     32 import android.provider.Settings.Global;
     33 import android.service.notification.ZenModeConfig;
     34 import android.service.notification.ZenModeConfig.ZenRule;
     35 import android.service.quicksettings.Tile;
     36 import android.util.Log;
     37 import android.util.Slog;
     38 import android.view.LayoutInflater;
     39 import android.view.View;
     40 import android.view.View.OnAttachStateChangeListener;
     41 import android.view.ViewGroup;
     42 import android.widget.Switch;
     43 import android.widget.Toast;
     44 
     45 import com.android.internal.logging.MetricsLogger;
     46 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     47 import com.android.systemui.Dependency;
     48 import com.android.systemui.Prefs;
     49 import com.android.systemui.R;
     50 import com.android.systemui.SysUIToast;
     51 import com.android.systemui.plugins.ActivityStarter;
     52 import com.android.systemui.plugins.qs.DetailAdapter;
     53 import com.android.systemui.plugins.qs.QSTile;
     54 import com.android.systemui.plugins.qs.QSTile.BooleanState;
     55 import com.android.systemui.qs.QSHost;
     56 import com.android.systemui.qs.tileimpl.QSTileImpl;
     57 import com.android.systemui.statusbar.policy.ZenModeController;
     58 import com.android.systemui.statusbar.policy.ZenModeController.Callback;
     59 import com.android.systemui.volume.ZenModePanel;
     60 
     61 /** Quick settings tile: Do not disturb **/
     62 public class DndTile extends QSTileImpl<BooleanState> {
     63 
     64     private static final Intent ZEN_SETTINGS =
     65             new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
     66 
     67     private static final Intent ZEN_PRIORITY_SETTINGS =
     68             new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
     69 
     70     private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
     71     private static final String EXTRA_VISIBLE = "visible";
     72 
     73     private static final QSTile.Icon TOTAL_SILENCE =
     74             ResourceIcon.get(R.drawable.ic_qs_dnd_on_total_silence);
     75 
     76     private final ZenModeController mController;
     77     private final DndDetailAdapter mDetailAdapter;
     78 
     79     private boolean mListening;
     80     private boolean mShowingDetail;
     81     private boolean mReceiverRegistered;
     82 
     83     public DndTile(QSHost host) {
     84         super(host);
     85         mController = Dependency.get(ZenModeController.class);
     86         mDetailAdapter = new DndDetailAdapter();
     87         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_SET_VISIBLE));
     88         mReceiverRegistered = true;
     89     }
     90 
     91     @Override
     92     protected void handleDestroy() {
     93         super.handleDestroy();
     94         if (mReceiverRegistered) {
     95             mContext.unregisterReceiver(mReceiver);
     96             mReceiverRegistered = false;
     97         }
     98     }
     99 
    100     public static void setVisible(Context context, boolean visible) {
    101         Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
    102     }
    103 
    104     public static boolean isVisible(Context context) {
    105         return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
    106     }
    107 
    108     public static void setCombinedIcon(Context context, boolean combined) {
    109         Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
    110     }
    111 
    112     public static boolean isCombinedIcon(Context context) {
    113         return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
    114                 false /* defaultValue */);
    115     }
    116 
    117     @Override
    118     public DetailAdapter getDetailAdapter() {
    119         return mDetailAdapter;
    120     }
    121 
    122     @Override
    123     public BooleanState newTileState() {
    124         return new BooleanState();
    125     }
    126 
    127     @Override
    128     public Intent getLongClickIntent() {
    129         return ZEN_SETTINGS;
    130     }
    131 
    132     @Override
    133     protected void handleClick() {
    134         if (mState.value) {
    135             mController.setZen(ZEN_MODE_OFF, null, TAG);
    136         } else {
    137             int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN, Global.ZEN_MODE_ALARMS);
    138             mController.setZen(zen, null, TAG);
    139         }
    140     }
    141 
    142     @Override
    143     protected void handleSecondaryClick() {
    144         if (mController.isVolumeRestricted()) {
    145             // Collapse the panels, so the user can see the toast.
    146             mHost.collapsePanels();
    147             SysUIToast.makeText(mContext, mContext.getString(
    148                     com.android.internal.R.string.error_message_change_not_allowed),
    149                     Toast.LENGTH_LONG).show();
    150             return;
    151         }
    152         if (!mState.value) {
    153             // Because of the complexity of the zen panel, it needs to be shown after
    154             // we turn on zen below.
    155             mController.addCallback(new ZenModeController.Callback() {
    156                 @Override
    157                 public void onZenChanged(int zen) {
    158                     mController.removeCallback(this);
    159                     showDetail(true);
    160                 }
    161             });
    162             int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
    163                     Global.ZEN_MODE_ALARMS);
    164             mController.setZen(zen, null, TAG);
    165         } else {
    166             showDetail(true);
    167         }
    168     }
    169 
    170     @Override
    171     public CharSequence getTileLabel() {
    172         return mContext.getString(R.string.quick_settings_dnd_label);
    173     }
    174 
    175     @Override
    176     protected void handleUpdateState(BooleanState state, Object arg) {
    177         final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
    178         final boolean newValue = zen != ZEN_MODE_OFF;
    179         final boolean valueChanged = state.value != newValue;
    180         if (state.slash == null) state.slash = new SlashState();
    181         state.dualTarget = true;
    182         state.value = newValue;
    183         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
    184         state.slash.isSlashed = !state.value;
    185         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
    186         switch (zen) {
    187             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    188                 state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
    189                 state.label = mContext.getString(R.string.quick_settings_dnd_priority_label);
    190                 state.contentDescription = mContext.getString(
    191                         R.string.accessibility_quick_settings_dnd_priority_on);
    192                 break;
    193             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    194                 state.icon = TOTAL_SILENCE;
    195                 state.label = mContext.getString(R.string.quick_settings_dnd_none_label);
    196                 state.contentDescription = mContext.getString(
    197                         R.string.accessibility_quick_settings_dnd_none_on);
    198                 break;
    199             case ZEN_MODE_ALARMS:
    200                 state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
    201                 state.label = mContext.getString(R.string.quick_settings_dnd_alarms_label);
    202                 state.contentDescription = mContext.getString(
    203                         R.string.accessibility_quick_settings_dnd_alarms_on);
    204                 break;
    205             default:
    206                 state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
    207                 state.label = mContext.getString(R.string.quick_settings_dnd_label);
    208                 state.contentDescription = mContext.getString(
    209                         R.string.accessibility_quick_settings_dnd);
    210                 break;
    211         }
    212         if (valueChanged) {
    213             fireToggleStateChanged(state.value);
    214         }
    215         state.dualLabelContentDescription = mContext.getResources().getString(
    216                 R.string.accessibility_quick_settings_open_settings, getTileLabel());
    217         state.expandedAccessibilityClassName = Switch.class.getName();
    218     }
    219 
    220     @Override
    221     public int getMetricsCategory() {
    222         return MetricsEvent.QS_DND;
    223     }
    224 
    225     @Override
    226     protected String composeChangeAnnouncement() {
    227         if (mState.value) {
    228             return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_on);
    229         } else {
    230             return mContext.getString(R.string.accessibility_quick_settings_dnd_changed_off);
    231         }
    232     }
    233 
    234     @Override
    235     public void handleSetListening(boolean listening) {
    236         if (mListening == listening) return;
    237         mListening = listening;
    238         if (mListening) {
    239             mController.addCallback(mZenCallback);
    240             Prefs.registerListener(mContext, mPrefListener);
    241         } else {
    242             mController.removeCallback(mZenCallback);
    243             Prefs.unregisterListener(mContext, mPrefListener);
    244         }
    245     }
    246 
    247     @Override
    248     public boolean isAvailable() {
    249         return isVisible(mContext);
    250     }
    251 
    252     private final OnSharedPreferenceChangeListener mPrefListener
    253             = new OnSharedPreferenceChangeListener() {
    254         @Override
    255         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
    256                 @Prefs.Key String key) {
    257             if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
    258                     Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
    259                 refreshState();
    260             }
    261         }
    262     };
    263 
    264     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
    265         public void onZenChanged(int zen) {
    266             refreshState(zen);
    267             if (isShowingDetail()) {
    268                 mDetailAdapter.updatePanel();
    269             }
    270         }
    271 
    272         @Override
    273         public void onConfigChanged(ZenModeConfig config) {
    274             if (isShowingDetail()) {
    275                 mDetailAdapter.updatePanel();
    276             }
    277         }
    278     };
    279 
    280     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    281         @Override
    282         public void onReceive(Context context, Intent intent) {
    283             final boolean visible = intent.getBooleanExtra(EXTRA_VISIBLE, false);
    284             setVisible(mContext, visible);
    285             refreshState();
    286         }
    287     };
    288 
    289     private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
    290 
    291         private ZenModePanel mZenPanel;
    292         private boolean mAuto;
    293 
    294         @Override
    295         public CharSequence getTitle() {
    296             return mContext.getString(R.string.quick_settings_dnd_label);
    297         }
    298 
    299         @Override
    300         public Boolean getToggleState() {
    301             return mState.value;
    302         }
    303 
    304         @Override
    305         public Intent getSettingsIntent() {
    306             return ZEN_SETTINGS;
    307         }
    308 
    309         @Override
    310         public void setToggleState(boolean state) {
    311             MetricsLogger.action(mContext, MetricsEvent.QS_DND_TOGGLE, state);
    312             if (!state) {
    313                 mController.setZen(ZEN_MODE_OFF, null, TAG);
    314                 mAuto = false;
    315             } else {
    316                 int zen = Prefs.getInt(mContext, Prefs.Key.DND_FAVORITE_ZEN,
    317                         ZEN_MODE_ALARMS);
    318                 mController.setZen(zen, null, TAG);
    319             }
    320         }
    321 
    322         @Override
    323         public int getMetricsCategory() {
    324             return MetricsEvent.QS_DND_DETAILS;
    325         }
    326 
    327         @Override
    328         public View createDetailView(Context context, View convertView, ViewGroup parent) {
    329             mZenPanel = convertView != null ? (ZenModePanel) convertView
    330                     : (ZenModePanel) LayoutInflater.from(context).inflate(
    331                             R.layout.zen_mode_panel, parent, false);
    332             if (convertView == null) {
    333                 mZenPanel.init(mController);
    334                 mZenPanel.addOnAttachStateChangeListener(this);
    335                 mZenPanel.setCallback(mZenModePanelCallback);
    336                 mZenPanel.setEmptyState(R.drawable.ic_qs_dnd_detail_empty, R.string.dnd_is_off);
    337             }
    338             updatePanel();
    339             return mZenPanel;
    340         }
    341 
    342         private void updatePanel() {
    343             if (mZenPanel == null) return;
    344             mAuto = false;
    345             if (mController.getZen() == ZEN_MODE_OFF) {
    346                 mZenPanel.setState(ZenModePanel.STATE_OFF);
    347             } else {
    348                 ZenModeConfig config = mController.getConfig();
    349                 String summary = "";
    350                 if (config.manualRule != null && config.manualRule.enabler != null) {
    351                     summary = getOwnerCaption(config.manualRule.enabler);
    352                 }
    353                 for (ZenRule automaticRule : config.automaticRules.values()) {
    354                     if (automaticRule.isAutomaticActive()) {
    355                         if (summary.isEmpty()) {
    356                             summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule,
    357                                     automaticRule.name);
    358                         } else {
    359                             summary = mContext.getString(R.string.qs_dnd_prompt_auto_rule_app);
    360                         }
    361                     }
    362                 }
    363                 if (summary.isEmpty()) {
    364                     mZenPanel.setState(ZenModePanel.STATE_MODIFY);
    365                 } else {
    366                     mAuto = true;
    367                     mZenPanel.setState(ZenModePanel.STATE_AUTO_RULE);
    368                     mZenPanel.setAutoText(summary);
    369                 }
    370             }
    371         }
    372 
    373         private String getOwnerCaption(String owner) {
    374             final PackageManager pm = mContext.getPackageManager();
    375             try {
    376                 final ApplicationInfo info = pm.getApplicationInfo(owner, 0);
    377                 if (info != null) {
    378                     final CharSequence seq = info.loadLabel(pm);
    379                     if (seq != null) {
    380                         final String str = seq.toString().trim();
    381                         return mContext.getString(R.string.qs_dnd_prompt_app, str);
    382                     }
    383                 }
    384             } catch (Throwable e) {
    385                 Slog.w(TAG, "Error loading owner caption", e);
    386             }
    387             return "";
    388         }
    389 
    390         @Override
    391         public void onViewAttachedToWindow(View v) {
    392             mShowingDetail = true;
    393         }
    394 
    395         @Override
    396         public void onViewDetachedFromWindow(View v) {
    397             mShowingDetail = false;
    398             mZenPanel = null;
    399         }
    400     }
    401 
    402     private final ZenModePanel.Callback mZenModePanelCallback = new ZenModePanel.Callback() {
    403         @Override
    404         public void onPrioritySettings() {
    405             Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
    406                     ZEN_PRIORITY_SETTINGS, 0);
    407         }
    408 
    409         @Override
    410         public void onInteraction() {
    411             // noop
    412         }
    413 
    414         @Override
    415         public void onExpanded(boolean expanded) {
    416             // noop
    417         }
    418     };
    419 
    420 }
    421