blob: 14fabc593991e4756866789543e301cdc1c1ea5e [file] [log] [blame]
Jason Monk361915c2017-03-21 20:33:59 -04001/*
2 * Copyright (C) 2008 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.server.policy;
18
19import com.android.internal.app.AlertController;
20import com.android.internal.app.AlertController.AlertParams;
21import com.android.internal.logging.MetricsLogger;
22import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
23import com.android.internal.util.EmergencyAffordanceManager;
24import com.android.internal.telephony.TelephonyIntents;
25import com.android.internal.telephony.TelephonyProperties;
26import com.android.internal.R;
27import com.android.internal.widget.LockPatternUtils;
28
29import android.app.ActivityManager;
30import android.app.Dialog;
31import android.content.BroadcastReceiver;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.pm.UserInfo;
37import android.database.ContentObserver;
38import android.graphics.drawable.Drawable;
39import android.media.AudioManager;
40import android.net.ConnectivityManager;
41import android.os.Build;
42import android.os.Bundle;
43import android.os.Handler;
44import android.os.Message;
45import android.os.RemoteException;
46import android.os.ServiceManager;
47import android.os.SystemProperties;
48import android.os.UserHandle;
49import android.os.UserManager;
50import android.os.Vibrator;
51import android.provider.Settings;
52import android.service.dreams.DreamService;
53import android.service.dreams.IDreamManager;
54import android.telephony.PhoneStateListener;
55import android.telephony.ServiceState;
56import android.telephony.TelephonyManager;
57import android.text.TextUtils;
58import android.util.ArraySet;
59import android.util.Log;
60import android.util.TypedValue;
61import android.view.KeyEvent;
62import android.view.LayoutInflater;
63import android.view.View;
64import android.view.ViewGroup;
65import android.view.WindowManager;
66import android.view.WindowManagerGlobal;
67import android.view.WindowManagerPolicy.WindowManagerFuncs;
68import android.view.accessibility.AccessibilityEvent;
69import android.widget.AdapterView;
70import android.widget.BaseAdapter;
71import android.widget.ImageView;
72import android.widget.ImageView.ScaleType;
73import android.widget.ListView;
74import android.widget.TextView;
75
76import java.util.ArrayList;
77import java.util.List;
78
79/**
80 * Helper to show the global actions dialog. Each item is an {@link Action} that
81 * may show depending on whether the keyguard is showing, and whether the device
82 * is provisioned.
83 */
84class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
85
86 private static final String TAG = "LegacyGlobalActions";
87
88 private static final boolean SHOW_SILENT_TOGGLE = true;
89
90 /* Valid settings for global actions keys.
91 * see config.xml config_globalActionList */
92 private static final String GLOBAL_ACTION_KEY_POWER = "power";
93 private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
94 private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
95 private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
96 private static final String GLOBAL_ACTION_KEY_USERS = "users";
97 private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
98 private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
99 private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
100 private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
101 private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
102
103 private final Context mContext;
104 private final WindowManagerFuncs mWindowManagerFuncs;
105 private final AudioManager mAudioManager;
106 private final IDreamManager mDreamManager;
107 private final Runnable mOnDismiss;
108
109 private ArrayList<Action> mItems;
110 private GlobalActionsDialog mDialog;
111
112 private Action mSilentModeAction;
113 private ToggleAction mAirplaneModeOn;
114
115 private MyAdapter mAdapter;
116
117 private boolean mKeyguardShowing = false;
118 private boolean mDeviceProvisioned = false;
119 private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
120 private boolean mIsWaitingForEcmExit = false;
121 private boolean mHasTelephony;
122 private boolean mHasVibrator;
123 private final boolean mShowSilentToggle;
124 private final EmergencyAffordanceManager mEmergencyAffordanceManager;
125
126 /**
127 * @param context everything needs a context :(
128 */
129 public LegacyGlobalActions(Context context, WindowManagerFuncs windowManagerFuncs,
130 Runnable onDismiss) {
131 mContext = context;
132 mWindowManagerFuncs = windowManagerFuncs;
133 mOnDismiss = onDismiss;
134 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
135 mDreamManager = IDreamManager.Stub.asInterface(
136 ServiceManager.getService(DreamService.DREAM_SERVICE));
137
138 // receive broadcasts
139 IntentFilter filter = new IntentFilter();
140 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
141 filter.addAction(Intent.ACTION_SCREEN_OFF);
142 filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
143 context.registerReceiver(mBroadcastReceiver, filter);
144
145 ConnectivityManager cm = (ConnectivityManager)
146 context.getSystemService(Context.CONNECTIVITY_SERVICE);
147 mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
148
149 // get notified of phone state changes
150 TelephonyManager telephonyManager =
151 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
152 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
153 mContext.getContentResolver().registerContentObserver(
154 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
155 mAirplaneModeObserver);
156 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
157 mHasVibrator = vibrator != null && vibrator.hasVibrator();
158
159 mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
160 com.android.internal.R.bool.config_useFixedVolume);
161
162 mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
163 }
164
165 /**
166 * Show the global actions dialog (creating if necessary)
167 * @param keyguardShowing True if keyguard is showing
168 */
169 public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
170 mKeyguardShowing = keyguardShowing;
171 mDeviceProvisioned = isDeviceProvisioned;
172 if (mDialog != null) {
173 mDialog.dismiss();
174 mDialog = null;
175 // Show delayed, so that the dismiss of the previous dialog completes
176 mHandler.sendEmptyMessage(MESSAGE_SHOW);
177 } else {
178 handleShow();
179 }
180 }
181
182 private void awakenIfNecessary() {
183 if (mDreamManager != null) {
184 try {
185 if (mDreamManager.isDreaming()) {
186 mDreamManager.awaken();
187 }
188 } catch (RemoteException e) {
189 // we tried
190 }
191 }
192 }
193
194 private void handleShow() {
195 awakenIfNecessary();
196 mDialog = createDialog();
197 prepareDialog();
198
199 // If we only have 1 item and it's a simple press action, just do this action.
200 if (mAdapter.getCount() == 1
201 && mAdapter.getItem(0) instanceof SinglePressAction
202 && !(mAdapter.getItem(0) instanceof LongPressAction)) {
203 ((SinglePressAction) mAdapter.getItem(0)).onPress();
204 } else {
205 WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
206 attrs.setTitle("LegacyGlobalActions");
207 mDialog.getWindow().setAttributes(attrs);
208 mDialog.show();
209 mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
210 }
211 }
212
213 /**
214 * Create the global actions dialog.
215 * @return A new dialog.
216 */
217 private GlobalActionsDialog createDialog() {
218 // Simple toggle style if there's no vibrator, otherwise use a tri-state
219 if (!mHasVibrator) {
220 mSilentModeAction = new SilentModeToggleAction();
221 } else {
222 mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler);
223 }
224 mAirplaneModeOn = new ToggleAction(
225 R.drawable.ic_lock_airplane_mode,
226 R.drawable.ic_lock_airplane_mode_off,
227 R.string.global_actions_toggle_airplane_mode,
228 R.string.global_actions_airplane_mode_on_status,
229 R.string.global_actions_airplane_mode_off_status) {
230
Andrew Scullb307e302017-05-19 15:26:30 +0100231 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400232 void onToggle(boolean on) {
233 if (mHasTelephony && Boolean.parseBoolean(
234 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
235 mIsWaitingForEcmExit = true;
236 // Launch ECM exit dialog
237 Intent ecmDialogIntent =
238 new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null);
239 ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
240 mContext.startActivity(ecmDialogIntent);
241 } else {
242 changeAirplaneModeSystemSetting(on);
243 }
244 }
245
246 @Override
247 protected void changeStateFromPress(boolean buttonOn) {
248 if (!mHasTelephony) return;
249
250 // In ECM mode airplane state cannot be changed
251 if (!(Boolean.parseBoolean(
252 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) {
253 mState = buttonOn ? State.TurningOn : State.TurningOff;
254 mAirplaneState = mState;
255 }
256 }
257
Andrew Scullb307e302017-05-19 15:26:30 +0100258 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400259 public boolean showDuringKeyguard() {
260 return true;
261 }
262
Andrew Scullb307e302017-05-19 15:26:30 +0100263 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400264 public boolean showBeforeProvisioning() {
265 return false;
266 }
267 };
268 onAirplaneModeChanged();
269
270 mItems = new ArrayList<Action>();
271 String[] defaultActions = mContext.getResources().getStringArray(
272 com.android.internal.R.array.config_globalActionsList);
273
274 ArraySet<String> addedKeys = new ArraySet<String>();
275 for (int i = 0; i < defaultActions.length; i++) {
276 String actionKey = defaultActions[i];
277 if (addedKeys.contains(actionKey)) {
278 // If we already have added this, don't add it again.
279 continue;
280 }
281 if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
282 mItems.add(new PowerAction());
283 } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
284 mItems.add(mAirplaneModeOn);
285 } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
286 if (Settings.Global.getInt(mContext.getContentResolver(),
287 Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
288 mItems.add(new BugReportAction());
289 }
290 } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
291 if (mShowSilentToggle) {
292 mItems.add(mSilentModeAction);
293 }
294 } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
295 if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
296 addUsersToMenu(mItems);
297 }
298 } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
299 mItems.add(getSettingsAction());
300 } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
301 mItems.add(getLockdownAction());
302 } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
303 mItems.add(getVoiceAssistAction());
304 } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
305 mItems.add(getAssistAction());
306 } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
307 mItems.add(new RestartAction());
308 } else {
309 Log.e(TAG, "Invalid global action key " + actionKey);
310 }
311 // Add here so we don't add more than one.
312 addedKeys.add(actionKey);
313 }
314
315 if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
316 mItems.add(getEmergencyAction());
317 }
318
319 mAdapter = new MyAdapter();
320
321 AlertParams params = new AlertParams(mContext);
322 params.mAdapter = mAdapter;
323 params.mOnClickListener = this;
324 params.mForceInverseBackground = true;
325
326 GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
327 dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
328
329 dialog.getListView().setItemsCanFocus(true);
330 dialog.getListView().setLongClickable(true);
331 dialog.getListView().setOnItemLongClickListener(
332 new AdapterView.OnItemLongClickListener() {
333 @Override
334 public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
335 long id) {
336 final Action action = mAdapter.getItem(position);
337 if (action instanceof LongPressAction) {
338 return ((LongPressAction) action).onLongPress();
339 }
340 return false;
341 }
342 });
343 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
344
345 dialog.setOnDismissListener(this);
346
347 return dialog;
348 }
349
350 private final class PowerAction extends SinglePressAction implements LongPressAction {
351 private PowerAction() {
352 super(com.android.internal.R.drawable.ic_lock_power_off,
353 R.string.global_action_power_off);
354 }
355
356 @Override
357 public boolean onLongPress() {
358 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
359 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
360 mWindowManagerFuncs.rebootSafeMode(true);
361 return true;
362 }
363 return false;
364 }
365
366 @Override
367 public boolean showDuringKeyguard() {
368 return true;
369 }
370
371 @Override
372 public boolean showBeforeProvisioning() {
373 return true;
374 }
375
376 @Override
377 public void onPress() {
378 // shutdown by making sure radio and power are handled accordingly.
379 mWindowManagerFuncs.shutdown(false /* confirm */);
380 }
381 }
382
383 private final class RestartAction extends SinglePressAction implements LongPressAction {
384 private RestartAction() {
385 super(R.drawable.ic_restart, R.string.global_action_restart);
386 }
387
388 @Override
389 public boolean onLongPress() {
390 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
391 if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
392 mWindowManagerFuncs.rebootSafeMode(true);
393 return true;
394 }
395 return false;
396 }
397
398 @Override
399 public boolean showDuringKeyguard() {
400 return true;
401 }
402
403 @Override
404 public boolean showBeforeProvisioning() {
405 return true;
406 }
407
408 @Override
409 public void onPress() {
410 mWindowManagerFuncs.reboot(false /* confirm */);
411 }
412 }
413
414
415 private class BugReportAction extends SinglePressAction implements LongPressAction {
416
417 public BugReportAction() {
418 super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title);
419 }
420
421 @Override
422 public void onPress() {
423 // don't actually trigger the bugreport if we are running stability
424 // tests via monkey
425 if (ActivityManager.isUserAMonkey()) {
426 return;
427 }
428 // Add a little delay before executing, to give the
429 // dialog a chance to go away before it takes a
430 // screenshot.
431 mHandler.postDelayed(new Runnable() {
432 @Override
433 public void run() {
434 try {
435 // Take an "interactive" bugreport.
436 MetricsLogger.action(mContext,
437 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
438 ActivityManager.getService().requestBugReport(
439 ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
440 } catch (RemoteException e) {
441 }
442 }
443 }, 500);
444 }
445
446 @Override
447 public boolean onLongPress() {
448 // don't actually trigger the bugreport if we are running stability
449 // tests via monkey
450 if (ActivityManager.isUserAMonkey()) {
451 return false;
452 }
453 try {
454 // Take a "full" bugreport.
455 MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
456 ActivityManager.getService().requestBugReport(
457 ActivityManager.BUGREPORT_OPTION_FULL);
458 } catch (RemoteException e) {
459 }
460 return false;
461 }
462
Andrew Scullb307e302017-05-19 15:26:30 +0100463 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400464 public boolean showDuringKeyguard() {
465 return true;
466 }
467
468 @Override
469 public boolean showBeforeProvisioning() {
470 return false;
471 }
472
473 @Override
474 public String getStatus() {
475 return mContext.getString(
476 com.android.internal.R.string.bugreport_status,
477 Build.VERSION.RELEASE,
478 Build.ID);
479 }
480 }
481
482 private Action getSettingsAction() {
483 return new SinglePressAction(com.android.internal.R.drawable.ic_settings,
484 R.string.global_action_settings) {
485
486 @Override
487 public void onPress() {
488 Intent intent = new Intent(Settings.ACTION_SETTINGS);
489 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
490 mContext.startActivity(intent);
491 }
492
493 @Override
494 public boolean showDuringKeyguard() {
495 return true;
496 }
497
498 @Override
499 public boolean showBeforeProvisioning() {
500 return true;
501 }
502 };
503 }
504
505 private Action getEmergencyAction() {
506 return new SinglePressAction(com.android.internal.R.drawable.emergency_icon,
507 R.string.global_action_emergency) {
508 @Override
509 public void onPress() {
510 mEmergencyAffordanceManager.performEmergencyCall();
511 }
512
513 @Override
514 public boolean showDuringKeyguard() {
515 return true;
516 }
517
518 @Override
519 public boolean showBeforeProvisioning() {
520 return true;
521 }
522 };
523 }
524
525 private Action getAssistAction() {
526 return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused,
527 R.string.global_action_assist) {
528 @Override
529 public void onPress() {
530 Intent intent = new Intent(Intent.ACTION_ASSIST);
531 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
532 mContext.startActivity(intent);
533 }
534
535 @Override
536 public boolean showDuringKeyguard() {
537 return true;
538 }
539
540 @Override
541 public boolean showBeforeProvisioning() {
542 return true;
543 }
544 };
545 }
546
547 private Action getVoiceAssistAction() {
548 return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search,
549 R.string.global_action_voice_assist) {
550 @Override
551 public void onPress() {
552 Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
553 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
554 mContext.startActivity(intent);
555 }
556
557 @Override
558 public boolean showDuringKeyguard() {
559 return true;
560 }
561
562 @Override
563 public boolean showBeforeProvisioning() {
564 return true;
565 }
566 };
567 }
568
569 private Action getLockdownAction() {
570 return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
571 R.string.global_action_lockdown) {
572
573 @Override
574 public void onPress() {
575 new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
576 try {
577 WindowManagerGlobal.getWindowManagerService().lockNow(null);
578 } catch (RemoteException e) {
579 Log.e(TAG, "Error while trying to lock device.", e);
580 }
581 }
582
583 @Override
584 public boolean showDuringKeyguard() {
585 return true;
586 }
587
588 @Override
589 public boolean showBeforeProvisioning() {
590 return false;
591 }
592 };
593 }
594
595 private UserInfo getCurrentUser() {
596 try {
597 return ActivityManager.getService().getCurrentUser();
598 } catch (RemoteException re) {
599 return null;
600 }
601 }
602
603 private boolean isCurrentUserOwner() {
604 UserInfo currentUser = getCurrentUser();
605 return currentUser == null || currentUser.isPrimary();
606 }
607
608 private void addUsersToMenu(ArrayList<Action> items) {
609 UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
610 if (um.isUserSwitcherEnabled()) {
611 List<UserInfo> users = um.getUsers();
612 UserInfo currentUser = getCurrentUser();
613 for (final UserInfo user : users) {
614 if (user.supportsSwitchToByUser()) {
615 boolean isCurrentUser = currentUser == null
616 ? user.id == 0 : (currentUser.id == user.id);
617 Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath)
618 : null;
619 SinglePressAction switchToUser = new SinglePressAction(
620 com.android.internal.R.drawable.ic_menu_cc, icon,
621 (user.name != null ? user.name : "Primary")
622 + (isCurrentUser ? " \u2714" : "")) {
Andrew Scullb307e302017-05-19 15:26:30 +0100623 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400624 public void onPress() {
625 try {
626 ActivityManager.getService().switchUser(user.id);
627 } catch (RemoteException re) {
628 Log.e(TAG, "Couldn't switch user " + re);
629 }
630 }
631
Andrew Scullb307e302017-05-19 15:26:30 +0100632 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400633 public boolean showDuringKeyguard() {
634 return true;
635 }
636
Andrew Scullb307e302017-05-19 15:26:30 +0100637 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400638 public boolean showBeforeProvisioning() {
639 return false;
640 }
641 };
642 items.add(switchToUser);
643 }
644 }
645 }
646 }
647
648 private void prepareDialog() {
649 refreshSilentMode();
650 mAirplaneModeOn.updateState(mAirplaneState);
651 mAdapter.notifyDataSetChanged();
652 mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
653 if (mShowSilentToggle) {
654 IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
655 mContext.registerReceiver(mRingerModeReceiver, filter);
656 }
657 }
658
659 private void refreshSilentMode() {
660 if (!mHasVibrator) {
661 final boolean silentModeOn =
662 mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
663 ((ToggleAction)mSilentModeAction).updateState(
664 silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
665 }
666 }
667
668 /** {@inheritDoc} */
Andrew Scullb307e302017-05-19 15:26:30 +0100669 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400670 public void onDismiss(DialogInterface dialog) {
671 if (mOnDismiss != null) {
672 mOnDismiss.run();
673 }
674 if (mShowSilentToggle) {
675 try {
676 mContext.unregisterReceiver(mRingerModeReceiver);
677 } catch (IllegalArgumentException ie) {
678 // ignore this
679 Log.w(TAG, ie);
680 }
681 }
682 }
683
684 /** {@inheritDoc} */
Andrew Scullb307e302017-05-19 15:26:30 +0100685 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400686 public void onClick(DialogInterface dialog, int which) {
687 if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) {
688 dialog.dismiss();
689 }
690 mAdapter.getItem(which).onPress();
691 }
692
693 /**
694 * The adapter used for the list within the global actions dialog, taking
695 * into account whether the keyguard is showing via
696 * {@link LegacyGlobalActions#mKeyguardShowing} and whether the device is provisioned
697 * via {@link LegacyGlobalActions#mDeviceProvisioned}.
698 */
699 private class MyAdapter extends BaseAdapter {
700
Andrew Scullb307e302017-05-19 15:26:30 +0100701 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400702 public int getCount() {
703 int count = 0;
704
705 for (int i = 0; i < mItems.size(); i++) {
706 final Action action = mItems.get(i);
707
708 if (mKeyguardShowing && !action.showDuringKeyguard()) {
709 continue;
710 }
711 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
712 continue;
713 }
714 count++;
715 }
716 return count;
717 }
718
719 @Override
720 public boolean isEnabled(int position) {
721 return getItem(position).isEnabled();
722 }
723
724 @Override
725 public boolean areAllItemsEnabled() {
726 return false;
727 }
728
Andrew Scullb307e302017-05-19 15:26:30 +0100729 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400730 public Action getItem(int position) {
731
732 int filteredPos = 0;
733 for (int i = 0; i < mItems.size(); i++) {
734 final Action action = mItems.get(i);
735 if (mKeyguardShowing && !action.showDuringKeyguard()) {
736 continue;
737 }
738 if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
739 continue;
740 }
741 if (filteredPos == position) {
742 return action;
743 }
744 filteredPos++;
745 }
746
747 throw new IllegalArgumentException("position " + position
748 + " out of range of showable actions"
749 + ", filtered count=" + getCount()
750 + ", keyguardshowing=" + mKeyguardShowing
751 + ", provisioned=" + mDeviceProvisioned);
752 }
753
Andrew Scullb307e302017-05-19 15:26:30 +0100754 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400755 public long getItemId(int position) {
756 return position;
757 }
758
Andrew Scullb307e302017-05-19 15:26:30 +0100759 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400760 public View getView(int position, View convertView, ViewGroup parent) {
761 Action action = getItem(position);
762 return action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
763 }
764 }
765
766 // note: the scheme below made more sense when we were planning on having
767 // 8 different things in the global actions dialog. seems overkill with
768 // only 3 items now, but may as well keep this flexible approach so it will
769 // be easy should someone decide at the last minute to include something
770 // else, such as 'enable wifi', or 'enable bluetooth'
771
772 /**
773 * What each item in the global actions dialog must be able to support.
774 */
775 private interface Action {
776 /**
777 * @return Text that will be announced when dialog is created. null
778 * for none.
779 */
780 CharSequence getLabelForAccessibility(Context context);
781
782 View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater);
783
784 void onPress();
785
786 /**
787 * @return whether this action should appear in the dialog when the keygaurd
788 * is showing.
789 */
790 boolean showDuringKeyguard();
791
792 /**
793 * @return whether this action should appear in the dialog before the
794 * device is provisioned.
795 */
796 boolean showBeforeProvisioning();
797
798 boolean isEnabled();
799 }
800
801 /**
802 * An action that also supports long press.
803 */
804 private interface LongPressAction extends Action {
805 boolean onLongPress();
806 }
807
808 /**
809 * A single press action maintains no state, just responds to a press
810 * and takes an action.
811 */
812 private static abstract class SinglePressAction implements Action {
813 private final int mIconResId;
814 private final Drawable mIcon;
815 private final int mMessageResId;
816 private final CharSequence mMessage;
817
818 protected SinglePressAction(int iconResId, int messageResId) {
819 mIconResId = iconResId;
820 mMessageResId = messageResId;
821 mMessage = null;
822 mIcon = null;
823 }
824
825 protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) {
826 mIconResId = iconResId;
827 mMessageResId = 0;
828 mMessage = message;
829 mIcon = icon;
830 }
831
Andrew Scullb307e302017-05-19 15:26:30 +0100832 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400833 public boolean isEnabled() {
834 return true;
835 }
836
837 public String getStatus() {
838 return null;
839 }
840
Andrew Scullb307e302017-05-19 15:26:30 +0100841 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400842 abstract public void onPress();
843
Andrew Scullb307e302017-05-19 15:26:30 +0100844 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400845 public CharSequence getLabelForAccessibility(Context context) {
846 if (mMessage != null) {
847 return mMessage;
848 } else {
849 return context.getString(mMessageResId);
850 }
851 }
852
Andrew Scullb307e302017-05-19 15:26:30 +0100853 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400854 public View create(
855 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
856 View v = inflater.inflate(R.layout.global_actions_item, parent, false);
857
858 ImageView icon = (ImageView) v.findViewById(R.id.icon);
859 TextView messageView = (TextView) v.findViewById(R.id.message);
860
861 TextView statusView = (TextView) v.findViewById(R.id.status);
862 final String status = getStatus();
863 if (!TextUtils.isEmpty(status)) {
864 statusView.setText(status);
865 } else {
866 statusView.setVisibility(View.GONE);
867 }
868 if (mIcon != null) {
869 icon.setImageDrawable(mIcon);
870 icon.setScaleType(ScaleType.CENTER_CROP);
871 } else if (mIconResId != 0) {
872 icon.setImageDrawable(context.getDrawable(mIconResId));
873 }
874 if (mMessage != null) {
875 messageView.setText(mMessage);
876 } else {
877 messageView.setText(mMessageResId);
878 }
879
880 return v;
881 }
882 }
883
884 /**
885 * A toggle action knows whether it is on or off, and displays an icon
886 * and status message accordingly.
887 */
888 private static abstract class ToggleAction implements Action {
889
890 enum State {
891 Off(false),
892 TurningOn(true),
893 TurningOff(true),
894 On(false);
895
896 private final boolean inTransition;
897
898 State(boolean intermediate) {
899 inTransition = intermediate;
900 }
901
902 public boolean inTransition() {
903 return inTransition;
904 }
905 }
906
907 protected State mState = State.Off;
908
909 // prefs
910 protected int mEnabledIconResId;
911 protected int mDisabledIconResid;
912 protected int mMessageResId;
913 protected int mEnabledStatusMessageResId;
914 protected int mDisabledStatusMessageResId;
915
916 /**
917 * @param enabledIconResId The icon for when this action is on.
918 * @param disabledIconResid The icon for when this action is off.
919 * @param essage The general information message, e.g 'Silent Mode'
920 * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
921 * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
922 */
923 public ToggleAction(int enabledIconResId,
924 int disabledIconResid,
925 int message,
926 int enabledStatusMessageResId,
927 int disabledStatusMessageResId) {
928 mEnabledIconResId = enabledIconResId;
929 mDisabledIconResid = disabledIconResid;
930 mMessageResId = message;
931 mEnabledStatusMessageResId = enabledStatusMessageResId;
932 mDisabledStatusMessageResId = disabledStatusMessageResId;
933 }
934
935 /**
936 * Override to make changes to resource IDs just before creating the
937 * View.
938 */
939 void willCreate() {
940
941 }
942
943 @Override
944 public CharSequence getLabelForAccessibility(Context context) {
945 return context.getString(mMessageResId);
946 }
947
Andrew Scullb307e302017-05-19 15:26:30 +0100948 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400949 public View create(Context context, View convertView, ViewGroup parent,
950 LayoutInflater inflater) {
951 willCreate();
952
953 View v = inflater.inflate(R
954 .layout.global_actions_item, parent, false);
955
956 ImageView icon = (ImageView) v.findViewById(R.id.icon);
957 TextView messageView = (TextView) v.findViewById(R.id.message);
958 TextView statusView = (TextView) v.findViewById(R.id.status);
959 final boolean enabled = isEnabled();
960
961 if (messageView != null) {
962 messageView.setText(mMessageResId);
963 messageView.setEnabled(enabled);
964 }
965
966 boolean on = ((mState == State.On) || (mState == State.TurningOn));
967 if (icon != null) {
968 icon.setImageDrawable(context.getDrawable(
969 (on ? mEnabledIconResId : mDisabledIconResid)));
970 icon.setEnabled(enabled);
971 }
972
973 if (statusView != null) {
974 statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
975 statusView.setVisibility(View.VISIBLE);
976 statusView.setEnabled(enabled);
977 }
978 v.setEnabled(enabled);
979
980 return v;
981 }
982
Andrew Scullb307e302017-05-19 15:26:30 +0100983 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400984 public final void onPress() {
985 if (mState.inTransition()) {
986 Log.w(TAG, "shouldn't be able to toggle when in transition");
987 return;
988 }
989
990 final boolean nowOn = !(mState == State.On);
991 onToggle(nowOn);
992 changeStateFromPress(nowOn);
993 }
994
Andrew Scullb307e302017-05-19 15:26:30 +0100995 @Override
Jason Monk361915c2017-03-21 20:33:59 -0400996 public boolean isEnabled() {
997 return !mState.inTransition();
998 }
999
1000 /**
1001 * Implementations may override this if their state can be in on of the intermediate
1002 * states until some notification is received (e.g airplane mode is 'turning off' until
1003 * we know the wireless connections are back online
1004 * @param buttonOn Whether the button was turned on or off
1005 */
1006 protected void changeStateFromPress(boolean buttonOn) {
1007 mState = buttonOn ? State.On : State.Off;
1008 }
1009
1010 abstract void onToggle(boolean on);
1011
1012 public void updateState(State state) {
1013 mState = state;
1014 }
1015 }
1016
1017 private class SilentModeToggleAction extends ToggleAction {
1018 public SilentModeToggleAction() {
1019 super(R.drawable.ic_audio_vol_mute,
1020 R.drawable.ic_audio_vol,
1021 R.string.global_action_toggle_silent_mode,
1022 R.string.global_action_silent_mode_on_status,
1023 R.string.global_action_silent_mode_off_status);
1024 }
1025
Andrew Scullb307e302017-05-19 15:26:30 +01001026 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001027 void onToggle(boolean on) {
1028 if (on) {
1029 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
1030 } else {
1031 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
1032 }
1033 }
1034
Andrew Scullb307e302017-05-19 15:26:30 +01001035 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001036 public boolean showDuringKeyguard() {
1037 return true;
1038 }
1039
Andrew Scullb307e302017-05-19 15:26:30 +01001040 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001041 public boolean showBeforeProvisioning() {
1042 return false;
1043 }
1044 }
1045
1046 private static class SilentModeTriStateAction implements Action, View.OnClickListener {
1047
1048 private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 };
1049
1050 private final AudioManager mAudioManager;
1051 private final Handler mHandler;
1052 private final Context mContext;
1053
1054 SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) {
1055 mAudioManager = audioManager;
1056 mHandler = handler;
1057 mContext = context;
1058 }
1059
1060 private int ringerModeToIndex(int ringerMode) {
1061 // They just happen to coincide
1062 return ringerMode;
1063 }
1064
1065 private int indexToRingerMode(int index) {
1066 // They just happen to coincide
1067 return index;
1068 }
1069
1070 @Override
1071 public CharSequence getLabelForAccessibility(Context context) {
1072 return null;
1073 }
1074
Andrew Scullb307e302017-05-19 15:26:30 +01001075 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001076 public View create(Context context, View convertView, ViewGroup parent,
1077 LayoutInflater inflater) {
1078 View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
1079
1080 int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode());
1081 for (int i = 0; i < 3; i++) {
1082 View itemView = v.findViewById(ITEM_IDS[i]);
1083 itemView.setSelected(selectedIndex == i);
1084 // Set up click handler
1085 itemView.setTag(i);
1086 itemView.setOnClickListener(this);
1087 }
1088 return v;
1089 }
1090
Andrew Scullb307e302017-05-19 15:26:30 +01001091 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001092 public void onPress() {
1093 }
1094
Andrew Scullb307e302017-05-19 15:26:30 +01001095 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001096 public boolean showDuringKeyguard() {
1097 return true;
1098 }
1099
Andrew Scullb307e302017-05-19 15:26:30 +01001100 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001101 public boolean showBeforeProvisioning() {
1102 return false;
1103 }
1104
Andrew Scullb307e302017-05-19 15:26:30 +01001105 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001106 public boolean isEnabled() {
1107 return true;
1108 }
1109
1110 void willCreate() {
1111 }
1112
Andrew Scullb307e302017-05-19 15:26:30 +01001113 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001114 public void onClick(View v) {
1115 if (!(v.getTag() instanceof Integer)) return;
1116
1117 int index = (Integer) v.getTag();
1118 mAudioManager.setRingerMode(indexToRingerMode(index));
1119 mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY);
1120 }
1121 }
1122
1123 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Andrew Scullb307e302017-05-19 15:26:30 +01001124 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001125 public void onReceive(Context context, Intent intent) {
1126 String action = intent.getAction();
1127 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
1128 || Intent.ACTION_SCREEN_OFF.equals(action)) {
1129 String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY);
1130 if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) {
1131 mHandler.sendEmptyMessage(MESSAGE_DISMISS);
1132 }
1133 } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
1134 // Airplane mode can be changed after ECM exits if airplane toggle button
1135 // is pressed during ECM mode
1136 if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
1137 mIsWaitingForEcmExit) {
1138 mIsWaitingForEcmExit = false;
1139 changeAirplaneModeSystemSetting(true);
1140 }
1141 }
1142 }
1143 };
1144
1145 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1146 @Override
1147 public void onServiceStateChanged(ServiceState serviceState) {
1148 if (!mHasTelephony) return;
1149 final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
1150 mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
1151 mAirplaneModeOn.updateState(mAirplaneState);
1152 mAdapter.notifyDataSetChanged();
1153 }
1154 };
1155
1156 private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
1157 @Override
1158 public void onReceive(Context context, Intent intent) {
1159 if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
1160 mHandler.sendEmptyMessage(MESSAGE_REFRESH);
1161 }
1162 }
1163 };
1164
1165 private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
1166 @Override
1167 public void onChange(boolean selfChange) {
1168 onAirplaneModeChanged();
1169 }
1170 };
1171
1172 private static final int MESSAGE_DISMISS = 0;
1173 private static final int MESSAGE_REFRESH = 1;
1174 private static final int MESSAGE_SHOW = 2;
1175 private static final int DIALOG_DISMISS_DELAY = 300; // ms
1176
1177 private Handler mHandler = new Handler() {
Andrew Scullb307e302017-05-19 15:26:30 +01001178 @Override
Jason Monk361915c2017-03-21 20:33:59 -04001179 public void handleMessage(Message msg) {
1180 switch (msg.what) {
1181 case MESSAGE_DISMISS:
1182 if (mDialog != null) {
1183 mDialog.dismiss();
1184 mDialog = null;
1185 }
1186 break;
1187 case MESSAGE_REFRESH:
1188 refreshSilentMode();
1189 mAdapter.notifyDataSetChanged();
1190 break;
1191 case MESSAGE_SHOW:
1192 handleShow();
1193 break;
1194 }
1195 }
1196 };
1197
1198 private void onAirplaneModeChanged() {
1199 // Let the service state callbacks handle the state.
1200 if (mHasTelephony) return;
1201
1202 boolean airplaneModeOn = Settings.Global.getInt(
1203 mContext.getContentResolver(),
1204 Settings.Global.AIRPLANE_MODE_ON,
1205 0) == 1;
1206 mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
1207 mAirplaneModeOn.updateState(mAirplaneState);
1208 }
1209
1210 /**
1211 * Change the airplane mode system setting
1212 */
1213 private void changeAirplaneModeSystemSetting(boolean on) {
1214 Settings.Global.putInt(
1215 mContext.getContentResolver(),
1216 Settings.Global.AIRPLANE_MODE_ON,
1217 on ? 1 : 0);
1218 Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
1219 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1220 intent.putExtra("state", on);
1221 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
1222 if (!mHasTelephony) {
1223 mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
1224 }
1225 }
1226
1227 private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
1228 private final Context mContext;
1229 private final AlertController mAlert;
1230 private final MyAdapter mAdapter;
1231
1232 public GlobalActionsDialog(Context context, AlertParams params) {
1233 super(context, getDialogTheme(context));
1234 mContext = getContext();
1235 mAlert = AlertController.create(mContext, this, getWindow());
1236 mAdapter = (MyAdapter) params.mAdapter;
1237 params.apply(mAlert);
1238 }
1239
1240 private static int getDialogTheme(Context context) {
1241 TypedValue outValue = new TypedValue();
1242 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
1243 outValue, true);
1244 return outValue.resourceId;
1245 }
1246
1247 @Override
1248 protected void onStart() {
1249 super.setCanceledOnTouchOutside(true);
1250 super.onStart();
1251 }
1252
1253 public ListView getListView() {
1254 return mAlert.getListView();
1255 }
1256
1257 @Override
1258 protected void onCreate(Bundle savedInstanceState) {
1259 super.onCreate(savedInstanceState);
1260 mAlert.installContent();
1261 }
1262
1263 @Override
1264 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1265 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
1266 for (int i = 0; i < mAdapter.getCount(); ++i) {
1267 CharSequence label =
1268 mAdapter.getItem(i).getLabelForAccessibility(getContext());
1269 if (label != null) {
1270 event.getText().add(label);
1271 }
1272 }
1273 }
1274 return super.dispatchPopulateAccessibilityEvent(event);
1275 }
1276
1277 @Override
1278 public boolean onKeyDown(int keyCode, KeyEvent event) {
1279 if (mAlert.onKeyDown(keyCode, event)) {
1280 return true;
1281 }
1282 return super.onKeyDown(keyCode, event);
1283 }
1284
1285 @Override
1286 public boolean onKeyUp(int keyCode, KeyEvent event) {
1287 if (mAlert.onKeyUp(keyCode, event)) {
1288 return true;
1289 }
1290 return super.onKeyUp(keyCode, event);
1291 }
1292 }
1293}