Merge "Tuner: Allow lockscreen launch targets to be customized"

This commit is contained in:
Jason Monk
2017-01-26 03:23:28 +00:00
committed by Android (Google) Code Review
8 changed files with 763 additions and 7 deletions

View File

@@ -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>

View File

@@ -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" />

View File

@@ -1785,5 +1785,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>

View File

@@ -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>

View File

@@ -155,6 +155,11 @@
android:title="@string/nav_bar"
android:fragment="com.android.systemui.tuner.NavBarTuner" />
<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"

View File

@@ -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);
}
};

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}