diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 9cbbc5a48c0da..21e39f6408edd 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -106,6 +106,11 @@ oneway interface IStatusBar */ void showTvPictureInPictureMenu(); + /** + * Shows the global actions menu. + */ + void showGlobalActionsMenu(); + void addQsTile(in ComponentName tile); void remQsTile(in ComponentName tile); void clickQsTile(in ComponentName tile); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 698e387175fbc..20db49970e8f9 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -63,6 +63,15 @@ interface IStatusBarService void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void setSystemUiVisibility(int vis, int mask, String cause); + void onGlobalActionsShown(); + void onGlobalActionsHidden(); + + /** + * These methods are needed for global actions control which the UI is shown in sysui. + */ + void shutdown(); + void reboot(boolean safeMode); + void addTile(in ComponentName tile); void remTile(in ComponentName tile); void clickTile(in ComponentName tile); diff --git a/core/java/com/android/internal/policy/EmergencyAffordanceManager.java b/core/java/com/android/internal/util/EmergencyAffordanceManager.java similarity index 85% rename from core/java/com/android/internal/policy/EmergencyAffordanceManager.java rename to core/java/com/android/internal/util/EmergencyAffordanceManager.java index eb75bd4974342..ba95bfca41eae 100644 --- a/core/java/com/android/internal/policy/EmergencyAffordanceManager.java +++ b/core/java/com/android/internal/util/EmergencyAffordanceManager.java @@ -1,20 +1,18 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.android.internal.policy; +package com.android.internal.util; import android.content.Context; import android.content.Intent; diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java new file mode 100644 index 0000000000000..bb21fb3e64eb2 --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins; + +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.plugins.annotations.DependsOn; +import com.android.systemui.plugins.annotations.ProvidesInterface; + +@ProvidesInterface(action = GlobalActions.ACTION, version = GlobalActions.VERSION) +@DependsOn(target = GlobalActionsManager.class) +public interface GlobalActions extends Plugin { + + String ACTION = "com.android.systemui.action.PLUGIN_GLOBAL_ACTIONS"; + int VERSION = 1; + + void showGlobalActions(GlobalActionsManager manager); + + @ProvidesInterface(version = GlobalActionsManager.VERSION) + public interface GlobalActionsManager { + int VERSION = 1; + + void onGlobalActionsShown(); + void onGlobalActionsHidden(); + + void shutdown(); + void reboot(boolean safeMode); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java index e3ab05d81de7c..943558943821a 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java @@ -37,7 +37,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.widget.LockPatternUtils; -import com.android.internal.policy.EmergencyAffordanceManager; +import com.android.internal.util.EmergencyAffordanceManager; /** * This class implements a smart emergency button that updates itself based diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index be6986750804a..51fa425559efb 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -31,10 +31,12 @@ import android.util.ArraySet; import android.util.Log; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyboard.KeyboardUI; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; import com.android.systemui.pip.PipUI; +import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.OverlayPlugin; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; @@ -84,6 +86,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv VendorServices.class, GarbageMonitor.Service.class, LatencyTester.class, + GlobalActionsComponent.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java new file mode 100644 index 0000000000000..f07027e9a9626 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.globalactions; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Dependency; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.SystemUI; +import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.CommandQueue.Callbacks; +import com.android.systemui.statusbar.policy.ExtensionController; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager { + + private Extension mExtension; + private IStatusBarService mBarService; + + @Override + public void start() { + mBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class) + .withPlugin(GlobalActions.class) + .withDefault(() -> new GlobalActionsImpl(mContext)) + .build(); + SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this); + } + + @Override + public void handleShowGlobalActionsMenu() { + mExtension.get().showGlobalActions(this); + } + + @Override + public void onGlobalActionsShown() { + try { + mBarService.onGlobalActionsShown(); + } catch (RemoteException e) { + } + } + + @Override + public void onGlobalActionsHidden() { + try { + mBarService.onGlobalActionsHidden(); + } catch (RemoteException e) { + } + } + + @Override + public void shutdown() { + try { + mBarService.shutdown(); + } catch (RemoteException e) { + } + } + + @Override + public void reboot(boolean safeMode) { + try { + mBarService.reboot(safeMode); + } catch (RemoteException e) { + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java new file mode 100644 index 0000000000000..206342ede6e09 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -0,0 +1,1260 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.globalactions; + +import com.android.internal.R; +import com.android.internal.app.AlertController; +import com.android.internal.app.AlertController.AlertParams; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; + +import android.app.ActivityManager; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityEvent; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that + * may show depending on whether the keyguard is showing, and whether the device + * is provisioned. + */ +class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { + + static public final String SYSTEM_DIALOG_REASON_KEY = "reason"; + static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions"; + + private static final String TAG = "GlobalActionsDialog"; + + private static final boolean SHOW_SILENT_TOGGLE = true; + + /* Valid settings for global actions keys. + * see config.xml config_globalActionList */ + private static final String GLOBAL_ACTION_KEY_POWER = "power"; + private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; + private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; + private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; + private static final String GLOBAL_ACTION_KEY_USERS = "users"; + private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; + private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; + private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; + private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + + private final Context mContext; + private final GlobalActionsManager mWindowManagerFuncs; + private final AudioManager mAudioManager; + private final IDreamManager mDreamManager; + + private ArrayList mItems; + private ActionsDialog mDialog; + + private Action mSilentModeAction; + private ToggleAction mAirplaneModeOn; + + private MyAdapter mAdapter; + + private boolean mKeyguardShowing = false; + private boolean mDeviceProvisioned = false; + private ToggleAction.State mAirplaneState = ToggleAction.State.Off; + private boolean mIsWaitingForEcmExit = false; + private boolean mHasTelephony; + private boolean mHasVibrator; + private final boolean mShowSilentToggle; + private final EmergencyAffordanceManager mEmergencyAffordanceManager; + + /** + * @param context everything needs a context :( + */ + public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) { + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.getService(DreamService.DREAM_SERVICE)); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + context.registerReceiver(mBroadcastReceiver, filter); + + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + + // get notified of phone state changes + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, + mAirplaneModeObserver); + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = vibrator != null && vibrator.hasVibrator(); + + mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( + R.bool.config_useFixedVolume); + + mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + } + + /** + * Show the global actions dialog (creating if necessary) + * @param keyguardShowing True if keyguard is showing + */ + public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + mKeyguardShowing = keyguardShowing; + mDeviceProvisioned = isDeviceProvisioned; + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + // Show delayed, so that the dismiss of the previous dialog completes + mHandler.sendEmptyMessage(MESSAGE_SHOW); + } else { + handleShow(); + } + } + + private void awakenIfNecessary() { + if (mDreamManager != null) { + try { + if (mDreamManager.isDreaming()) { + mDreamManager.awaken(); + } + } catch (RemoteException e) { + // we tried + } + } + } + + private void handleShow() { + awakenIfNecessary(); + mDialog = createDialog(); + prepareDialog(); + + // If we only have 1 item and it's a simple press action, just do this action. + if (mAdapter.getCount() == 1 + && mAdapter.getItem(0) instanceof SinglePressAction + && !(mAdapter.getItem(0) instanceof LongPressAction)) { + ((SinglePressAction) mAdapter.getItem(0)).onPress(); + } else { + WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); + attrs.setTitle("ActionsDialog"); + mDialog.getWindow().setAttributes(attrs); + mDialog.show(); + mWindowManagerFuncs.onGlobalActionsShown(); + mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); + } + } + + /** + * Create the global actions dialog. + * @return A new dialog. + */ + private ActionsDialog createDialog() { + // Simple toggle style if there's no vibrator, otherwise use a tri-state + if (!mHasVibrator) { + mSilentModeAction = new SilentModeToggleAction(); + } else { + mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler); + } + mAirplaneModeOn = new ToggleAction( + R.drawable.ic_lock_airplane_mode, + R.drawable.ic_lock_airplane_mode_off, + R.string.global_actions_toggle_airplane_mode, + R.string.global_actions_airplane_mode_on_status, + R.string.global_actions_airplane_mode_off_status) { + + void onToggle(boolean on) { + if (mHasTelephony && Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { + mIsWaitingForEcmExit = true; + // Launch ECM exit dialog + Intent ecmDialogIntent = + new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); + ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(ecmDialogIntent); + } else { + changeAirplaneModeSystemSetting(on); + } + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + if (!mHasTelephony) return; + + // In ECM mode airplane state cannot be changed + if (!(Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { + mState = buttonOn ? State.TurningOn : State.TurningOff; + mAirplaneState = mState; + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + onAirplaneModeChanged(); + + mItems = new ArrayList(); + String[] defaultActions = mContext.getResources().getStringArray( + R.array.config_globalActionsList); + + ArraySet addedKeys = new ArraySet(); + for (int i = 0; i < defaultActions.length; i++) { + String actionKey = defaultActions[i]; + if (addedKeys.contains(actionKey)) { + // If we already have added this, don't add it again. + continue; + } + if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { + mItems.add(new PowerAction()); + } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { + mItems.add(mAirplaneModeOn); + } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { + mItems.add(new BugReportAction()); + } + } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { + if (mShowSilentToggle) { + mItems.add(mSilentModeAction); + } + } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { + if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { + addUsersToMenu(mItems); + } + } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { + mItems.add(getSettingsAction()); + } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { + mItems.add(getLockdownAction()); + } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { + mItems.add(getVoiceAssistAction()); + } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { + mItems.add(getAssistAction()); + } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { + mItems.add(new RestartAction()); + } else { + Log.e(TAG, "Invalid global action key " + actionKey); + } + // Add here so we don't add more than one. + addedKeys.add(actionKey); + } + + if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { + mItems.add(getEmergencyAction()); + } + + mAdapter = new MyAdapter(); + + AlertParams params = new AlertParams(mContext); + params.mAdapter = mAdapter; + params.mOnClickListener = this; + params.mForceInverseBackground = true; + + ActionsDialog dialog = new ActionsDialog(mContext, params); + dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. + + dialog.getListView().setItemsCanFocus(true); + dialog.getListView().setLongClickable(true); + dialog.getListView().setOnItemLongClickListener( + new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, + long id) { + final Action action = mAdapter.getItem(position); + if (action instanceof LongPressAction) { + return ((LongPressAction) action).onLongPress(); + } + return false; + } + }); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + + dialog.setOnDismissListener(this); + + return dialog; + } + + private final class PowerAction extends SinglePressAction implements LongPressAction { + private PowerAction() { + super(R.drawable.ic_lock_power_off, + R.string.global_action_power_off); + } + + @Override + public boolean onLongPress() { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.reboot(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + mWindowManagerFuncs.shutdown(); + } + } + + private final class RestartAction extends SinglePressAction implements LongPressAction { + private RestartAction() { + super(R.drawable.ic_restart, R.string.global_action_restart); + } + + @Override + public boolean onLongPress() { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.reboot(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + mWindowManagerFuncs.reboot(false); + } + } + + + private class BugReportAction extends SinglePressAction implements LongPressAction { + + public BugReportAction() { + super(R.drawable.ic_lock_bugreport, R.string.bugreport_title); + } + + @Override + public void onPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + try { + // Take an "interactive" bugreport. + MetricsLogger.action(mContext, + MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); + ActivityManager.getService().requestBugReport( + ActivityManager.BUGREPORT_OPTION_INTERACTIVE); + } catch (RemoteException e) { + } + } + }, 500); + } + + @Override + public boolean onLongPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } + try { + // Take a "full" bugreport. + MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); + ActivityManager.getService().requestBugReport( + ActivityManager.BUGREPORT_OPTION_FULL); + } catch (RemoteException e) { + } + return false; + } + + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public String getStatus() { + return mContext.getString( + R.string.bugreport_status, + Build.VERSION.RELEASE, + Build.ID); + } + } + + private Action getSettingsAction() { + return new SinglePressAction(R.drawable.ic_settings, + R.string.global_action_settings) { + + @Override + public void onPress() { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getEmergencyAction() { + return new SinglePressAction(R.drawable.emergency_icon, + R.string.global_action_emergency) { + @Override + public void onPress() { + mEmergencyAffordanceManager.performEmergencyCall(); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getAssistAction() { + return new SinglePressAction(R.drawable.ic_action_assist_focused, + R.string.global_action_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getVoiceAssistAction() { + return new SinglePressAction(R.drawable.ic_voice_search, + R.string.global_action_voice_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getLockdownAction() { + return new SinglePressAction(R.drawable.ic_lock_lock, + R.string.global_action_lockdown) { + + @Override + public void onPress() { + new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Error while trying to lock device.", e); + } + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + }; + } + + private UserInfo getCurrentUser() { + try { + return ActivityManager.getService().getCurrentUser(); + } catch (RemoteException re) { + return null; + } + } + + private boolean isCurrentUserOwner() { + UserInfo currentUser = getCurrentUser(); + return currentUser == null || currentUser.isPrimary(); + } + + private void addUsersToMenu(ArrayList items) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (um.isUserSwitcherEnabled()) { + List users = um.getUsers(); + UserInfo currentUser = getCurrentUser(); + for (final UserInfo user : users) { + if (user.supportsSwitchToByUser()) { + boolean isCurrentUser = currentUser == null + ? user.id == 0 : (currentUser.id == user.id); + Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) + : null; + SinglePressAction switchToUser = new SinglePressAction( + R.drawable.ic_menu_cc, icon, + (user.name != null ? user.name : "Primary") + + (isCurrentUser ? " \u2714" : "")) { + public void onPress() { + try { + ActivityManager.getService().switchUser(user.id); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't switch user " + re); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + items.add(switchToUser); + } + } + } + } + + private void prepareDialog() { + refreshSilentMode(); + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (mShowSilentToggle) { + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + mContext.registerReceiver(mRingerModeReceiver, filter); + } + } + + private void refreshSilentMode() { + if (!mHasVibrator) { + final boolean silentModeOn = + mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + ((ToggleAction)mSilentModeAction).updateState( + silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + } + } + + /** {@inheritDoc} */ + public void onDismiss(DialogInterface dialog) { + mWindowManagerFuncs.onGlobalActionsHidden(); + if (mShowSilentToggle) { + try { + mContext.unregisterReceiver(mRingerModeReceiver); + } catch (IllegalArgumentException ie) { + // ignore this + Log.w(TAG, ie); + } + } + } + + /** {@inheritDoc} */ + public void onClick(DialogInterface dialog, int which) { + if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { + dialog.dismiss(); + } + mAdapter.getItem(which).onPress(); + } + + /** + * The adapter used for the list within the global actions dialog, taking + * into account whether the keyguard is showing via + * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing} and whether the device is provisioned + * via {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}. + */ + private class MyAdapter extends BaseAdapter { + + public int getCount() { + int count = 0; + + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + count++; + } + return count; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + public Action getItem(int position) { + + int filteredPos = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + + " out of range of showable actions" + + ", filtered count=" + getCount() + + ", keyguardshowing=" + mKeyguardShowing + + ", provisioned=" + mDeviceProvisioned); + } + + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + } + } + + // note: the scheme below made more sense when we were planning on having + // 8 different things in the global actions dialog. seems overkill with + // only 3 items now, but may as well keep this flexible approach so it will + // be easy should someone decide at the last minute to include something + // else, such as 'enable wifi', or 'enable bluetooth' + + /** + * What each item in the global actions dialog must be able to support. + */ + private interface Action { + /** + * @return Text that will be announced when dialog is created. null + * for none. + */ + CharSequence getLabelForAccessibility(Context context); + + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + void onPress(); + + /** + * @return whether this action should appear in the dialog when the keygaurd + * is showing. + */ + boolean showDuringKeyguard(); + + /** + * @return whether this action should appear in the dialog before the + * device is provisioned. + */ + boolean showBeforeProvisioning(); + + boolean isEnabled(); + } + + /** + * An action that also supports long press. + */ + private interface LongPressAction extends Action { + boolean onLongPress(); + } + + /** + * A single press action maintains no state, just responds to a press + * and takes an action. + */ + private static abstract class SinglePressAction implements Action { + private final int mIconResId; + private final Drawable mIcon; + private final int mMessageResId; + private final CharSequence mMessage; + + protected SinglePressAction(int iconResId, int messageResId) { + mIconResId = iconResId; + mMessageResId = messageResId; + mMessage = null; + mIcon = null; + } + + protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + mIcon = icon; + } + + public boolean isEnabled() { + return true; + } + + public String getStatus() { + return null; + } + + abstract public void onPress(); + + public CharSequence getLabelForAccessibility(Context context) { + if (mMessage != null) { + return mMessage; + } else { + return context.getString(mMessageResId); + } + } + + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + + TextView statusView = (TextView) v.findViewById(R.id.status); + final String status = getStatus(); + if (!TextUtils.isEmpty(status)) { + statusView.setText(status); + } else { + statusView.setVisibility(View.GONE); + } + if (mIcon != null) { + icon.setImageDrawable(mIcon); + icon.setScaleType(ScaleType.CENTER_CROP); + } else if (mIconResId != 0) { + icon.setImageDrawable(context.getDrawable(mIconResId)); + } + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } + + return v; + } + } + + /** + * A toggle action knows whether it is on or off, and displays an icon + * and status message accordingly. + */ + private static abstract class ToggleAction implements Action { + + enum State { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); + + private final boolean inTransition; + + State(boolean intermediate) { + inTransition = intermediate; + } + + public boolean inTransition() { + return inTransition; + } + } + + protected State mState = State.Off; + + // prefs + protected int mEnabledIconResId; + protected int mDisabledIconResid; + protected int mMessageResId; + protected int mEnabledStatusMessageResId; + protected int mDisabledStatusMessageResId; + + /** + * @param enabledIconResId The icon for when this action is on. + * @param disabledIconResid The icon for when this action is off. + * @param message The general information message, e.g 'Silent Mode' + * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' + * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' + */ + public ToggleAction(int enabledIconResId, + int disabledIconResid, + int message, + int enabledStatusMessageResId, + int disabledStatusMessageResId) { + mEnabledIconResId = enabledIconResId; + mDisabledIconResid = disabledIconResid; + mMessageResId = message; + mEnabledStatusMessageResId = enabledStatusMessageResId; + mDisabledStatusMessageResId = disabledStatusMessageResId; + } + + /** + * Override to make changes to resource IDs just before creating the + * View. + */ + void willCreate() { + + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return context.getString(mMessageResId); + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + willCreate(); + + View v = inflater.inflate(R + .layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + TextView statusView = (TextView) v.findViewById(R.id.status); + final boolean enabled = isEnabled(); + + if (messageView != null) { + messageView.setText(mMessageResId); + messageView.setEnabled(enabled); + } + + boolean on = ((mState == State.On) || (mState == State.TurningOn)); + if (icon != null) { + icon.setImageDrawable(context.getDrawable( + (on ? mEnabledIconResId : mDisabledIconResid))); + icon.setEnabled(enabled); + } + + if (statusView != null) { + statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + statusView.setVisibility(View.VISIBLE); + statusView.setEnabled(enabled); + } + v.setEnabled(enabled); + + return v; + } + + public final void onPress() { + if (mState.inTransition()) { + Log.w(TAG, "shouldn't be able to toggle when in transition"); + return; + } + + final boolean nowOn = !(mState == State.On); + onToggle(nowOn); + changeStateFromPress(nowOn); + } + + public boolean isEnabled() { + return !mState.inTransition(); + } + + /** + * Implementations may override this if their state can be in on of the intermediate + * states until some notification is received (e.g airplane mode is 'turning off' until + * we know the wireless connections are back online + * @param buttonOn Whether the button was turned on or off + */ + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? State.On : State.Off; + } + + abstract void onToggle(boolean on); + + public void updateState(State state) { + mState = state; + } + } + + private class SilentModeToggleAction extends ToggleAction { + public SilentModeToggleAction() { + super(R.drawable.ic_audio_vol_mute, + R.drawable.ic_audio_vol, + R.string.global_action_toggle_silent_mode, + R.string.global_action_silent_mode_on_status, + R.string.global_action_silent_mode_off_status); + } + + void onToggle(boolean on) { + if (on) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + } + + private static class SilentModeTriStateAction implements Action, View.OnClickListener { + + private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; + + private final AudioManager mAudioManager; + private final Handler mHandler; + private final Context mContext; + + SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { + mAudioManager = audioManager; + mHandler = handler; + mContext = context; + } + + private int ringerModeToIndex(int ringerMode) { + // They just happen to coincide + return ringerMode; + } + + private int indexToRingerMode(int index) { + // They just happen to coincide + return index; + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return null; + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); + + int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); + for (int i = 0; i < 3; i++) { + View itemView = v.findViewById(ITEM_IDS[i]); + itemView.setSelected(selectedIndex == i); + // Set up click handler + itemView.setTag(i); + itemView.setOnClickListener(this); + } + return v; + } + + public void onPress() { + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + + public boolean isEnabled() { + return true; + } + + void willCreate() { + } + + public void onClick(View v) { + if (!(v.getTag() instanceof Integer)) return; + + int index = (Integer) v.getTag(); + mAudioManager.setRingerMode(indexToRingerMode(index)); + mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); + if (!SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { + // Airplane mode can be changed after ECM exits if airplane toggle button + // is pressed during ECM mode + if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && + mIsWaitingForEcmExit) { + mIsWaitingForEcmExit = false; + changeAirplaneModeSystemSetting(true); + } + } + } + }; + + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + if (!mHasTelephony) return; + final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + } + }; + + private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + mHandler.sendEmptyMessage(MESSAGE_REFRESH); + } + } + }; + + private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onAirplaneModeChanged(); + } + }; + + private static final int MESSAGE_DISMISS = 0; + private static final int MESSAGE_REFRESH = 1; + private static final int MESSAGE_SHOW = 2; + private static final int DIALOG_DISMISS_DELAY = 300; // ms + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + case MESSAGE_SHOW: + handleShow(); + break; + } + } + }; + + private void onAirplaneModeChanged() { + // Let the service state callbacks handle the state. + if (mHasTelephony) return; + + boolean airplaneModeOn = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + 0) == 1; + mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + } + + /** + * Change the airplane mode system setting + */ + private void changeAirplaneModeSystemSetting(boolean on) { + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (!mHasTelephony) { + mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; + } + } + + private static final class ActionsDialog extends Dialog implements DialogInterface { + private final Context mContext; + private final AlertController mAlert; + private final MyAdapter mAdapter; + + public ActionsDialog(Context context, AlertParams params) { + super(context, getDialogTheme(context)); + mContext = getContext(); + mAlert = AlertController.create(mContext, this, getWindow()); + mAdapter = (MyAdapter) params.mAdapter; + params.apply(mAlert); + } + + private static int getDialogTheme(Context context) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(R.attr.alertDialogTheme, + outValue, true); + return outValue.resourceId; + } + + @Override + protected void onStart() { + super.setCanceledOnTouchOutside(true); + super.onStart(); + } + + public ListView getListView() { + return mAlert.getListView(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAlert.installContent(); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + for (int i = 0; i < mAdapter.getCount(); ++i) { + CharSequence label = + mAdapter.getItem(i).getLabelForAccessibility(getContext()); + if (label != null) { + event.getText().add(label); + } + } + } + return super.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mAlert.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mAlert.onKeyUp(keyCode, event)) { + return true; + } + return super.onKeyUp(keyCode, event); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java new file mode 100644 index 0000000000000..c1e51b9c0bbbc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.globalactions; + +import com.android.systemui.Dependency; +import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.KeyguardMonitor; + +import android.content.Context; +import android.support.v7.view.ContextThemeWrapper; + +public class GlobalActionsImpl implements GlobalActions { + + private final Context mContext; + private final KeyguardMonitor mKeyguardMonitor; + private final DeviceProvisionedController mDeviceProvisionedController; + private GlobalActionsDialog mGlobalActions; + + public GlobalActionsImpl(Context context) { + mContext = context; + mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); + mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); + } + + @Override + public void showGlobalActions(GlobalActionsManager manager) { + if (mGlobalActions == null) { + final ContextThemeWrapper context = new ContextThemeWrapper(mContext, + android.R.style.Theme_Material_Light); + mGlobalActions = new GlobalActionsDialog(context, manager); + } + mGlobalActions.showDialog(mKeyguardMonitor.isShowing(), + mDeviceProvisionedController.isDeviceProvisioned()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 09b7bec673f40..73bf454ec5f0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -78,6 +78,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_APP_TRANSITION_FINISHED = 31 << MSG_SHIFT; private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT; private static final int MSG_HANDLE_SYSNAV_KEY = 33 << MSG_SHIFT; + private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -134,6 +135,7 @@ public class CommandQueue extends IStatusBar.Stub { default void clickTile(ComponentName tile) { } default void handleSystemNavigationKey(int arg1) { } + default void handleShowGlobalActionsMenu() { } } @VisibleForTesting @@ -414,6 +416,14 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showGlobalActionsMenu() { + synchronized (mLock) { + mHandler.removeMessages(MSG_SHOW_GLOBAL_ACTIONS); + mHandler.obtainMessage(MSG_SHOW_GLOBAL_ACTIONS).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -590,6 +600,11 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).handleSystemNavigationKey(msg.arg1); } break; + case MSG_SHOW_GLOBAL_ACTIONS: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).handleShowGlobalActionsMenu(); + } + break; } } } diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index 335a2309bdcea..17e5e9f0fe69d 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -1,1257 +1,94 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. */ package com.android.server.policy; -import com.android.internal.app.AlertController; -import com.android.internal.app.AlertController.AlertParams; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.policy.EmergencyAffordanceManager; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.telephony.TelephonyProperties; -import com.android.internal.R; -import com.android.internal.widget.LockPatternUtils; +import com.android.server.LocalServices; +import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; -import android.app.ActivityManager; -import android.app.Dialog; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.net.ConnectivityManager; -import android.os.Build; -import android.os.Bundle; import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.os.Vibrator; -import android.provider.Settings; -import android.service.dreams.DreamService; -import android.service.dreams.IDreamManager; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.ArraySet; -import android.util.Log; -import android.util.TypedValue; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; +import android.util.Slog; import android.view.WindowManagerPolicy.WindowManagerFuncs; -import android.view.accessibility.AccessibilityEvent; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ImageView.ScaleType; -import android.widget.ListView; -import android.widget.TextView; -import java.util.ArrayList; -import java.util.List; - -/** - * Helper to show the global actions dialog. Each item is an {@link Action} that - * may show depending on whether the keyguard is showing, and whether the device - * is provisioned. - */ -class GlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { +class GlobalActions implements GlobalActionsListener { private static final String TAG = "GlobalActions"; - - private static final boolean SHOW_SILENT_TOGGLE = true; - - /* Valid settings for global actions keys. - * see config.xml config_globalActionList */ - private static final String GLOBAL_ACTION_KEY_POWER = "power"; - private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; - private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; - private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; - private static final String GLOBAL_ACTION_KEY_USERS = "users"; - private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; - private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; - private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; - private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; - private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + private static final boolean DEBUG = false; private final Context mContext; - private final WindowManagerFuncs mWindowManagerFuncs; - private final AudioManager mAudioManager; - private final IDreamManager mDreamManager; + private final LegacyGlobalActions mLegacyGlobalActions; + private final StatusBarManagerInternal mStatusBarInternal; + private final Handler mHandler; + private boolean mKeyguardShowing; + private boolean mDeviceProvisioned; + private boolean mStatusBarConnected; + private boolean mShowing; - private ArrayList mItems; - private GlobalActionsDialog mDialog; - - private Action mSilentModeAction; - private ToggleAction mAirplaneModeOn; - - private MyAdapter mAdapter; - - private boolean mKeyguardShowing = false; - private boolean mDeviceProvisioned = false; - private ToggleAction.State mAirplaneState = ToggleAction.State.Off; - private boolean mIsWaitingForEcmExit = false; - private boolean mHasTelephony; - private boolean mHasVibrator; - private final boolean mShowSilentToggle; - private final EmergencyAffordanceManager mEmergencyAffordanceManager; - - /** - * @param context everything needs a context :( - */ public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) { mContext = context; - mWindowManagerFuncs = windowManagerFuncs; - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - mDreamManager = IDreamManager.Stub.asInterface( - ServiceManager.getService(DreamService.DREAM_SERVICE)); - - // receive broadcasts - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - filter.addAction(Intent.ACTION_SCREEN_OFF); - filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); - context.registerReceiver(mBroadcastReceiver, filter); - - ConnectivityManager cm = (ConnectivityManager) - context.getSystemService(Context.CONNECTIVITY_SERVICE); - mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); - - // get notified of phone state changes - TelephonyManager telephonyManager = - (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, - mAirplaneModeObserver); - Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); - mHasVibrator = vibrator != null && vibrator.hasVibrator(); - - mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( - com.android.internal.R.bool.config_useFixedVolume); - - mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + mHandler = new Handler(); + mLegacyGlobalActions = new LegacyGlobalActions(context, windowManagerFuncs, + this::onGlobalActionsDismissed); + mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class); + mStatusBarInternal.setGlobalActionsListener(this); } - /** - * Show the global actions dialog (creating if necessary) - * @param keyguardShowing True if keyguard is showing - */ - public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) { + if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned); mKeyguardShowing = keyguardShowing; - mDeviceProvisioned = isDeviceProvisioned; - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - // Show delayed, so that the dismiss of the previous dialog completes - mHandler.sendEmptyMessage(MESSAGE_SHOW); + mDeviceProvisioned = deviceProvisioned; + mShowing = true; + if (mStatusBarConnected) { + mStatusBarInternal.showGlobalActions(); + mHandler.postDelayed(mShowTimeout, 5000); } else { - handleShow(); + // SysUI isn't alive, show legacy menu. + mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); } } - private void awakenIfNecessary() { - if (mDreamManager != null) { - try { - if (mDreamManager.isDreaming()) { - mDreamManager.awaken(); - } - } catch (RemoteException e) { - // we tried - } + @Override + public void onGlobalActionsShown() { + if (DEBUG) Slog.d(TAG, "onGlobalActionsShown"); + // SysUI is showing, remove timeout callbacks. + mHandler.removeCallbacks(mShowTimeout); + } + + @Override + public void onGlobalActionsDismissed() { + if (DEBUG) Slog.d(TAG, "onGlobalActionsDismissed"); + mShowing = false; + } + + @Override + public void onStatusBarConnectedChanged(boolean connected) { + if (DEBUG) Slog.d(TAG, "onStatusBarConnectedChanged " + connected); + mStatusBarConnected = connected; + if (mShowing && !mStatusBarConnected) { + // Status bar died but we need to be showing global actions still, show the legacy. + mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); } } - private void handleShow() { - awakenIfNecessary(); - mDialog = createDialog(); - prepareDialog(); - - // If we only have 1 item and it's a simple press action, just do this action. - if (mAdapter.getCount() == 1 - && mAdapter.getItem(0) instanceof SinglePressAction - && !(mAdapter.getItem(0) instanceof LongPressAction)) { - ((SinglePressAction) mAdapter.getItem(0)).onPress(); - } else { - WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); - attrs.setTitle("GlobalActions"); - mDialog.getWindow().setAttributes(attrs); - mDialog.show(); - mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); - } - } - - /** - * Create the global actions dialog. - * @return A new dialog. - */ - private GlobalActionsDialog createDialog() { - // Simple toggle style if there's no vibrator, otherwise use a tri-state - if (!mHasVibrator) { - mSilentModeAction = new SilentModeToggleAction(); - } else { - mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler); - } - mAirplaneModeOn = new ToggleAction( - R.drawable.ic_lock_airplane_mode, - R.drawable.ic_lock_airplane_mode_off, - R.string.global_actions_toggle_airplane_mode, - R.string.global_actions_airplane_mode_on_status, - R.string.global_actions_airplane_mode_off_status) { - - void onToggle(boolean on) { - if (mHasTelephony && Boolean.parseBoolean( - SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { - mIsWaitingForEcmExit = true; - // Launch ECM exit dialog - Intent ecmDialogIntent = - new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); - ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(ecmDialogIntent); - } else { - changeAirplaneModeSystemSetting(on); - } - } - - @Override - protected void changeStateFromPress(boolean buttonOn) { - if (!mHasTelephony) return; - - // In ECM mode airplane state cannot be changed - if (!(Boolean.parseBoolean( - SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { - mState = buttonOn ? State.TurningOn : State.TurningOff; - mAirplaneState = mState; - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - }; - onAirplaneModeChanged(); - - mItems = new ArrayList(); - String[] defaultActions = mContext.getResources().getStringArray( - com.android.internal.R.array.config_globalActionsList); - - ArraySet addedKeys = new ArraySet(); - for (int i = 0; i < defaultActions.length; i++) { - String actionKey = defaultActions[i]; - if (addedKeys.contains(actionKey)) { - // If we already have added this, don't add it again. - continue; - } - if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - mItems.add(new PowerAction()); - } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { - mItems.add(mAirplaneModeOn); - } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { - if (Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { - mItems.add(new BugReportAction()); - } - } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { - if (mShowSilentToggle) { - mItems.add(mSilentModeAction); - } - } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { - if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { - addUsersToMenu(mItems); - } - } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { - mItems.add(getSettingsAction()); - } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { - mItems.add(getLockdownAction()); - } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { - mItems.add(getVoiceAssistAction()); - } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { - mItems.add(getAssistAction()); - } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { - mItems.add(new RestartAction()); - } else { - Log.e(TAG, "Invalid global action key " + actionKey); - } - // Add here so we don't add more than one. - addedKeys.add(actionKey); - } - - if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { - mItems.add(getEmergencyAction()); - } - - mAdapter = new MyAdapter(); - - AlertParams params = new AlertParams(mContext); - params.mAdapter = mAdapter; - params.mOnClickListener = this; - params.mForceInverseBackground = true; - - GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); - dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. - - dialog.getListView().setItemsCanFocus(true); - dialog.getListView().setLongClickable(true); - dialog.getListView().setOnItemLongClickListener( - new AdapterView.OnItemLongClickListener() { - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, - long id) { - final Action action = mAdapter.getItem(position); - if (action instanceof LongPressAction) { - return ((LongPressAction) action).onLongPress(); - } - return false; - } - }); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - - dialog.setOnDismissListener(this); - - return dialog; - } - - private final class PowerAction extends SinglePressAction implements LongPressAction { - private PowerAction() { - super(com.android.internal.R.drawable.ic_lock_power_off, - R.string.global_action_power_off); - } - + private final Runnable mShowTimeout = new Runnable() { @Override - public boolean onLongPress() { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { - mWindowManagerFuncs.rebootSafeMode(true); - return true; - } - return false; - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - - @Override - public void onPress() { - // shutdown by making sure radio and power are handled accordingly. - mWindowManagerFuncs.shutdown(false /* confirm */); - } - } - - private final class RestartAction extends SinglePressAction implements LongPressAction { - private RestartAction() { - super(R.drawable.ic_restart, R.string.global_action_restart); - } - - @Override - public boolean onLongPress() { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { - mWindowManagerFuncs.rebootSafeMode(true); - return true; - } - return false; - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - - @Override - public void onPress() { - mWindowManagerFuncs.reboot(false /* confirm */); - } - } - - - private class BugReportAction extends SinglePressAction implements LongPressAction { - - public BugReportAction() { - super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title); - } - - @Override - public void onPress() { - // don't actually trigger the bugreport if we are running stability - // tests via monkey - if (ActivityManager.isUserAMonkey()) { - return; - } - // Add a little delay before executing, to give the - // dialog a chance to go away before it takes a - // screenshot. - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - try { - // Take an "interactive" bugreport. - MetricsLogger.action(mContext, - MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); - ActivityManager.getService().requestBugReport( - ActivityManager.BUGREPORT_OPTION_INTERACTIVE); - } catch (RemoteException e) { - } - } - }, 500); - } - - @Override - public boolean onLongPress() { - // don't actually trigger the bugreport if we are running stability - // tests via monkey - if (ActivityManager.isUserAMonkey()) { - return false; - } - try { - // Take a "full" bugreport. - MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); - ActivityManager.getService().requestBugReport( - ActivityManager.BUGREPORT_OPTION_FULL); - } catch (RemoteException e) { - } - return false; - } - - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - - @Override - public String getStatus() { - return mContext.getString( - com.android.internal.R.string.bugreport_status, - Build.VERSION.RELEASE, - Build.ID); - } - } - - private Action getSettingsAction() { - return new SinglePressAction(com.android.internal.R.drawable.ic_settings, - R.string.global_action_settings) { - - @Override - public void onPress() { - Intent intent = new Intent(Settings.ACTION_SETTINGS); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getEmergencyAction() { - return new SinglePressAction(com.android.internal.R.drawable.emergency_icon, - R.string.global_action_emergency) { - @Override - public void onPress() { - mEmergencyAffordanceManager.performEmergencyCall(); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getAssistAction() { - return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused, - R.string.global_action_assist) { - @Override - public void onPress() { - Intent intent = new Intent(Intent.ACTION_ASSIST); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getVoiceAssistAction() { - return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search, - R.string.global_action_voice_assist) { - @Override - public void onPress() { - Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivity(intent); - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return true; - } - }; - } - - private Action getLockdownAction() { - return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, - R.string.global_action_lockdown) { - - @Override - public void onPress() { - new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); - } catch (RemoteException e) { - Log.e(TAG, "Error while trying to lock device.", e); - } - } - - @Override - public boolean showDuringKeyguard() { - return true; - } - - @Override - public boolean showBeforeProvisioning() { - return false; - } - }; - } - - private UserInfo getCurrentUser() { - try { - return ActivityManager.getService().getCurrentUser(); - } catch (RemoteException re) { - return null; - } - } - - private boolean isCurrentUserOwner() { - UserInfo currentUser = getCurrentUser(); - return currentUser == null || currentUser.isPrimary(); - } - - private void addUsersToMenu(ArrayList items) { - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - if (um.isUserSwitcherEnabled()) { - List users = um.getUsers(); - UserInfo currentUser = getCurrentUser(); - for (final UserInfo user : users) { - if (user.supportsSwitchToByUser()) { - boolean isCurrentUser = currentUser == null - ? user.id == 0 : (currentUser.id == user.id); - Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) - : null; - SinglePressAction switchToUser = new SinglePressAction( - com.android.internal.R.drawable.ic_menu_cc, icon, - (user.name != null ? user.name : "Primary") - + (isCurrentUser ? " \u2714" : "")) { - public void onPress() { - try { - ActivityManager.getService().switchUser(user.id); - } catch (RemoteException re) { - Log.e(TAG, "Couldn't switch user " + re); - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - }; - items.add(switchToUser); - } - } - } - } - - private void prepareDialog() { - refreshSilentMode(); - mAirplaneModeOn.updateState(mAirplaneState); - mAdapter.notifyDataSetChanged(); - mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - if (mShowSilentToggle) { - IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(mRingerModeReceiver, filter); - } - } - - private void refreshSilentMode() { - if (!mHasVibrator) { - final boolean silentModeOn = - mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; - ((ToggleAction)mSilentModeAction).updateState( - silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); - } - } - - /** {@inheritDoc} */ - public void onDismiss(DialogInterface dialog) { - if (mShowSilentToggle) { - try { - mContext.unregisterReceiver(mRingerModeReceiver); - } catch (IllegalArgumentException ie) { - // ignore this - Log.w(TAG, ie); - } - } - } - - /** {@inheritDoc} */ - public void onClick(DialogInterface dialog, int which) { - if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { - dialog.dismiss(); - } - mAdapter.getItem(which).onPress(); - } - - /** - * The adapter used for the list within the global actions dialog, taking - * into account whether the keyguard is showing via - * {@link GlobalActions#mKeyguardShowing} and whether the device is provisioned - * via {@link GlobalActions#mDeviceProvisioned}. - */ - private class MyAdapter extends BaseAdapter { - - public int getCount() { - int count = 0; - - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - - if (mKeyguardShowing && !action.showDuringKeyguard()) { - continue; - } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { - continue; - } - count++; - } - return count; - } - - @Override - public boolean isEnabled(int position) { - return getItem(position).isEnabled(); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - public Action getItem(int position) { - - int filteredPos = 0; - for (int i = 0; i < mItems.size(); i++) { - final Action action = mItems.get(i); - if (mKeyguardShowing && !action.showDuringKeyguard()) { - continue; - } - if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { - continue; - } - if (filteredPos == position) { - return action; - } - filteredPos++; - } - - throw new IllegalArgumentException("position " + position - + " out of range of showable actions" - + ", filtered count=" + getCount() - + ", keyguardshowing=" + mKeyguardShowing - + ", provisioned=" + mDeviceProvisioned); - } - - - public long getItemId(int position) { - return position; - } - - public View getView(int position, View convertView, ViewGroup parent) { - Action action = getItem(position); - return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - } - } - - // note: the scheme below made more sense when we were planning on having - // 8 different things in the global actions dialog. seems overkill with - // only 3 items now, but may as well keep this flexible approach so it will - // be easy should someone decide at the last minute to include something - // else, such as 'enable wifi', or 'enable bluetooth' - - /** - * What each item in the global actions dialog must be able to support. - */ - private interface Action { - /** - * @return Text that will be announced when dialog is created. null - * for none. - */ - CharSequence getLabelForAccessibility(Context context); - - View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); - - void onPress(); - - /** - * @return whether this action should appear in the dialog when the keygaurd - * is showing. - */ - boolean showDuringKeyguard(); - - /** - * @return whether this action should appear in the dialog before the - * device is provisioned. - */ - boolean showBeforeProvisioning(); - - boolean isEnabled(); - } - - /** - * An action that also supports long press. - */ - private interface LongPressAction extends Action { - boolean onLongPress(); - } - - /** - * A single press action maintains no state, just responds to a press - * and takes an action. - */ - private static abstract class SinglePressAction implements Action { - private final int mIconResId; - private final Drawable mIcon; - private final int mMessageResId; - private final CharSequence mMessage; - - protected SinglePressAction(int iconResId, int messageResId) { - mIconResId = iconResId; - mMessageResId = messageResId; - mMessage = null; - mIcon = null; - } - - protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { - mIconResId = iconResId; - mMessageResId = 0; - mMessage = message; - mIcon = icon; - } - - public boolean isEnabled() { - return true; - } - - public String getStatus() { - return null; - } - - abstract public void onPress(); - - public CharSequence getLabelForAccessibility(Context context) { - if (mMessage != null) { - return mMessage; - } else { - return context.getString(mMessageResId); - } - } - - public View create( - Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { - View v = inflater.inflate(R.layout.global_actions_item, parent, false); - - ImageView icon = (ImageView) v.findViewById(R.id.icon); - TextView messageView = (TextView) v.findViewById(R.id.message); - - TextView statusView = (TextView) v.findViewById(R.id.status); - final String status = getStatus(); - if (!TextUtils.isEmpty(status)) { - statusView.setText(status); - } else { - statusView.setVisibility(View.GONE); - } - if (mIcon != null) { - icon.setImageDrawable(mIcon); - icon.setScaleType(ScaleType.CENTER_CROP); - } else if (mIconResId != 0) { - icon.setImageDrawable(context.getDrawable(mIconResId)); - } - if (mMessage != null) { - messageView.setText(mMessage); - } else { - messageView.setText(mMessageResId); - } - - return v; - } - } - - /** - * A toggle action knows whether it is on or off, and displays an icon - * and status message accordingly. - */ - private static abstract class ToggleAction implements Action { - - enum State { - Off(false), - TurningOn(true), - TurningOff(true), - On(false); - - private final boolean inTransition; - - State(boolean intermediate) { - inTransition = intermediate; - } - - public boolean inTransition() { - return inTransition; - } - } - - protected State mState = State.Off; - - // prefs - protected int mEnabledIconResId; - protected int mDisabledIconResid; - protected int mMessageResId; - protected int mEnabledStatusMessageResId; - protected int mDisabledStatusMessageResId; - - /** - * @param enabledIconResId The icon for when this action is on. - * @param disabledIconResid The icon for when this action is off. - * @param essage The general information message, e.g 'Silent Mode' - * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' - * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' - */ - public ToggleAction(int enabledIconResId, - int disabledIconResid, - int message, - int enabledStatusMessageResId, - int disabledStatusMessageResId) { - mEnabledIconResId = enabledIconResId; - mDisabledIconResid = disabledIconResid; - mMessageResId = message; - mEnabledStatusMessageResId = enabledStatusMessageResId; - mDisabledStatusMessageResId = disabledStatusMessageResId; - } - - /** - * Override to make changes to resource IDs just before creating the - * View. - */ - void willCreate() { - - } - - @Override - public CharSequence getLabelForAccessibility(Context context) { - return context.getString(mMessageResId); - } - - public View create(Context context, View convertView, ViewGroup parent, - LayoutInflater inflater) { - willCreate(); - - View v = inflater.inflate(R - .layout.global_actions_item, parent, false); - - ImageView icon = (ImageView) v.findViewById(R.id.icon); - TextView messageView = (TextView) v.findViewById(R.id.message); - TextView statusView = (TextView) v.findViewById(R.id.status); - final boolean enabled = isEnabled(); - - if (messageView != null) { - messageView.setText(mMessageResId); - messageView.setEnabled(enabled); - } - - boolean on = ((mState == State.On) || (mState == State.TurningOn)); - if (icon != null) { - icon.setImageDrawable(context.getDrawable( - (on ? mEnabledIconResId : mDisabledIconResid))); - icon.setEnabled(enabled); - } - - if (statusView != null) { - statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); - statusView.setVisibility(View.VISIBLE); - statusView.setEnabled(enabled); - } - v.setEnabled(enabled); - - return v; - } - - public final void onPress() { - if (mState.inTransition()) { - Log.w(TAG, "shouldn't be able to toggle when in transition"); - return; - } - - final boolean nowOn = !(mState == State.On); - onToggle(nowOn); - changeStateFromPress(nowOn); - } - - public boolean isEnabled() { - return !mState.inTransition(); - } - - /** - * Implementations may override this if their state can be in on of the intermediate - * states until some notification is received (e.g airplane mode is 'turning off' until - * we know the wireless connections are back online - * @param buttonOn Whether the button was turned on or off - */ - protected void changeStateFromPress(boolean buttonOn) { - mState = buttonOn ? State.On : State.Off; - } - - abstract void onToggle(boolean on); - - public void updateState(State state) { - mState = state; - } - } - - private class SilentModeToggleAction extends ToggleAction { - public SilentModeToggleAction() { - super(R.drawable.ic_audio_vol_mute, - R.drawable.ic_audio_vol, - R.string.global_action_toggle_silent_mode, - R.string.global_action_silent_mode_on_status, - R.string.global_action_silent_mode_off_status); - } - - void onToggle(boolean on) { - if (on) { - mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); - } else { - mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); - } - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - } - - private static class SilentModeTriStateAction implements Action, View.OnClickListener { - - private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; - - private final AudioManager mAudioManager; - private final Handler mHandler; - private final Context mContext; - - SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { - mAudioManager = audioManager; - mHandler = handler; - mContext = context; - } - - private int ringerModeToIndex(int ringerMode) { - // They just happen to coincide - return ringerMode; - } - - private int indexToRingerMode(int index) { - // They just happen to coincide - return index; - } - - @Override - public CharSequence getLabelForAccessibility(Context context) { - return null; - } - - public View create(Context context, View convertView, ViewGroup parent, - LayoutInflater inflater) { - View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); - - int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); - for (int i = 0; i < 3; i++) { - View itemView = v.findViewById(ITEM_IDS[i]); - itemView.setSelected(selectedIndex == i); - // Set up click handler - itemView.setTag(i); - itemView.setOnClickListener(this); - } - return v; - } - - public void onPress() { - } - - public boolean showDuringKeyguard() { - return true; - } - - public boolean showBeforeProvisioning() { - return false; - } - - public boolean isEnabled() { - return true; - } - - void willCreate() { - } - - public void onClick(View v) { - if (!(v.getTag() instanceof Integer)) return; - - int index = (Integer) v.getTag(); - mAudioManager.setRingerMode(indexToRingerMode(index)); - mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); - } - } - - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - || Intent.ACTION_SCREEN_OFF.equals(action)) { - String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); - if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { - mHandler.sendEmptyMessage(MESSAGE_DISMISS); - } - } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { - // Airplane mode can be changed after ECM exits if airplane toggle button - // is pressed during ECM mode - if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && - mIsWaitingForEcmExit) { - mIsWaitingForEcmExit = false; - changeAirplaneModeSystemSetting(true); - } - } + public void run() { + if (DEBUG) Slog.d(TAG, "Global actions timeout"); + // We haven't heard from sysui, show the legacy dialog. + mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); } }; - - PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onServiceStateChanged(ServiceState serviceState) { - if (!mHasTelephony) return; - final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; - mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; - mAirplaneModeOn.updateState(mAirplaneState); - mAdapter.notifyDataSetChanged(); - } - }; - - private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mHandler.sendEmptyMessage(MESSAGE_REFRESH); - } - } - }; - - private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - onAirplaneModeChanged(); - } - }; - - private static final int MESSAGE_DISMISS = 0; - private static final int MESSAGE_REFRESH = 1; - private static final int MESSAGE_SHOW = 2; - private static final int DIALOG_DISMISS_DELAY = 300; // ms - - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_DISMISS: - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - break; - case MESSAGE_REFRESH: - refreshSilentMode(); - mAdapter.notifyDataSetChanged(); - break; - case MESSAGE_SHOW: - handleShow(); - break; - } - } - }; - - private void onAirplaneModeChanged() { - // Let the service state callbacks handle the state. - if (mHasTelephony) return; - - boolean airplaneModeOn = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, - 0) == 1; - mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; - mAirplaneModeOn.updateState(mAirplaneState); - } - - /** - * Change the airplane mode system setting - */ - private void changeAirplaneModeSystemSetting(boolean on) { - Settings.Global.putInt( - mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, - on ? 1 : 0); - Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("state", on); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL); - if (!mHasTelephony) { - mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; - } - } - - private static final class GlobalActionsDialog extends Dialog implements DialogInterface { - private final Context mContext; - private final AlertController mAlert; - private final MyAdapter mAdapter; - - public GlobalActionsDialog(Context context, AlertParams params) { - super(context, getDialogTheme(context)); - mContext = getContext(); - mAlert = AlertController.create(mContext, this, getWindow()); - mAdapter = (MyAdapter) params.mAdapter; - params.apply(mAlert); - } - - private static int getDialogTheme(Context context) { - TypedValue outValue = new TypedValue(); - context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme, - outValue, true); - return outValue.resourceId; - } - - @Override - protected void onStart() { - super.setCanceledOnTouchOutside(true); - super.onStart(); - } - - public ListView getListView() { - return mAlert.getListView(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mAlert.installContent(); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - for (int i = 0; i < mAdapter.getCount(); ++i) { - CharSequence label = - mAdapter.getItem(i).getLabelForAccessibility(getContext()); - if (label != null) { - event.getText().add(label); - } - } - } - return super.dispatchPopulateAccessibilityEvent(event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mAlert.onKeyDown(keyCode, event)) { - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (mAlert.onKeyUp(keyCode, event)) { - return true; - } - return super.onKeyUp(keyCode, event); - } - } } diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java new file mode 100644 index 0000000000000..a71bc4cd37a1e --- /dev/null +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -0,0 +1,1263 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import com.android.internal.app.AlertController; +import com.android.internal.app.AlertController.AlertParams; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; + +import android.app.ActivityManager; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.os.Vibrator; +import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowManagerPolicy.WindowManagerFuncs; +import android.view.accessibility.AccessibilityEvent; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that + * may show depending on whether the keyguard is showing, and whether the device + * is provisioned. + */ +class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { + + private static final String TAG = "LegacyGlobalActions"; + + private static final boolean SHOW_SILENT_TOGGLE = true; + + /* Valid settings for global actions keys. + * see config.xml config_globalActionList */ + private static final String GLOBAL_ACTION_KEY_POWER = "power"; + private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane"; + private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport"; + private static final String GLOBAL_ACTION_KEY_SILENT = "silent"; + private static final String GLOBAL_ACTION_KEY_USERS = "users"; + private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings"; + private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown"; + private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; + private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; + private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + + private final Context mContext; + private final WindowManagerFuncs mWindowManagerFuncs; + private final AudioManager mAudioManager; + private final IDreamManager mDreamManager; + private final Runnable mOnDismiss; + + private ArrayList mItems; + private GlobalActionsDialog mDialog; + + private Action mSilentModeAction; + private ToggleAction mAirplaneModeOn; + + private MyAdapter mAdapter; + + private boolean mKeyguardShowing = false; + private boolean mDeviceProvisioned = false; + private ToggleAction.State mAirplaneState = ToggleAction.State.Off; + private boolean mIsWaitingForEcmExit = false; + private boolean mHasTelephony; + private boolean mHasVibrator; + private final boolean mShowSilentToggle; + private final EmergencyAffordanceManager mEmergencyAffordanceManager; + + /** + * @param context everything needs a context :( + */ + public LegacyGlobalActions(Context context, WindowManagerFuncs windowManagerFuncs, + Runnable onDismiss) { + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + mOnDismiss = onDismiss; + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mDreamManager = IDreamManager.Stub.asInterface( + ServiceManager.getService(DreamService.DREAM_SERVICE)); + + // receive broadcasts + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); + context.registerReceiver(mBroadcastReceiver, filter); + + ConnectivityManager cm = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + + // get notified of phone state changes + TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, + mAirplaneModeObserver); + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + mHasVibrator = vibrator != null && vibrator.hasVibrator(); + + mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useFixedVolume); + + mEmergencyAffordanceManager = new EmergencyAffordanceManager(context); + } + + /** + * Show the global actions dialog (creating if necessary) + * @param keyguardShowing True if keyguard is showing + */ + public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + mKeyguardShowing = keyguardShowing; + mDeviceProvisioned = isDeviceProvisioned; + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + // Show delayed, so that the dismiss of the previous dialog completes + mHandler.sendEmptyMessage(MESSAGE_SHOW); + } else { + handleShow(); + } + } + + private void awakenIfNecessary() { + if (mDreamManager != null) { + try { + if (mDreamManager.isDreaming()) { + mDreamManager.awaken(); + } + } catch (RemoteException e) { + // we tried + } + } + } + + private void handleShow() { + awakenIfNecessary(); + mDialog = createDialog(); + prepareDialog(); + + // If we only have 1 item and it's a simple press action, just do this action. + if (mAdapter.getCount() == 1 + && mAdapter.getItem(0) instanceof SinglePressAction + && !(mAdapter.getItem(0) instanceof LongPressAction)) { + ((SinglePressAction) mAdapter.getItem(0)).onPress(); + } else { + WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes(); + attrs.setTitle("LegacyGlobalActions"); + mDialog.getWindow().setAttributes(attrs); + mDialog.show(); + mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); + } + } + + /** + * Create the global actions dialog. + * @return A new dialog. + */ + private GlobalActionsDialog createDialog() { + // Simple toggle style if there's no vibrator, otherwise use a tri-state + if (!mHasVibrator) { + mSilentModeAction = new SilentModeToggleAction(); + } else { + mSilentModeAction = new SilentModeTriStateAction(mContext, mAudioManager, mHandler); + } + mAirplaneModeOn = new ToggleAction( + R.drawable.ic_lock_airplane_mode, + R.drawable.ic_lock_airplane_mode_off, + R.string.global_actions_toggle_airplane_mode, + R.string.global_actions_airplane_mode_on_status, + R.string.global_actions_airplane_mode_off_status) { + + void onToggle(boolean on) { + if (mHasTelephony && Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { + mIsWaitingForEcmExit = true; + // Launch ECM exit dialog + Intent ecmDialogIntent = + new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null); + ecmDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(ecmDialogIntent); + } else { + changeAirplaneModeSystemSetting(on); + } + } + + @Override + protected void changeStateFromPress(boolean buttonOn) { + if (!mHasTelephony) return; + + // In ECM mode airplane state cannot be changed + if (!(Boolean.parseBoolean( + SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)))) { + mState = buttonOn ? State.TurningOn : State.TurningOff; + mAirplaneState = mState; + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + onAirplaneModeChanged(); + + mItems = new ArrayList(); + String[] defaultActions = mContext.getResources().getStringArray( + com.android.internal.R.array.config_globalActionsList); + + ArraySet addedKeys = new ArraySet(); + for (int i = 0; i < defaultActions.length; i++) { + String actionKey = defaultActions[i]; + if (addedKeys.contains(actionKey)) { + // If we already have added this, don't add it again. + continue; + } + if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { + mItems.add(new PowerAction()); + } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { + mItems.add(mAirplaneModeOn); + } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { + if (Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) { + mItems.add(new BugReportAction()); + } + } else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) { + if (mShowSilentToggle) { + mItems.add(mSilentModeAction); + } + } else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) { + if (SystemProperties.getBoolean("fw.power_user_switcher", false)) { + addUsersToMenu(mItems); + } + } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) { + mItems.add(getSettingsAction()); + } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) { + mItems.add(getLockdownAction()); + } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { + mItems.add(getVoiceAssistAction()); + } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { + mItems.add(getAssistAction()); + } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { + mItems.add(new RestartAction()); + } else { + Log.e(TAG, "Invalid global action key " + actionKey); + } + // Add here so we don't add more than one. + addedKeys.add(actionKey); + } + + if (mEmergencyAffordanceManager.needsEmergencyAffordance()) { + mItems.add(getEmergencyAction()); + } + + mAdapter = new MyAdapter(); + + AlertParams params = new AlertParams(mContext); + params.mAdapter = mAdapter; + params.mOnClickListener = this; + params.mForceInverseBackground = true; + + GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); + dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. + + dialog.getListView().setItemsCanFocus(true); + dialog.getListView().setLongClickable(true); + dialog.getListView().setOnItemLongClickListener( + new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, + long id) { + final Action action = mAdapter.getItem(position); + if (action instanceof LongPressAction) { + return ((LongPressAction) action).onLongPress(); + } + return false; + } + }); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + + dialog.setOnDismissListener(this); + + return dialog; + } + + private final class PowerAction extends SinglePressAction implements LongPressAction { + private PowerAction() { + super(com.android.internal.R.drawable.ic_lock_power_off, + R.string.global_action_power_off); + } + + @Override + public boolean onLongPress() { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.rebootSafeMode(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + // shutdown by making sure radio and power are handled accordingly. + mWindowManagerFuncs.shutdown(false /* confirm */); + } + } + + private final class RestartAction extends SinglePressAction implements LongPressAction { + private RestartAction() { + super(R.drawable.ic_restart, R.string.global_action_restart); + } + + @Override + public boolean onLongPress() { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) { + mWindowManagerFuncs.rebootSafeMode(true); + return true; + } + return false; + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + + @Override + public void onPress() { + mWindowManagerFuncs.reboot(false /* confirm */); + } + } + + + private class BugReportAction extends SinglePressAction implements LongPressAction { + + public BugReportAction() { + super(com.android.internal.R.drawable.ic_lock_bugreport, R.string.bugreport_title); + } + + @Override + public void onPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return; + } + // Add a little delay before executing, to give the + // dialog a chance to go away before it takes a + // screenshot. + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + try { + // Take an "interactive" bugreport. + MetricsLogger.action(mContext, + MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE); + ActivityManager.getService().requestBugReport( + ActivityManager.BUGREPORT_OPTION_INTERACTIVE); + } catch (RemoteException e) { + } + } + }, 500); + } + + @Override + public boolean onLongPress() { + // don't actually trigger the bugreport if we are running stability + // tests via monkey + if (ActivityManager.isUserAMonkey()) { + return false; + } + try { + // Take a "full" bugreport. + MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL); + ActivityManager.getService().requestBugReport( + ActivityManager.BUGREPORT_OPTION_FULL); + } catch (RemoteException e) { + } + return false; + } + + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public String getStatus() { + return mContext.getString( + com.android.internal.R.string.bugreport_status, + Build.VERSION.RELEASE, + Build.ID); + } + } + + private Action getSettingsAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_settings, + R.string.global_action_settings) { + + @Override + public void onPress() { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getEmergencyAction() { + return new SinglePressAction(com.android.internal.R.drawable.emergency_icon, + R.string.global_action_emergency) { + @Override + public void onPress() { + mEmergencyAffordanceManager.performEmergencyCall(); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getAssistAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_action_assist_focused, + R.string.global_action_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getVoiceAssistAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_voice_search, + R.string.global_action_voice_assist) { + @Override + public void onPress() { + Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return true; + } + }; + } + + private Action getLockdownAction() { + return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock, + R.string.global_action_lockdown) { + + @Override + public void onPress() { + new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL); + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Error while trying to lock device.", e); + } + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + }; + } + + private UserInfo getCurrentUser() { + try { + return ActivityManager.getService().getCurrentUser(); + } catch (RemoteException re) { + return null; + } + } + + private boolean isCurrentUserOwner() { + UserInfo currentUser = getCurrentUser(); + return currentUser == null || currentUser.isPrimary(); + } + + private void addUsersToMenu(ArrayList items) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + if (um.isUserSwitcherEnabled()) { + List users = um.getUsers(); + UserInfo currentUser = getCurrentUser(); + for (final UserInfo user : users) { + if (user.supportsSwitchToByUser()) { + boolean isCurrentUser = currentUser == null + ? user.id == 0 : (currentUser.id == user.id); + Drawable icon = user.iconPath != null ? Drawable.createFromPath(user.iconPath) + : null; + SinglePressAction switchToUser = new SinglePressAction( + com.android.internal.R.drawable.ic_menu_cc, icon, + (user.name != null ? user.name : "Primary") + + (isCurrentUser ? " \u2714" : "")) { + public void onPress() { + try { + ActivityManager.getService().switchUser(user.id); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't switch user " + re); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + }; + items.add(switchToUser); + } + } + } + } + + private void prepareDialog() { + refreshSilentMode(); + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + if (mShowSilentToggle) { + IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + mContext.registerReceiver(mRingerModeReceiver, filter); + } + } + + private void refreshSilentMode() { + if (!mHasVibrator) { + final boolean silentModeOn = + mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; + ((ToggleAction)mSilentModeAction).updateState( + silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off); + } + } + + /** {@inheritDoc} */ + public void onDismiss(DialogInterface dialog) { + if (mOnDismiss != null) { + mOnDismiss.run(); + } + if (mShowSilentToggle) { + try { + mContext.unregisterReceiver(mRingerModeReceiver); + } catch (IllegalArgumentException ie) { + // ignore this + Log.w(TAG, ie); + } + } + } + + /** {@inheritDoc} */ + public void onClick(DialogInterface dialog, int which) { + if (!(mAdapter.getItem(which) instanceof SilentModeTriStateAction)) { + dialog.dismiss(); + } + mAdapter.getItem(which).onPress(); + } + + /** + * The adapter used for the list within the global actions dialog, taking + * into account whether the keyguard is showing via + * {@link LegacyGlobalActions#mKeyguardShowing} and whether the device is provisioned + * via {@link LegacyGlobalActions#mDeviceProvisioned}. + */ + private class MyAdapter extends BaseAdapter { + + public int getCount() { + int count = 0; + + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + count++; + } + return count; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + public Action getItem(int position) { + + int filteredPos = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (mKeyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!mDeviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + + " out of range of showable actions" + + ", filtered count=" + getCount() + + ", keyguardshowing=" + mKeyguardShowing + + ", provisioned=" + mDeviceProvisioned); + } + + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + } + } + + // note: the scheme below made more sense when we were planning on having + // 8 different things in the global actions dialog. seems overkill with + // only 3 items now, but may as well keep this flexible approach so it will + // be easy should someone decide at the last minute to include something + // else, such as 'enable wifi', or 'enable bluetooth' + + /** + * What each item in the global actions dialog must be able to support. + */ + private interface Action { + /** + * @return Text that will be announced when dialog is created. null + * for none. + */ + CharSequence getLabelForAccessibility(Context context); + + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + void onPress(); + + /** + * @return whether this action should appear in the dialog when the keygaurd + * is showing. + */ + boolean showDuringKeyguard(); + + /** + * @return whether this action should appear in the dialog before the + * device is provisioned. + */ + boolean showBeforeProvisioning(); + + boolean isEnabled(); + } + + /** + * An action that also supports long press. + */ + private interface LongPressAction extends Action { + boolean onLongPress(); + } + + /** + * A single press action maintains no state, just responds to a press + * and takes an action. + */ + private static abstract class SinglePressAction implements Action { + private final int mIconResId; + private final Drawable mIcon; + private final int mMessageResId; + private final CharSequence mMessage; + + protected SinglePressAction(int iconResId, int messageResId) { + mIconResId = iconResId; + mMessageResId = messageResId; + mMessage = null; + mIcon = null; + } + + protected SinglePressAction(int iconResId, Drawable icon, CharSequence message) { + mIconResId = iconResId; + mMessageResId = 0; + mMessage = message; + mIcon = icon; + } + + public boolean isEnabled() { + return true; + } + + public String getStatus() { + return null; + } + + abstract public void onPress(); + + public CharSequence getLabelForAccessibility(Context context) { + if (mMessage != null) { + return mMessage; + } else { + return context.getString(mMessageResId); + } + } + + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + + TextView statusView = (TextView) v.findViewById(R.id.status); + final String status = getStatus(); + if (!TextUtils.isEmpty(status)) { + statusView.setText(status); + } else { + statusView.setVisibility(View.GONE); + } + if (mIcon != null) { + icon.setImageDrawable(mIcon); + icon.setScaleType(ScaleType.CENTER_CROP); + } else if (mIconResId != 0) { + icon.setImageDrawable(context.getDrawable(mIconResId)); + } + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } + + return v; + } + } + + /** + * A toggle action knows whether it is on or off, and displays an icon + * and status message accordingly. + */ + private static abstract class ToggleAction implements Action { + + enum State { + Off(false), + TurningOn(true), + TurningOff(true), + On(false); + + private final boolean inTransition; + + State(boolean intermediate) { + inTransition = intermediate; + } + + public boolean inTransition() { + return inTransition; + } + } + + protected State mState = State.Off; + + // prefs + protected int mEnabledIconResId; + protected int mDisabledIconResid; + protected int mMessageResId; + protected int mEnabledStatusMessageResId; + protected int mDisabledStatusMessageResId; + + /** + * @param enabledIconResId The icon for when this action is on. + * @param disabledIconResid The icon for when this action is off. + * @param essage The general information message, e.g 'Silent Mode' + * @param enabledStatusMessageResId The on status message, e.g 'sound disabled' + * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled' + */ + public ToggleAction(int enabledIconResId, + int disabledIconResid, + int message, + int enabledStatusMessageResId, + int disabledStatusMessageResId) { + mEnabledIconResId = enabledIconResId; + mDisabledIconResid = disabledIconResid; + mMessageResId = message; + mEnabledStatusMessageResId = enabledStatusMessageResId; + mDisabledStatusMessageResId = disabledStatusMessageResId; + } + + /** + * Override to make changes to resource IDs just before creating the + * View. + */ + void willCreate() { + + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return context.getString(mMessageResId); + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + willCreate(); + + View v = inflater.inflate(R + .layout.global_actions_item, parent, false); + + ImageView icon = (ImageView) v.findViewById(R.id.icon); + TextView messageView = (TextView) v.findViewById(R.id.message); + TextView statusView = (TextView) v.findViewById(R.id.status); + final boolean enabled = isEnabled(); + + if (messageView != null) { + messageView.setText(mMessageResId); + messageView.setEnabled(enabled); + } + + boolean on = ((mState == State.On) || (mState == State.TurningOn)); + if (icon != null) { + icon.setImageDrawable(context.getDrawable( + (on ? mEnabledIconResId : mDisabledIconResid))); + icon.setEnabled(enabled); + } + + if (statusView != null) { + statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId); + statusView.setVisibility(View.VISIBLE); + statusView.setEnabled(enabled); + } + v.setEnabled(enabled); + + return v; + } + + public final void onPress() { + if (mState.inTransition()) { + Log.w(TAG, "shouldn't be able to toggle when in transition"); + return; + } + + final boolean nowOn = !(mState == State.On); + onToggle(nowOn); + changeStateFromPress(nowOn); + } + + public boolean isEnabled() { + return !mState.inTransition(); + } + + /** + * Implementations may override this if their state can be in on of the intermediate + * states until some notification is received (e.g airplane mode is 'turning off' until + * we know the wireless connections are back online + * @param buttonOn Whether the button was turned on or off + */ + protected void changeStateFromPress(boolean buttonOn) { + mState = buttonOn ? State.On : State.Off; + } + + abstract void onToggle(boolean on); + + public void updateState(State state) { + mState = state; + } + } + + private class SilentModeToggleAction extends ToggleAction { + public SilentModeToggleAction() { + super(R.drawable.ic_audio_vol_mute, + R.drawable.ic_audio_vol, + R.string.global_action_toggle_silent_mode, + R.string.global_action_silent_mode_on_status, + R.string.global_action_silent_mode_off_status); + } + + void onToggle(boolean on) { + if (on) { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); + } else { + mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + } + + private static class SilentModeTriStateAction implements Action, View.OnClickListener { + + private final int[] ITEM_IDS = { R.id.option1, R.id.option2, R.id.option3 }; + + private final AudioManager mAudioManager; + private final Handler mHandler; + private final Context mContext; + + SilentModeTriStateAction(Context context, AudioManager audioManager, Handler handler) { + mAudioManager = audioManager; + mHandler = handler; + mContext = context; + } + + private int ringerModeToIndex(int ringerMode) { + // They just happen to coincide + return ringerMode; + } + + private int indexToRingerMode(int index) { + // They just happen to coincide + return index; + } + + @Override + public CharSequence getLabelForAccessibility(Context context) { + return null; + } + + public View create(Context context, View convertView, ViewGroup parent, + LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false); + + int selectedIndex = ringerModeToIndex(mAudioManager.getRingerMode()); + for (int i = 0; i < 3; i++) { + View itemView = v.findViewById(ITEM_IDS[i]); + itemView.setSelected(selectedIndex == i); + // Set up click handler + itemView.setTag(i); + itemView.setOnClickListener(this); + } + return v; + } + + public void onPress() { + } + + public boolean showDuringKeyguard() { + return true; + } + + public boolean showBeforeProvisioning() { + return false; + } + + public boolean isEnabled() { + return true; + } + + void willCreate() { + } + + public void onClick(View v) { + if (!(v.getTag() instanceof Integer)) return; + + int index = (Integer) v.getTag(); + mAudioManager.setRingerMode(indexToRingerMode(index)); + mHandler.sendEmptyMessageDelayed(MESSAGE_DISMISS, DIALOG_DISMISS_DELAY); + } + } + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) + || Intent.ACTION_SCREEN_OFF.equals(action)) { + String reason = intent.getStringExtra(PhoneWindowManager.SYSTEM_DIALOG_REASON_KEY); + if (!PhoneWindowManager.SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS.equals(reason)) { + mHandler.sendEmptyMessage(MESSAGE_DISMISS); + } + } else if (TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) { + // Airplane mode can be changed after ECM exits if airplane toggle button + // is pressed during ECM mode + if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) && + mIsWaitingForEcmExit) { + mIsWaitingForEcmExit = false; + changeAirplaneModeSystemSetting(true); + } + } + } + }; + + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + if (!mHasTelephony) return; + final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF; + mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + mAdapter.notifyDataSetChanged(); + } + }; + + private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { + mHandler.sendEmptyMessage(MESSAGE_REFRESH); + } + } + }; + + private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onAirplaneModeChanged(); + } + }; + + private static final int MESSAGE_DISMISS = 0; + private static final int MESSAGE_REFRESH = 1; + private static final int MESSAGE_SHOW = 2; + private static final int DIALOG_DISMISS_DELAY = 300; // ms + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_DISMISS: + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + break; + case MESSAGE_REFRESH: + refreshSilentMode(); + mAdapter.notifyDataSetChanged(); + break; + case MESSAGE_SHOW: + handleShow(); + break; + } + } + }; + + private void onAirplaneModeChanged() { + // Let the service state callbacks handle the state. + if (mHasTelephony) return; + + boolean airplaneModeOn = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + 0) == 1; + mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off; + mAirplaneModeOn.updateState(mAirplaneState); + } + + /** + * Change the airplane mode system setting + */ + private void changeAirplaneModeSystemSetting(boolean on) { + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, + on ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("state", on); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (!mHasTelephony) { + mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off; + } + } + + private static final class GlobalActionsDialog extends Dialog implements DialogInterface { + private final Context mContext; + private final AlertController mAlert; + private final MyAdapter mAdapter; + + public GlobalActionsDialog(Context context, AlertParams params) { + super(context, getDialogTheme(context)); + mContext = getContext(); + mAlert = AlertController.create(mContext, this, getWindow()); + mAdapter = (MyAdapter) params.mAdapter; + params.apply(mAlert); + } + + private static int getDialogTheme(Context context) { + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme, + outValue, true); + return outValue.resourceId; + } + + @Override + protected void onStart() { + super.setCanceledOnTouchOutside(true); + super.onStart(); + } + + public ListView getListView() { + return mAlert.getListView(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAlert.installContent(); + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + for (int i = 0; i < mAdapter.getCount(); ++i) { + CharSequence label = + mAdapter.getItem(i).getLabelForAccessibility(getContext()); + if (label != null) { + event.getText().add(label); + } + } + } + return super.dispatchPopulateAccessibilityEvent(event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mAlert.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mAlert.onKeyUp(keyCode, event)) { + return true; + } + return super.onKeyUp(keyCode, event); + } + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index b4467af5aa0e0..135b20d16d639 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -76,4 +76,26 @@ public interface StatusBarManagerInternal { void toggleRecentApps(); void setCurrentUser(int newUserId); + + void setGlobalActionsListener(GlobalActionsListener listener); + void showGlobalActions(); + + public interface GlobalActionsListener { + /** + * Called when sysui starts and connects its status bar, or when the status bar binder + * dies indicating sysui is no longer alive. + */ + void onStatusBarConnectedChanged(boolean connected); + + /** + * Callback from sysui to notify system that global actions has been successfully shown. + */ + void onGlobalActionsShown(); + + /** + * Callback from sysui to notify system that the user has dismissed global actions and + * it no longer needs to be displayed (even if sysui dies). + */ + void onGlobalActionsDismissed(); + } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2dfe20a8e6746..aaaa0805b3c4f 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -40,6 +41,8 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.StatusBarIcon; import com.android.server.LocalServices; import com.android.server.notification.NotificationDelegate; +import com.android.server.power.ShutdownThread; +import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; @@ -65,6 +68,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { // for disabling the status bar private final ArrayList mDisableRecords = new ArrayList(); + private GlobalActionsListener mGlobalActionListener; private IBinder mSysUiVisToken = new Binder(); private int mDisabled1 = 0; private int mDisabled2 = 0; @@ -307,6 +311,21 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } catch (RemoteException ex) {} } } + + @Override + public void setGlobalActionsListener(GlobalActionsListener listener) { + mGlobalActionListener = listener; + mGlobalActionListener.onStatusBarConnectedChanged(mBar != null); + } + + @Override + public void showGlobalActions() { + if (mBar != null) { + try { + mBar.showGlobalActionsMenu(); + } catch (RemoteException ex) {} + } + } }; // ================================================================================ @@ -656,6 +675,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub { Slog.i(TAG, "registerStatusBar bar=" + bar); mBar = bar; + try { + mBar.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + mBar = null; + notifyBarAttachChanged(); + } + }, 0); + } catch (RemoteException e) { + } + notifyBarAttachChanged(); synchronized (mIcons) { for (String slot : mIcons.keySet()) { iconSlots.add(slot); @@ -678,6 +708,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } + private void notifyBarAttachChanged() { + mHandler.post(() -> { + if (mGlobalActionListener == null) return; + mGlobalActionListener.onStatusBarConnectedChanged(mBar != null); + }); + } + /** * @param clearNotificationEffects whether to consider notifications as "shown" and stop * LED, vibration, and ringing @@ -715,6 +752,65 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } } + /** + * Allows the status bar to shutdown the device. + */ + @Override + public void shutdown() { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> + ShutdownThread.shutdown(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, false)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Allows the status bar to reboot the device. + */ + @Override + public void reboot(boolean safeMode) { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> { + if (safeMode) { + ShutdownThread.rebootSafeMode(mContext, false); + } else { + ShutdownThread.reboot(mContext, PowerManager.SHUTDOWN_USER_REQUESTED, false); + } + }); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onGlobalActionsShown() { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + if (mGlobalActionListener == null) return; + mGlobalActionListener.onGlobalActionsShown(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void onGlobalActionsHidden() { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + if (mGlobalActionListener == null) return; + mGlobalActionListener.onGlobalActionsDismissed(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void onNotificationClick(String key) { enforceStatusBarService(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b77000b066303..9ed7c93b52df9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -56,7 +56,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; -import com.android.internal.policy.EmergencyAffordanceManager; +import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.ConcurrentUtils; import com.android.internal.widget.ILockSettings; import com.android.server.accessibility.AccessibilityManagerService;