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 0000000000000..e9eae3b0e1d62 --- /dev/null +++ b/packages/SystemUI/res/layout/tuner_shortcut_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + 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 0000000000000..9aaffb49e6527 --- /dev/null +++ b/packages/SystemUI/res/layout/tuner_shortcut_list.xml @@ -0,0 +1,22 @@ + + + + diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 422431e7b31a1..1eb3c5a0d6d3a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1752,5 +1752,19 @@ 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. + + Left + + + Right + + + Customize shortcut + + + Shortcut + + + Prompt for password diff --git a/packages/SystemUI/res/xml/lockscreen_settings.xml b/packages/SystemUI/res/xml/lockscreen_settings.xml new file mode 100644 index 0000000000000..73e29af428210 --- /dev/null +++ b/packages/SystemUI/res/xml/lockscreen_settings.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 59a10dabe9671..5afab7ba0cc5e 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -157,6 +157,11 @@ android:fragment="com.android.systemui.tuner.NavBarTuner" /> --> + + 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 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 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 { + private ArrayList mItems = new ArrayList<>(); + private final Context mContext; + private final Consumer mCallback; + + public Adapter(Context context, Consumer 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 0000000000000..2aa51b3f787c1 --- /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 getShortcuts() { + List 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(); + } + } +}