diff options
8 files changed, 763 insertions, 7 deletions
diff --git a/packages/SystemUI/res/layout/tuner_shortcut_item.xml b/packages/SystemUI/res/layout/tuner_shortcut_item.xml new file mode 100644 index 000000000000..e9eae3b0e1d6 --- /dev/null +++ b/packages/SystemUI/res/layout/tuner_shortcut_item.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="48dp" + android:paddingStart="4dp" + android:paddingEnd="4dp" + android:clickable="true" + android:gravity="center" + android:background="?android:attr/selectableItemBackground"> + + <ImageView + android:id="@android:id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="12dp" /> + + <TextView android:id="@android:id/title" + android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_gravity="center_vertical" + android:textAppearance="?android:attr/textAppearanceListItem" + android:textColor="?android:attr/textColorPrimary" /> + + <com.android.systemui.statusbar.phone.ExpandableIndicator + android:id="@+id/expand" + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="12dp" + android:visibility="gone" /> + +</LinearLayout> diff --git a/packages/SystemUI/res/layout/tuner_shortcut_list.xml b/packages/SystemUI/res/layout/tuner_shortcut_list.xml new file mode 100644 index 000000000000..9aaffb49e652 --- /dev/null +++ b/packages/SystemUI/res/layout/tuner_shortcut_list.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<android.support.v7.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="200dp" + android:gravity="center" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 422431e7b31a..1eb3c5a0d6d3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1752,5 +1752,19 @@ <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] --> <string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string> + <!-- SysUI Tuner: Group of settings for left lock screen affordance [CHAR LIMIT=60] --> + <string name="lockscreen_left">Left</string> + + <!-- SysUI Tuner: Group of settings for right lock screen affordance [CHAR LIMIT=60] --> + <string name="lockscreen_right">Right</string> + + <!-- SysUI Tuner: Switch controlling whether to customize lock screen button [CHAR LIMIT=60] --> + <string name="lockscreen_customize">Customize shortcut</string> + + <!-- SysUI Tuner: Button to select lock screen shortcut [CHAR LIMIT=60] --> + <string name="lockscreen_shortcut">Shortcut</string> + + <!-- SysUI Tuner: Switch to control if device gets unlocked [CHAR LIMIT=60] --> + <string name="lockscreen_unlock">Prompt for password</string> </resources> diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml new file mode 100644 index 000000000000..73e29af42821 --- /dev/null +++ b/packages/SystemUI/res/xml/lockscreen_settings.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" + android:title="@string/other"> + + <PreferenceCategory + android:key="left" + android:title="@string/lockscreen_left"> + + <SwitchPreference + android:key="customize" + android:title="@string/lockscreen_customize" /> + + <Preference + android:key="shortcut" + android:title="@string/lockscreen_shortcut" /> + + <com.android.systemui.tuner.TunerSwitch + android:key="sysui_keyguard_left_unlock" + android:title="@string/lockscreen_unlock" + sysui:defValue="true" /> + + </PreferenceCategory> + + <PreferenceCategory + android:key="right" + android:title="@string/lockscreen_right"> + + <SwitchPreference + android:key="customize" + android:title="@string/lockscreen_customize" /> + + <Preference + android:key="shortcut" + android:title="@string/lockscreen_shortcut" /> + + <com.android.systemui.tuner.TunerSwitch + android:key="sysui_keyguard_right_unlock" + android:title="@string/lockscreen_unlock" + sysui:defValue="true" /> + + </PreferenceCategory> + +</PreferenceScreen> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 59a10dabe967..5afab7ba0cc5 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -158,6 +158,11 @@ --> <Preference + android:key="lockscreen" + android:title="@string/accessibility_desc_lock_screen" + android:fragment="com.android.systemui.tuner.LockscreenFragment" /> + + <Preference android:key="other" android:title="@string/other" android:fragment="com.android.systemui.tuner.OtherPrefs" /> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 79120d889c2f..7dcf8117c5cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -18,6 +18,11 @@ package com.android.systemui.statusbar.phone; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON; +import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK; +import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON; +import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK; +import static com.android.systemui.tuner.LockscreenFragment.getIntentButton; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -31,7 +36,9 @@ import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.ColorStateList; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; @@ -43,6 +50,7 @@ import android.os.UserHandle; import android.provider.MediaStore; import android.service.media.CameraPrewarmService; import android.telecom.TelecomManager; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; @@ -74,6 +82,9 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.PreviewInflater; +import com.android.systemui.tuner.LockscreenFragment; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status @@ -81,7 +92,8 @@ import com.android.systemui.statusbar.policy.PreviewInflater; */ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, UnlockMethodCache.OnUnlockMethodChangedListener, - AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener { + AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener, + Tunable { final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; @@ -148,7 +160,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private Drawable mLeftAssistIcon; private IntentButton mRightButton = new DefaultRightButton(); + private IntentButton mRightDefault = mRightButton; + private IntentButton mRightPlugin; + private String mRightButtonStr; private IntentButton mLeftButton = new DefaultLeftButton(); + private IntentButton mLeftDefault = mLeftButton; + private IntentButton mLeftPlugin; + private String mLeftButtonStr; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -245,6 +263,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mRightListener, IntentButtonProvider.VERSION, false /* Only allow one */); PluginManager.getInstance(getContext()).addPluginListener(LEFT_BUTTON_PLUGIN, mLeftListener, IntentButtonProvider.VERSION, false /* Only allow one */); + TunerService.get(getContext()).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON, + LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON); } @Override @@ -252,6 +272,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL super.onDetachedFromWindow(); PluginManager.getInstance(getContext()).removePluginListener(mRightListener); PluginManager.getInstance(getContext()).removePluginListener(mLeftListener); + TunerService.get(getContext()).removeTunable(this); } private void initAccessibility() { @@ -552,8 +573,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (mPhoneStatusBar.isKeyguardCurrentlySecure()) { AsyncTask.execute(runnable); } else { + boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr) + && TunerService.get(getContext()).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0; mPhoneStatusBar.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */, - false /* dismissShade */, false /* afterKeyguardGone */, true /* deferred */); + dismissShade, false /* afterKeyguardGone */, true /* deferred */); } } @@ -571,7 +594,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }); } else { - mActivityStarter.startActivity(mLeftButton.getIntent(), false /* dismissShade */); + boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr) + && TunerService.get(getContext()).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0; + mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade); } } @@ -763,15 +788,33 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL inflateCameraPreview(); } + @Override + public void onTuningChanged(String key, String newValue) { + if (LockscreenFragment.LOCKSCREEN_LEFT_BUTTON.equals(key)) { + mLeftButtonStr = newValue; + mLeftIsVoiceAssist = TextUtils.isEmpty(mLeftButtonStr) && mLeftPlugin == null; + mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault); + updateLeftAffordance(); + } else { + mRightButtonStr = newValue; + mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault); + updateRightAffordanceIcon(); + updateCameraVisibility(); + inflateCameraPreview(); + } + } + private void setRightButton(IntentButton button) { - mRightButton = button; + mRightPlugin = button; + mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault); updateRightAffordanceIcon(); updateCameraVisibility(); inflateCameraPreview(); } private void setLeftButton(IntentButton button) { - mLeftButton = button; + mLeftPlugin = button; + mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault); mLeftIsVoiceAssist = false; updateLeftAffordance(); } @@ -785,7 +828,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onPluginDisconnected(IntentButtonProvider plugin) { - setRightButton(new DefaultRightButton()); + setRightButton(null); } }; @@ -798,7 +841,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override public void onPluginDisconnected(IntentButtonProvider plugin) { - setLeftButton(new DefaultLeftButton()); + setLeftButton(null); } }; diff --git a/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java new file mode 100644 index 000000000000..41786b5e53e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/LockscreenFragment.java @@ -0,0 +1,404 @@ +/* + * 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.tuner; + +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Process; +import android.support.v14.preference.PreferenceFragment; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ViewHolder; +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.systemui.R; +import com.android.systemui.plugins.IntentButtonProvider.IntentButton; +import com.android.systemui.statusbar.phone.ExpandableIndicator; +import com.android.systemui.tuner.ShortcutParser.Shortcut; +import com.android.systemui.tuner.TunerService.Tunable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class LockscreenFragment extends PreferenceFragment { + + private static final String KEY_LEFT = "left"; + private static final String KEY_RIGHT = "right"; + private static final String KEY_CUSTOMIZE = "customize"; + private static final String KEY_SHORTCUT = "shortcut"; + + public static final String LOCKSCREEN_LEFT_BUTTON = "sysui_keyguard_left"; + public static final String LOCKSCREEN_LEFT_UNLOCK = "sysui_keyguard_left_unlock"; + public static final String LOCKSCREEN_RIGHT_BUTTON = "sysui_keyguard_right"; + public static final String LOCKSCREEN_RIGHT_UNLOCK = "sysui_keyguard_right_unlock"; + + private final ArrayList<Tunable> mTunables = new ArrayList<>(); + private TunerService mTunerService; + private Handler mHandler; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + mTunerService = TunerService.get(getContext()); + mHandler = new Handler(); + addPreferencesFromResource(R.xml.lockscreen_settings); + setupGroup((PreferenceGroup) findPreference(KEY_LEFT), LOCKSCREEN_LEFT_BUTTON, + LOCKSCREEN_LEFT_UNLOCK); + setupGroup((PreferenceGroup) findPreference(KEY_RIGHT), LOCKSCREEN_RIGHT_BUTTON, + LOCKSCREEN_RIGHT_UNLOCK); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mTunables.forEach(t -> mTunerService.removeTunable(t)); + } + + private void setupGroup(PreferenceGroup group, String buttonSetting, String unlockKey) { + SwitchPreference customize = (SwitchPreference) group.findPreference(KEY_CUSTOMIZE); + Preference shortcut = group.findPreference(KEY_SHORTCUT); + SwitchPreference unlock = (SwitchPreference) group.findPreference(unlockKey); + addTunable((k, v) -> { + boolean visible = v != null; + customize.setChecked(visible); + shortcut.setVisible(visible); + unlock.setVisible(visible); + if (visible) { + setSummary(shortcut, v); + } + }, buttonSetting); + customize.setOnPreferenceChangeListener((preference, newValue) -> { + boolean hasSetting = mTunerService.getValue(buttonSetting) != null; + if (hasSetting != (boolean) newValue) { + mHandler.post(() -> mTunerService.setValue(buttonSetting, hasSetting ? null : "")); + } + return true; + }); + shortcut.setOnPreferenceClickListener(preference -> { + showSelectDialog(buttonSetting); + return true; + }); + } + + private void showSelectDialog(String buttonSetting) { + RecyclerView v = (RecyclerView) LayoutInflater.from(getContext()) + .inflate(R.layout.tuner_shortcut_list, null); + v.setLayoutManager(new LinearLayoutManager(getContext())); + AlertDialog dialog = new Builder(getContext()) + .setView(v) + .show(); + Adapter adapter = new Adapter(getContext(), item -> { + mTunerService.setValue(buttonSetting, item.getSettingValue()); + dialog.dismiss(); + }); + LauncherApps apps = getContext().getSystemService(LauncherApps.class); + List<LauncherActivityInfo> activities = apps.getActivityList(null, + Process.myUserHandle()); + + activities.forEach(info -> { + App app = new App(getContext(), info); + try { + new ShortcutParser(getContext(), info.getComponentName()).getShortcuts().forEach( + shortcut -> app.addChild(new StaticShortcut(getContext(), shortcut))); + } catch (NameNotFoundException e) { + } + adapter.addItem(app); + }); + + v.setAdapter(adapter); + } + + private void setSummary(Preference shortcut, String value) { + if (value.contains("::")) { + Shortcut info = getShortcutInfo(getContext(), value); + shortcut.setSummary(info != null ? info.label : null); + } else if (value.contains("/")) { + ActivityInfo info = getActivityinfo(getContext(), value); + shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager()) + : null); + } else { + shortcut.setSummary(null); + } + } + + private void addTunable(Tunable t, String... keys) { + mTunables.add(t); + mTunerService.addTunable(t, keys); + } + + public static ActivityInfo getActivityinfo(Context context, String value) { + ComponentName component = ComponentName.unflattenFromString(value); + try { + return context.getPackageManager().getActivityInfo(component, 0); + } catch (NameNotFoundException e) { + return null; + } + } + + public static Shortcut getShortcutInfo(Context context, String value) { + return Shortcut.create(context, value); + } + + public static class Holder extends ViewHolder { + public final ImageView icon; + public final TextView title; + public final ExpandableIndicator expand; + + public Holder(View itemView) { + super(itemView); + icon = (ImageView) itemView.findViewById(android.R.id.icon); + title = (TextView) itemView.findViewById(android.R.id.title); + expand = (ExpandableIndicator) itemView.findViewById(R.id.expand); + } + } + + private static class StaticShortcut extends Item { + + private final Context mContext; + private final Shortcut mShortcut; + + + public StaticShortcut(Context context, Shortcut shortcut) { + mContext = context; + mShortcut = shortcut; + } + + @Override + public Drawable getDrawable() { + return mShortcut.icon.loadDrawable(mContext); + } + + @Override + public String getLabel() { + return mShortcut.label; + } + + @Override + public String getSettingValue() { + return mShortcut.toString(); + } + + @Override + public Boolean getExpando() { + return null; + } + } + + private static class App extends Item { + + private final Context mContext; + private final LauncherActivityInfo mInfo; + private final ArrayList<Item> mChildren = new ArrayList<>(); + private boolean mExpanded; + + public App(Context context, LauncherActivityInfo info) { + mContext = context; + mInfo = info; + mExpanded = false; + } + + public void addChild(Item child) { + mChildren.add(child); + } + + @Override + public Drawable getDrawable() { + return mInfo.getBadgedIcon(mContext.getResources().getConfiguration().densityDpi); + } + + @Override + public String getLabel() { + return mInfo.getLabel().toString(); + } + + @Override + public String getSettingValue() { + return mInfo.getComponentName().flattenToString(); + } + + @Override + public Boolean getExpando() { + return mChildren.size() != 0 ? mExpanded : null; + } + + @Override + public void toggleExpando(Adapter adapter) { + mExpanded = !mExpanded; + if (mExpanded) { + mChildren.forEach(child -> adapter.addItem(this, child)); + } else { + mChildren.forEach(child -> adapter.remItem(child)); + } + } + } + + private abstract static class Item { + public abstract Drawable getDrawable(); + + public abstract String getLabel(); + + public abstract String getSettingValue(); + + public abstract Boolean getExpando(); + + public void toggleExpando(Adapter adapter) { + } + } + + public static class Adapter extends RecyclerView.Adapter<Holder> { + private ArrayList<Item> mItems = new ArrayList<>(); + private final Context mContext; + private final Consumer<Item> mCallback; + + public Adapter(Context context, Consumer<Item> callback) { + mContext = context; + mCallback = callback; + } + + @Override + public Holder onCreateViewHolder(ViewGroup parent, int viewType) { + return new Holder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.tuner_shortcut_item, parent, false)); + } + + @Override + public void onBindViewHolder(Holder holder, int position) { + Item item = mItems.get(position); + holder.icon.setImageDrawable(item.getDrawable()); + holder.title.setText(item.getLabel()); + holder.itemView.setOnClickListener( + v -> mCallback.accept(mItems.get(holder.getAdapterPosition()))); + Boolean expando = item.getExpando(); + if (expando != null) { + holder.expand.setVisibility(View.VISIBLE); + holder.expand.setExpanded(expando); + holder.expand.setOnClickListener( + v -> mItems.get(holder.getAdapterPosition()).toggleExpando(Adapter.this)); + } else { + holder.expand.setVisibility(View.GONE); + } + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + public void addItem(Item item) { + mItems.add(item); + notifyDataSetChanged(); + } + + public void remItem(Item item) { + int index = mItems.indexOf(item); + mItems.remove(item); + notifyItemRemoved(index); + } + + public void addItem(Item parent, Item child) { + int index = mItems.indexOf(parent); + mItems.add(index + 1, child); + notifyItemInserted(index + 1); + } + } + + public static IntentButton getIntentButton(Context context, String buttonStr, + IntentButton plugin, IntentButton def) { + // Plugin wins. + if (plugin != null) return plugin; + // Then tuner options. + if (!TextUtils.isEmpty(buttonStr)) { + if (buttonStr.contains("::")) { + Shortcut shortcut = getShortcutInfo(context, buttonStr); + if (shortcut != null) { + return new ShortcutButton(context, shortcut); + } + } else if (buttonStr.contains("/")) { + ActivityInfo info = getActivityinfo(context, buttonStr); + if (info != null) { + return new ActivityButton(context, info); + } + } + } + // Then default. + return def; + } + + private static class ShortcutButton implements IntentButton { + private final Shortcut mShortcut; + private final IconState mIconState; + + public ShortcutButton(Context context, Shortcut shortcut) { + mShortcut = shortcut; + mIconState = new IconState(); + mIconState.isVisible = true; + mIconState.drawable = shortcut.icon.loadDrawable(context); + mIconState.contentDescription = mShortcut.label; + } + + @Override + public IconState getIcon() { + return mIconState; + } + + @Override + public Intent getIntent() { + return mShortcut.intent; + } + } + + private static class ActivityButton implements IntentButton { + private final Intent mIntent; + private final IconState mIconState; + + public ActivityButton(Context context, ActivityInfo info) { + mIntent = new Intent().setComponent(new ComponentName(info.packageName, info.name)); + mIconState = new IconState(); + mIconState.isVisible = true; + mIconState.drawable = info.loadIcon(context.getPackageManager()); + mIconState.contentDescription = info.loadLabel(context.getPackageManager()); + } + + @Override + public IconState getIcon() { + return mIconState; + } + + @Override + public Intent getIntent() { + return mIntent; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java new file mode 100644 index 000000000000..2aa51b3f787c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/ShortcutParser.java @@ -0,0 +1,162 @@ +/* + * 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.tuner; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Icon; +import android.util.AttributeSet; +import android.util.Xml; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ShortcutParser { + private static final String SHORTCUTS = "android.app.shortcuts"; + private static final String SHORTCUT = "shortcut"; + private static final String INTENT = "intent"; + + private final Context mContext; + private final String mPkg; + private final int mResId; + private final String mName; + private Resources mResources; + private AttributeSet mAttrs; + + public ShortcutParser(Context context, ComponentName component) throws NameNotFoundException { + this(context, component.getPackageName(), component.getClassName(), + getResId(context, component)); + } + + private static int getResId(Context context, ComponentName component) + throws NameNotFoundException { + ActivityInfo i = context.getPackageManager().getActivityInfo( + component, PackageManager.GET_META_DATA); + int resId = 0; + if (i.metaData != null && i.metaData.containsKey(SHORTCUTS)) { + resId = i.metaData.getInt(SHORTCUTS); + } + return resId; + } + + public ShortcutParser(Context context, String pkg, String name, int resId) { + mContext = context; + mPkg = pkg; + mResId = resId; + mName = name; + } + + public List<Shortcut> getShortcuts() { + List<Shortcut> list = new ArrayList<>(); + if (mResId != 0) { + try { + mResources = mContext.getPackageManager().getResourcesForApplication(mPkg); + XmlResourceParser parser = mResources.getXml(mResId); + mAttrs = Xml.asAttributeSet(parser); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + if (parser.getName().equals(SHORTCUT)) { + Shortcut c = parseShortcut(parser); + if (c != null) { + list.add(c); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return list; + } + + private Shortcut parseShortcut(XmlResourceParser parser) + throws IOException, XmlPullParserException { + final TypedArray sa = mResources.obtainAttributes(mAttrs, R.styleable.Shortcut); + Shortcut c = new Shortcut(); + + final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true); + if (!enabled) return null; + final String id = sa.getString(R.styleable.Shortcut_shortcutId); + final int iconResId = sa.getResourceId(R.styleable.Shortcut_icon, 0); + final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutShortLabel, 0); + + c.pkg = mPkg; + c.icon = Icon.createWithResource(mPkg, iconResId); + c.id = id; + c.label = mResources.getString(titleResId); + c.name = mName; + int type; + while ((type = parser.next()) != XmlPullParser.END_TAG) { + if (type != XmlPullParser.START_TAG) { + continue; + } + if (parser.getName().equals(INTENT)) { + c.intent = Intent.parseIntent(mResources, parser, mAttrs); + } + } + return c.intent != null ? c : null; + } + + public static class Shortcut { + public Intent intent; + public String label; + public Icon icon; + public String pkg; + public String id; + public String name; + + public static Shortcut create(Context context, String value) { + String[] sp = value.split("::"); + try { + for (Shortcut shortcut : new ShortcutParser(context, + new ComponentName(sp[0], sp[1])).getShortcuts()) { + if (shortcut.id.equals(sp[2])) { + return shortcut; + } + } + } catch (NameNotFoundException e) { + } + return null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(pkg); + builder.append("::"); + builder.append(name); + builder.append("::"); + builder.append(id); + return builder.toString(); + } + } +} |