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