diff options
13 files changed, 755 insertions, 537 deletions
diff --git a/core/java/com/android/internal/globalactions/Action.java b/core/java/com/android/internal/globalactions/Action.java new file mode 100644 index 000000000000..ddb75c1cb11b --- /dev/null +++ b/core/java/com/android/internal/globalactions/Action.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** What each item in the global actions dialog must be able to support. */ +public interface Action { + /** @return Text that will be announced when dialog is created. {@code null} for none. */ + CharSequence getLabelForAccessibility(Context context); + + /** Create the view that represents this action. */ + View create(Context context, View convertView, ViewGroup parent, LayoutInflater inflater); + + /** Called when the action is selected by the user. */ + 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(); + + /** @return {@code true} if the action is enabled for user interaction. */ + boolean isEnabled(); +} diff --git a/core/java/com/android/internal/globalactions/ActionsAdapter.java b/core/java/com/android/internal/globalactions/ActionsAdapter.java new file mode 100644 index 000000000000..c9f01ce9c08b --- /dev/null +++ b/core/java/com/android/internal/globalactions/ActionsAdapter.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import java.util.function.BooleanSupplier; +import java.util.List; + +/** + * 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}. + */ +public class ActionsAdapter extends BaseAdapter { + private final Context mContext; + private final List<Action> mItems; + private final BooleanSupplier mDeviceProvisioned; + private final BooleanSupplier mKeyguardShowing; + + public ActionsAdapter(Context context, List<Action> items, + BooleanSupplier deviceProvisioned, BooleanSupplier keyguardShowing) { + mContext = context; + mItems = items; + mDeviceProvisioned = deviceProvisioned; + mKeyguardShowing = keyguardShowing; + } + + @Override + public int getCount() { + final boolean keyguardShowing = mKeyguardShowing.getAsBoolean(); + final boolean deviceProvisioned = mDeviceProvisioned.getAsBoolean(); + int count = 0; + + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + + if (keyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!deviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + count++; + } + return count; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public Action getItem(int position) { + final boolean keyguardShowing = mKeyguardShowing.getAsBoolean(); + final boolean deviceProvisioned = mDeviceProvisioned.getAsBoolean(); + + int filteredPos = 0; + for (int i = 0; i < mItems.size(); i++) { + final Action action = mItems.get(i); + if (keyguardShowing && !action.showDuringKeyguard()) { + continue; + } + if (!deviceProvisioned && !action.showBeforeProvisioning()) { + continue; + } + if (filteredPos == position) { + return action; + } + filteredPos++; + } + + throw new IllegalArgumentException("position " + position + + " out of range of showable actions" + + ", filtered count=" + getCount() + + ", keyguardshowing=" + keyguardShowing + + ", provisioned=" + deviceProvisioned); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Action action = getItem(position); + return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + } +} diff --git a/core/java/com/android/internal/globalactions/ActionsDialog.java b/core/java/com/android/internal/globalactions/ActionsDialog.java new file mode 100644 index 000000000000..1cca1cc64ca1 --- /dev/null +++ b/core/java/com/android/internal/globalactions/ActionsDialog.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.accessibility.AccessibilityEvent; +import android.view.KeyEvent; +import android.widget.ListView; +import com.android.internal.app.AlertController; + +/** A dialog that lists the given Action items to be user selectable. */ +public final class ActionsDialog extends Dialog implements DialogInterface { + private final Context mContext; + private final AlertController mAlert; + private final ActionsAdapter mAdapter; + + public ActionsDialog(Context context, AlertController.AlertParams params) { + super(context, getDialogTheme(context)); + mContext = getContext(); + mAlert = AlertController.create(mContext, this, getWindow()); + mAdapter = (ActionsAdapter) 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/core/java/com/android/internal/globalactions/LongPressAction.java b/core/java/com/android/internal/globalactions/LongPressAction.java new file mode 100644 index 000000000000..eed4cd9fe9b3 --- /dev/null +++ b/core/java/com/android/internal/globalactions/LongPressAction.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +/** An action that also supports long press. */ +public interface LongPressAction extends Action { + boolean onLongPress(); +} diff --git a/core/java/com/android/internal/globalactions/SinglePressAction.java b/core/java/com/android/internal/globalactions/SinglePressAction.java new file mode 100644 index 000000000000..0b8cd0b2c266 --- /dev/null +++ b/core/java/com/android/internal/globalactions/SinglePressAction.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.internal.R; + +/** A single press action maintains no state, just responds to a press and takes an action. */ +public 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; + } + + @Override + public boolean isEnabled() { + return true; + } + + public String getStatus() { + return null; + } + + @Override + abstract public void onPress(); + + @Override + public CharSequence getLabelForAccessibility(Context context) { + if (mMessage != null) { + return mMessage; + } else { + return context.getString(mMessageResId); + } + } + + @Override + public View create( + Context context, View convertView, ViewGroup parent, LayoutInflater inflater) { + View v = inflater.inflate(R.layout.global_actions_item, parent, false); + + ImageView icon = v.findViewById(R.id.icon); + TextView messageView = v.findViewById(R.id.message); + + TextView statusView = 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(ImageView.ScaleType.CENTER_CROP); + } else if (mIconResId != 0) { + icon.setImageDrawable(context.getDrawable(mIconResId)); + } + if (mMessage != null) { + messageView.setText(mMessage); + } else { + messageView.setText(mMessageResId); + } + + return v; + } +} diff --git a/core/java/com/android/internal/globalactions/ToggleAction.java b/core/java/com/android/internal/globalactions/ToggleAction.java new file mode 100644 index 000000000000..9167958612ea --- /dev/null +++ b/core/java/com/android/internal/globalactions/ToggleAction.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2018 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.internal.globalactions; + +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.internal.R; + +/** + * A toggle action knows whether it is on or off, and displays an icon and status message + * accordingly. + */ +public abstract class ToggleAction implements Action { + private static final String TAG = "ToggleAction"; + + public 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); + } + + @Override + 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 = v.findViewById(R.id.icon); + TextView messageView = v.findViewById(R.id.message); + TextView statusView = 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; + } + + @Override + 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); + } + + @Override + 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; + } + + public abstract void onToggle(boolean on); + + public void updateState(State state) { + mState = state; + } +} diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index 108b6b25b0c4..e1461356cc4c 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -20,33 +20,33 @@ import android.util.Slog; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; -import com.android.server.statusbar.StatusBarManagerInternal; -import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; +import com.android.server.policy.GlobalActionsProvider; -class GlobalActions implements GlobalActionsListener { +class GlobalActions implements GlobalActionsProvider.GlobalActionsListener { private static final String TAG = "GlobalActions"; private static final boolean DEBUG = false; private final Context mContext; - private final StatusBarManagerInternal mStatusBarInternal; + private final GlobalActionsProvider mGlobalActionsProvider; private final Handler mHandler; private final WindowManagerFuncs mWindowManagerFuncs; private LegacyGlobalActions mLegacyGlobalActions; private boolean mKeyguardShowing; private boolean mDeviceProvisioned; - private boolean mStatusBarConnected; + private boolean mGlobalActionsAvailable; private boolean mShowing; public GlobalActions(Context context, WindowManagerFuncs windowManagerFuncs) { mContext = context; mHandler = new Handler(); mWindowManagerFuncs = windowManagerFuncs; - mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class); - // Some form factors do not have a status bar. - if (mStatusBarInternal != null) { - mStatusBarInternal.setGlobalActionsListener(this); + mGlobalActionsProvider = LocalServices.getService(GlobalActionsProvider.class); + if (mGlobalActionsProvider != null) { + mGlobalActionsProvider.setGlobalActionsListener(this); + } else { + Slog.i(TAG, "No GlobalActionsProvider found, defaulting to LegacyGlobalActions"); } } @@ -58,15 +58,15 @@ class GlobalActions implements GlobalActionsListener { public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) { if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned); - if (mStatusBarInternal != null && mStatusBarInternal.isGlobalActionsDisabled()) { + if (mGlobalActionsProvider != null && mGlobalActionsProvider.isGlobalActionsDisabled()) { return; } mKeyguardShowing = keyguardShowing; mDeviceProvisioned = deviceProvisioned; mShowing = true; - if (mStatusBarConnected) { - mStatusBarInternal.showGlobalActions(); + if (mGlobalActionsAvailable) { mHandler.postDelayed(mShowTimeout, 5000); + mGlobalActionsProvider.showGlobalActions(); } else { // SysUI isn't alive, show legacy menu. ensureLegacyCreated(); @@ -88,11 +88,12 @@ class GlobalActions implements GlobalActionsListener { } @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. + public void onGlobalActionsAvailableChanged(boolean available) { + if (DEBUG) Slog.d(TAG, "onGlobalActionsAvailableChanged " + available); + mGlobalActionsAvailable = available; + if (mShowing && !mGlobalActionsAvailable) { + // Global actions provider died but we need to be showing global actions still, show the + // legacy global acrions provider. ensureLegacyCreated(); mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); } diff --git a/services/core/java/com/android/server/policy/GlobalActionsProvider.java b/services/core/java/com/android/server/policy/GlobalActionsProvider.java new file mode 100644 index 000000000000..d414314db2b5 --- /dev/null +++ b/services/core/java/com/android/server/policy/GlobalActionsProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, 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; + +/** Used with LocalServices to add custom handling to global actions. */ +public interface GlobalActionsProvider { + /** @return {@code true} if the dialog is enabled. */ + boolean isGlobalActionsDisabled(); + /** Set the listener that will handle various global actions evetns. */ + void setGlobalActionsListener(GlobalActionsListener listener); + /** Show the global actions UI to the user. */ + void showGlobalActions(); + + /** Listener to pass global actions events back to system. */ + 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 onGlobalActionsAvailableChanged(boolean available); + + /** + * 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/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java index 96d062df0fea..9cb2441d5662 100644 --- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java +++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java @@ -17,18 +17,24 @@ package com.android.server.policy; import com.android.internal.app.AlertController; -import com.android.internal.app.AlertController.AlertParams; +import com.android.internal.globalactions.Action; +import com.android.internal.globalactions.ActionsAdapter; +import com.android.internal.globalactions.ActionsDialog; +import com.android.internal.globalactions.LongPressAction; +import com.android.internal.globalactions.SinglePressAction; +import com.android.internal.globalactions.ToggleAction; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.util.EmergencyAffordanceManager; +import com.android.internal.R; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyProperties; -import com.android.internal.R; +import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.widget.LockPatternUtils; +import com.android.server.policy.PowerAction; +import com.android.server.policy.RestartAction; import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; import android.app.ActivityManager; -import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -40,7 +46,6 @@ 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; @@ -55,23 +60,14 @@ 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; @@ -107,12 +103,12 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn private final Runnable mOnDismiss; private ArrayList<Action> mItems; - private GlobalActionsDialog mDialog; + private ActionsDialog mDialog; private Action mSilentModeAction; private ToggleAction mAirplaneModeOn; - private MyAdapter mAdapter; + private ActionsAdapter mAdapter; private boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; @@ -217,7 +213,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn * Create the global actions dialog. * @return A new dialog. */ - private GlobalActionsDialog createDialog() { + private ActionsDialog createDialog() { // Simple toggle style if there's no vibrator, otherwise use a tri-state if (!mHasVibrator) { mSilentModeAction = new SilentModeToggleAction(); @@ -232,7 +228,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn R.string.global_actions_airplane_mode_off_status) { @Override - void onToggle(boolean on) { + public void onToggle(boolean on) { if (mHasTelephony && Boolean.parseBoolean( SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) { mIsWaitingForEcmExit = true; @@ -282,7 +278,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn continue; } if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) { - mItems.add(new PowerAction()); + mItems.add(new PowerAction(mContext, mWindowManagerFuncs)); } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) { mItems.add(mAirplaneModeOn); } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) { @@ -307,7 +303,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) { mItems.add(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { - mItems.add(new RestartAction()); + mItems.add(new RestartAction(mContext, mWindowManagerFuncs)); } else { Log.e(TAG, "Invalid global action key " + actionKey); } @@ -319,14 +315,15 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn mItems.add(getEmergencyAction()); } - mAdapter = new MyAdapter(); + mAdapter = new ActionsAdapter(mContext, mItems, + () -> mDeviceProvisioned, () -> mKeyguardShowing); - AlertParams params = new AlertParams(mContext); + AlertController.AlertParams params = new AlertController.AlertParams(mContext); params.mAdapter = mAdapter; params.mOnClickListener = this; params.mForceInverseBackground = true; - GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); + ActionsDialog dialog = new ActionsDialog(mContext, params); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.getListView().setItemsCanFocus(true); @@ -350,71 +347,6 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn 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() { @@ -693,330 +625,12 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn 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 { - - @Override - 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; - } - - @Override - 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); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - 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; - } - - @Override - public boolean isEnabled() { - return true; - } - - public String getStatus() { - return null; - } - - @Override - abstract public void onPress(); - - @Override - public CharSequence getLabelForAccessibility(Context context) { - if (mMessage != null) { - return mMessage; - } else { - return context.getString(mMessageResId); - } - } - - @Override - 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); - } - - @Override - 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; - } - - @Override - 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); - } - - @Override - 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, @@ -1027,7 +641,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } @Override - void onToggle(boolean on) { + public void onToggle(boolean on) { if (on) { mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT); } else { @@ -1226,71 +840,4 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn 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/PowerAction.java b/services/core/java/com/android/server/policy/PowerAction.java new file mode 100644 index 000000000000..d2de58e96551 --- /dev/null +++ b/services/core/java/com/android/server/policy/PowerAction.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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 android.content.Context; +import android.os.UserManager; +import com.android.internal.globalactions.LongPressAction; +import com.android.internal.globalactions.SinglePressAction; +import com.android.internal.R; +import com.android.server.policy.WindowManagerPolicy; + +public final class PowerAction extends SinglePressAction implements LongPressAction { + private final Context mContext; + private final WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncs; + + public PowerAction(Context context, + WindowManagerPolicy.WindowManagerFuncs windowManagerFuncs) { + super(R.drawable.ic_lock_power_off, R.string.global_action_power_off); + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + } + + @Override + public boolean onLongPress() { + UserManager um = mContext.getSystemService(UserManager.class); + 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 */); + } +} diff --git a/services/core/java/com/android/server/policy/RestartAction.java b/services/core/java/com/android/server/policy/RestartAction.java new file mode 100644 index 000000000000..0f13da82dad3 --- /dev/null +++ b/services/core/java/com/android/server/policy/RestartAction.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 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 android.content.Context; +import android.os.UserManager; +import com.android.internal.globalactions.LongPressAction; +import com.android.internal.globalactions.SinglePressAction; +import com.android.internal.R; +import com.android.server.policy.WindowManagerPolicy; + +public final class RestartAction extends SinglePressAction implements LongPressAction { + private final Context mContext; + private final WindowManagerPolicy.WindowManagerFuncs mWindowManagerFuncs; + + public RestartAction(Context context, + WindowManagerPolicy.WindowManagerFuncs windowManagerFuncs) { + super(R.drawable.ic_restart, R.string.global_action_restart); + mContext = context; + mWindowManagerFuncs = windowManagerFuncs; + } + + @Override + public boolean onLongPress() { + UserManager um = mContext.getSystemService(UserManager.class); + 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 */); + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 3ab771b7c6ec..095eaa5fde67 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -79,10 +79,6 @@ public interface StatusBarManagerInternal { void setCurrentUser(int newUserId); - boolean isGlobalActionsDisabled(); - void setGlobalActionsListener(GlobalActionsListener listener); - void showGlobalActions(); - /** * Set whether the top app currently hides the statusbar. * @@ -98,23 +94,4 @@ public interface StatusBarManagerInternal { * @param rotation rotation suggestion */ void onProposedRotationChanged(int rotation, boolean isValid); - - 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 343fb91eec53..59fce64b2f2c 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -47,8 +47,8 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.notification.NotificationDelegate; +import com.android.server.policy.GlobalActionsProvider; import com.android.server.power.ShutdownThread; -import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener; import com.android.server.wm.WindowManagerService; import java.io.FileDescriptor; @@ -74,7 +74,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { // for disabling the status bar private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); - private GlobalActionsListener mGlobalActionListener; + private GlobalActionsProvider.GlobalActionsListener mGlobalActionListener; private IBinder mSysUiVisToken = new Binder(); private int mDisabled1 = 0; private int mDisabled2 = 0; @@ -162,6 +162,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { mWindowManager = windowManager; LocalServices.addService(StatusBarManagerInternal.class, mInternalService); + LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider); } /** @@ -375,26 +376,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public boolean isGlobalActionsDisabled() { - return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0; - } - - @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) {} - } - } - - @Override public void setTopAppHidesStatusBar(boolean hidesStatusBar) { if (mBar != null) { try { @@ -427,6 +408,28 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } }; + private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() { + @Override + public boolean isGlobalActionsDisabled() { + return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0; + } + + @Override + public void setGlobalActionsListener(GlobalActionsProvider.GlobalActionsListener listener) { + mGlobalActionListener = listener; + mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null); + } + + @Override + public void showGlobalActions() { + if (mBar != null) { + try { + mBar.showGlobalActionsMenu(); + } catch (RemoteException ex) {} + } + } + }; + // ================================================================================ // From IStatusBarService // ================================================================================ @@ -892,7 +895,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub { private void notifyBarAttachChanged() { mHandler.post(() -> { if (mGlobalActionListener == null) return; - mGlobalActionListener.onStatusBarConnectedChanged(mBar != null); + mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null); }); } |