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