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