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.view.View; 39 import android.widget.Switch; 40 41 import androidx.annotation.Nullable; 42 43 import com.android.internal.jank.InteractionJankMonitor; 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 46 import com.android.settingslib.notification.EnableZenModeDialog; 47 import com.android.systemui.Prefs; 48 import com.android.systemui.R; 49 import com.android.systemui.animation.DialogCuj; 50 import com.android.systemui.animation.DialogLaunchAnimator; 51 import com.android.systemui.dagger.qualifiers.Background; 52 import com.android.systemui.dagger.qualifiers.Main; 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.SettingObserver; 60 import com.android.systemui.qs.logging.QSLogger; 61 import com.android.systemui.qs.tileimpl.QSTileImpl; 62 import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; 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 SettingObserver mSettingZenDuration; 85 private final DialogLaunchAnimator mDialogLaunchAnimator; 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, DialogLaunchAnimator dialogLaunchAnimator )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 DialogLaunchAnimator dialogLaunchAnimator 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 mDialogLaunchAnimator = dialogLaunchAnimator; 112 mSettingZenDuration = new SettingObserver(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 View view)150 protected void handleClick(@Nullable View view) { 151 // Zen is currently on 152 if (mState.value) { 153 mController.setZen(ZEN_MODE_OFF, null, TAG); 154 } else { 155 enableZenMode(view); 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 View view)165 private void enableZenMode(@Nullable View view) { 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 (view != null) { 187 mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj( 188 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, 189 INTERACTION_JANK_TAG), 190 /* animateBackgroundBoundsChange= */ false); 191 } else { 192 dialog.show(); 193 } 194 }); 195 break; 196 case Settings.Secure.ZEN_DURATION_FOREVER: 197 mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG); 198 break; 199 default: 200 Uri conditionId = ZenModeConfig.toTimeCondition(mContext, zenDuration, 201 mHost.getUserId(), true).id; 202 mController.setZen(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, 203 conditionId, TAG); 204 } 205 } 206 } 207 makeZenModeDialog()208 private Dialog makeZenModeDialog() { 209 AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog, 210 true /* cancelIsNeutral */, 211 mQSZenDialogMetricsLogger).createDialog(); 212 SystemUIDialog.applyFlags(dialog); 213 SystemUIDialog.setShowForAllUsers(dialog, true); 214 SystemUIDialog.registerDismissListener(dialog); 215 SystemUIDialog.setDialogSize(dialog); 216 return dialog; 217 } 218 219 @Override handleSecondaryClick(@ullable View view)220 protected void handleSecondaryClick(@Nullable View view) { 221 handleLongClick(view); 222 } 223 224 @Override getTileLabel()225 public CharSequence getTileLabel() { 226 return mContext.getString(R.string.quick_settings_dnd_label); 227 } 228 229 @Override handleUpdateState(BooleanState state, Object arg)230 protected void handleUpdateState(BooleanState state, Object arg) { 231 if (mController == null) return; 232 final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen(); 233 final boolean newValue = zen != ZEN_MODE_OFF; 234 state.dualTarget = true; 235 state.value = newValue; 236 state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; 237 state.icon = ResourceIcon.get(state.value 238 ? R.drawable.qs_dnd_icon_on 239 : R.drawable.qs_dnd_icon_off); 240 state.label = getTileLabel(); 241 state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext, 242 zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); 243 checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); 244 // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier 245 // to understand. 246 switch (zen) { 247 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 248 state.contentDescription = 249 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " 250 + state.secondaryLabel; 251 break; 252 case Global.ZEN_MODE_NO_INTERRUPTIONS: 253 state.contentDescription = 254 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " + 255 mContext.getString(R.string.accessibility_quick_settings_dnd_none_on) 256 + ", " + state.secondaryLabel; 257 break; 258 case ZEN_MODE_ALARMS: 259 state.contentDescription = 260 mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " + 261 mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on) 262 + ", " + state.secondaryLabel; 263 break; 264 default: 265 state.contentDescription = mContext.getString( 266 R.string.accessibility_quick_settings_dnd); 267 break; 268 } 269 state.dualLabelContentDescription = mContext.getResources().getString( 270 R.string.accessibility_quick_settings_open_settings, getTileLabel()); 271 state.expandedAccessibilityClassName = Switch.class.getName(); 272 state.forceExpandIcon = 273 mSettingZenDuration.getValue() == Settings.Secure.ZEN_DURATION_PROMPT; 274 } 275 276 @Override getMetricsCategory()277 public int getMetricsCategory() { 278 return MetricsEvent.QS_DND; 279 } 280 281 @Override handleSetListening(boolean listening)282 public void handleSetListening(boolean listening) { 283 super.handleSetListening(listening); 284 if (mListening == listening) return; 285 mListening = listening; 286 if (mListening) { 287 Prefs.registerListener(mContext, mPrefListener); 288 } else { 289 Prefs.unregisterListener(mContext, mPrefListener); 290 } 291 mSettingZenDuration.setListening(listening); 292 } 293 294 @Override handleDestroy()295 protected void handleDestroy() { 296 super.handleDestroy(); 297 mSettingZenDuration.setListening(false); 298 } 299 300 @Override isAvailable()301 public boolean isAvailable() { 302 return isVisible(mSharedPreferences); 303 } 304 305 private final OnSharedPreferenceChangeListener mPrefListener 306 = new OnSharedPreferenceChangeListener() { 307 @Override 308 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 309 @Prefs.Key String key) { 310 if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) || 311 Prefs.Key.DND_TILE_VISIBLE.equals(key)) { 312 refreshState(); 313 } 314 } 315 }; 316 317 private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { 318 public void onZenChanged(int zen) { 319 Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile."); 320 refreshState(zen); 321 } 322 }; 323 324 } 325