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