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