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