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.app.AlertDialog; 23 import android.app.Dialog; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.UserManager; 32 import android.provider.Settings; 33 import android.provider.Settings.Global; 34 import android.service.notification.ZenModeConfig; 35 import android.service.quicksettings.Tile; 36 import android.text.TextUtils; 37 import android.util.Log; 38 import android.widget.Switch; 39 40 import androidx.annotation.Nullable; 41 42 import com.android.internal.jank.InteractionJankMonitor; 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 45 import com.android.settingslib.notification.modes.EnableDndDialogFactory; 46 import com.android.systemui.Prefs; 47 import com.android.systemui.animation.DialogCuj; 48 import com.android.systemui.animation.DialogTransitionAnimator; 49 import com.android.systemui.animation.Expandable; 50 import com.android.systemui.dagger.qualifiers.Background; 51 import com.android.systemui.dagger.qualifiers.Main; 52 import com.android.systemui.modes.shared.ModesUi; 53 import com.android.systemui.plugins.ActivityStarter; 54 import com.android.systemui.plugins.FalsingManager; 55 import com.android.systemui.plugins.qs.QSTile.BooleanState; 56 import com.android.systemui.plugins.statusbar.StatusBarStateController; 57 import com.android.systemui.qs.QSHost; 58 import com.android.systemui.qs.QsEventLogger; 59 import com.android.systemui.qs.UserSettingObserver; 60 import com.android.systemui.qs.logging.QSLogger; 61 import com.android.systemui.qs.tileimpl.QSTileImpl; 62 import com.android.systemui.qs.tiles.dialog.QSEnableDndDialogMetricsLogger; 63 import com.android.systemui.res.R; 64 import com.android.systemui.statusbar.phone.SystemUIDialog; 65 import com.android.systemui.statusbar.policy.ZenModeController; 66 import com.android.systemui.util.settings.SecureSettings; 67 68 import javax.inject.Inject; 69 70 /** Quick settings tile: Do not disturb **/ 71 public class DndTile extends QSTileImpl<BooleanState> { 72 73 public static final String TILE_SPEC = "dnd"; 74 75 private static final Intent ZEN_SETTINGS = 76 new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); 77 78 private static final Intent ZEN_PRIORITY_SETTINGS = 79 new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS); 80 81 private static final String INTERACTION_JANK_TAG = "start_zen_mode"; 82 83 private final ZenModeController mController; 84 private final SharedPreferences mSharedPreferences; 85 private final UserSettingObserver mSettingZenDuration; 86 private final DialogTransitionAnimator mDialogTransitionAnimator; 87 private final QSEnableDndDialogMetricsLogger mQSDndDurationDialogLogger; 88 89 private boolean mListening; 90 91 @Inject DndTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, ZenModeController zenModeController, @Main SharedPreferences sharedPreferences, SecureSettings secureSettings, DialogTransitionAnimator dialogTransitionAnimator )92 public DndTile( 93 QSHost host, 94 QsEventLogger uiEventLogger, 95 @Background Looper backgroundLooper, 96 @Main Handler mainHandler, 97 FalsingManager falsingManager, 98 MetricsLogger metricsLogger, 99 StatusBarStateController statusBarStateController, 100 ActivityStarter activityStarter, 101 QSLogger qsLogger, 102 ZenModeController zenModeController, 103 @Main SharedPreferences sharedPreferences, 104 SecureSettings secureSettings, 105 DialogTransitionAnimator dialogTransitionAnimator 106 ) { 107 super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, 108 statusBarStateController, activityStarter, qsLogger); 109 110 // If the flag is on, this shouldn't run at all since the modes tile replaces the DND tile. 111 ModesUi.assertInLegacyMode(); 112 113 mController = zenModeController; 114 mSharedPreferences = sharedPreferences; 115 mController.observe(getLifecycle(), mZenCallback); 116 mDialogTransitionAnimator = dialogTransitionAnimator; 117 mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler, 118 Settings.Secure.ZEN_DURATION, getHost().getUserId()) { 119 @Override 120 protected void handleValueChanged(int value, boolean observedChange) { 121 refreshState(); 122 } 123 }; 124 mQSDndDurationDialogLogger = new QSEnableDndDialogMetricsLogger(mContext); 125 } 126 setVisible(Context context, boolean visible)127 public static void setVisible(Context context, boolean visible) { 128 Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible); 129 } 130 isVisible(SharedPreferences prefs)131 public static boolean isVisible(SharedPreferences prefs) { 132 return prefs.getBoolean(Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */); 133 } 134 setCombinedIcon(Context context, boolean combined)135 public static void setCombinedIcon(Context context, boolean combined) { 136 Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined); 137 } 138 isCombinedIcon(SharedPreferences sharedPreferences)139 public static boolean isCombinedIcon(SharedPreferences sharedPreferences) { 140 return sharedPreferences.getBoolean(Prefs.Key.DND_TILE_COMBINED_ICON, 141 false /* defaultValue */); 142 } 143 144 @Override newTileState()145 public BooleanState newTileState() { 146 return new BooleanState(); 147 } 148 149 @Override getLongClickIntent()150 public Intent getLongClickIntent() { 151 return ZEN_SETTINGS; 152 } 153 154 @Override handleClick(@ullable Expandable expandable)155 protected void handleClick(@Nullable Expandable expandable) { 156 // Zen is currently on 157 if (mState.value) { 158 mController.setZen(ZEN_MODE_OFF, null, TAG); 159 } else { 160 enableZenMode(expandable); 161 } 162 } 163 164 @Override handleUserSwitch(int newUserId)165 protected void handleUserSwitch(int newUserId) { 166 super.handleUserSwitch(newUserId); 167 mSettingZenDuration.setUserId(newUserId); 168 } 169 enableZenMode(@ullable Expandable expandable)170 private void enableZenMode(@Nullable Expandable expandable) { 171 int zenDuration = mSettingZenDuration.getValue(); 172 switch (zenDuration) { 173 case Settings.Secure.ZEN_DURATION_PROMPT: 174 mUiHandler.post(() -> { 175 Dialog dialog = makeZenModeDialog(); 176 if (expandable != null) { 177 DialogTransitionAnimator.Controller controller = 178 expandable.dialogTransitionController(new DialogCuj( 179 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, 180 INTERACTION_JANK_TAG)); 181 if (controller != null) { 182 mDialogTransitionAnimator.show(dialog, 183 controller, /* animateBackgroundBoundsChange= */ false); 184 } else { 185 dialog.show(); 186 } 187 } else { 188 dialog.show(); 189 } 190 }); 191 break; 192 case Settings.Secure.ZEN_DURATION_FOREVER: 193 mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); 194 break; 195 default: 196 Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration, 197 mHost.getUserId(), true).id; 198 mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 199 conditionId, TAG); 200 } 201 } 202 makeZenModeDialog()203 private Dialog makeZenModeDialog() { 204 AlertDialog dialog = new EnableDndDialogFactory(mContext, R.style.Theme_SystemUI_Dialog, 205 true /* cancelIsNeutral */, 206 mQSDndDurationDialogLogger).createDialog(); 207 SystemUIDialog.applyFlags(dialog); 208 SystemUIDialog.setShowForAllUsers(dialog, true); 209 SystemUIDialog.registerDismissListener(dialog); 210 SystemUIDialog.setDialogSize(dialog); 211 return dialog; 212 } 213 214 @Override handleSecondaryClick(@ullable Expandable expandable)215 protected void handleSecondaryClick(@Nullable Expandable expandable) { 216 handleLongClick(expandable); 217 } 218 219 @Override getTileLabel()220 public CharSequence getTileLabel() { 221 return mContext.getString(R.string.quick_settings_dnd_label); 222 } 223 224 @Override handleUpdateState(BooleanState state, Object arg)225 protected void handleUpdateState(BooleanState state, Object arg) { 226 if (mController == null) return; 227 final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen(); 228 final boolean newValue = zen != ZEN_MODE_OFF; 229 state.dualTarget = true; 230 state.value = newValue; 231 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; 232 state.icon = maybeLoadResourceIcon(state.value 233 ? R.drawable.qs_dnd_icon_on 234 : R.drawable.qs_dnd_icon_off); 235 state.label = getTileLabel(); 236 state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext, 237 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); 238 checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); 239 // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier 240 // to understand. 241 switch (zen) { 242 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 243 state.contentDescription = 244 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " 245 + state.secondaryLabel; 246 break; 247 case Global.ZEN_MODE_NO_INTERRUPTIONS: 248 state.contentDescription = 249 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " + 250 mContext.getString( 251 R.string.accessibility_quick_settings_dnd_none_on) 252 + ", " + state.secondaryLabel; 253 break; 254 case ZEN_MODE_ALARMS: 255 state.contentDescription = 256 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " + 257 mContext.getString( 258 R.string.accessibility_quick_settings_dnd_alarms_on) 259 + ", " + state.secondaryLabel; 260 break; 261 default: 262 state.contentDescription = mContext.getString( 263 R.string.accessibility_quick_settings_dnd); 264 break; 265 } 266 state.dualLabelContentDescription = mContext.getResources().getString( 267 R.string.accessibility_quick_settings_open_settings, getTileLabel()); 268 state.expandedAccessibilityClassName = Switch.class.getName(); 269 state.forceExpandIcon = 270 mSettingZenDuration.getValue() == Settings.Secure.ZEN_DURATION_PROMPT; 271 } 272 273 @Override getMetricsCategory()274 public int getMetricsCategory() { 275 return MetricsEvent.QS_DND; 276 } 277 278 @Override handleSetListening(boolean listening)279 public void handleSetListening(boolean listening) { 280 super.handleSetListening(listening); 281 if (mListening == listening) return; 282 mListening = listening; 283 if (mListening) { 284 Prefs.registerListener(mContext, mPrefListener); 285 } else { 286 Prefs.unregisterListener(mContext, mPrefListener); 287 } 288 mSettingZenDuration.setListening(listening); 289 } 290 291 @Override handleDestroy()292 protected void handleDestroy() { 293 super.handleDestroy(); 294 mSettingZenDuration.setListening(false); 295 } 296 297 @Override isAvailable()298 public boolean isAvailable() { 299 return isVisible(mSharedPreferences); 300 } 301 302 private final OnSharedPreferenceChangeListener mPrefListener 303 = new OnSharedPreferenceChangeListener() { 304 @Override 305 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 306 @Prefs.Key String key) { 307 if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) || 308 Prefs.Key.DND_TILE_VISIBLE.equals(key)) { 309 refreshState(); 310 } 311 } 312 }; 313 314 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { 315 public void onZenChanged(int zen) { 316 Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile."); 317 refreshState(zen); 318 } 319 }; 320 321 } 322