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