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