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