Home | History | Annotate | Download | only in tuner
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui.tuner;
     16 
     17 import android.app.AlertDialog;
     18 import android.app.AlertDialog.Builder;
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.pm.LauncherActivityInfo;
     24 import android.content.pm.LauncherApps;
     25 import android.content.pm.LauncherApps.ShortcutQuery;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.ShortcutInfo;
     28 import android.graphics.drawable.Drawable;
     29 import android.graphics.drawable.ScaleDrawable;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Process;
     33 import android.support.v14.preference.PreferenceFragment;
     34 import android.support.v14.preference.SwitchPreference;
     35 import android.support.v7.preference.Preference;
     36 import android.support.v7.preference.PreferenceGroup;
     37 import android.support.v7.widget.LinearLayoutManager;
     38 import android.support.v7.widget.RecyclerView;
     39 import android.support.v7.widget.RecyclerView.ViewHolder;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.util.TypedValue;
     43 import android.view.Gravity;
     44 import android.view.LayoutInflater;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.widget.ImageView;
     48 import android.widget.TextView;
     49 
     50 import com.android.systemui.Dependency;
     51 import com.android.systemui.R;
     52 import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
     53 import com.android.systemui.statusbar.ScalingDrawableWrapper;
     54 import com.android.systemui.statusbar.phone.ExpandableIndicator;
     55 import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
     56 import com.android.systemui.tuner.ShortcutParser.Shortcut;
     57 import com.android.systemui.tuner.TunerService.Tunable;
     58 
     59 import java.util.ArrayList;
     60 import java.util.List;
     61 import java.util.Map;
     62 import java.util.function.Consumer;
     63 
     64 public class LockscreenFragment extends PreferenceFragment {
     65 
     66     private static final String KEY_LEFT = "left";
     67     private static final String KEY_RIGHT = "right";
     68     private static final String KEY_CUSTOMIZE = "customize";
     69     private static final String KEY_SHORTCUT = "shortcut";
     70 
     71     public static final String LOCKSCREEN_LEFT_BUTTON = "sysui_keyguard_left";
     72     public static final String LOCKSCREEN_LEFT_UNLOCK = "sysui_keyguard_left_unlock";
     73     public static final String LOCKSCREEN_RIGHT_BUTTON = "sysui_keyguard_right";
     74     public static final String LOCKSCREEN_RIGHT_UNLOCK = "sysui_keyguard_right_unlock";
     75 
     76     private final ArrayList<Tunable> mTunables = new ArrayList<>();
     77     private TunerService mTunerService;
     78     private Handler mHandler;
     79 
     80     @Override
     81     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
     82         mTunerService = Dependency.get(TunerService.class);
     83         mHandler = new Handler();
     84         addPreferencesFromResource(R.xml.lockscreen_settings);
     85         setupGroup(LOCKSCREEN_LEFT_BUTTON, LOCKSCREEN_LEFT_UNLOCK);
     86         setupGroup(LOCKSCREEN_RIGHT_BUTTON, LOCKSCREEN_RIGHT_UNLOCK);
     87     }
     88 
     89     @Override
     90     public void onDestroy() {
     91         super.onDestroy();
     92         mTunables.forEach(t -> mTunerService.removeTunable(t));
     93     }
     94 
     95     private void setupGroup(String buttonSetting, String unlockKey) {
     96         Preference shortcut = findPreference(buttonSetting);
     97         SwitchPreference unlock = (SwitchPreference) findPreference(unlockKey);
     98         addTunable((k, v) -> {
     99             boolean visible = !TextUtils.isEmpty(v);
    100             unlock.setVisible(visible);
    101             setSummary(shortcut, v);
    102         }, buttonSetting);
    103     }
    104 
    105     private void showSelectDialog(String buttonSetting) {
    106         RecyclerView v = (RecyclerView) LayoutInflater.from(getContext())
    107                 .inflate(R.layout.tuner_shortcut_list, null);
    108         v.setLayoutManager(new LinearLayoutManager(getContext()));
    109         AlertDialog dialog = new Builder(getContext())
    110                 .setView(v)
    111                 .show();
    112         Adapter adapter = new Adapter(getContext(), item -> {
    113             mTunerService.setValue(buttonSetting, item.getSettingValue());
    114             dialog.dismiss();
    115         });
    116 
    117         v.setAdapter(adapter);
    118     }
    119 
    120     private void setSummary(Preference shortcut, String value) {
    121         if (value == null) {
    122             shortcut.setSummary(R.string.lockscreen_none);
    123             return;
    124         }
    125         if (value.contains("::")) {
    126             Shortcut info = getShortcutInfo(getContext(), value);
    127             shortcut.setSummary(info != null ? info.label : null);
    128         } else if (value.contains("/")) {
    129             ActivityInfo info = getActivityinfo(getContext(), value);
    130             shortcut.setSummary(info != null ? info.loadLabel(getContext().getPackageManager())
    131                     : null);
    132         } else {
    133             shortcut.setSummary(R.string.lockscreen_none);
    134         }
    135     }
    136 
    137     private void addTunable(Tunable t, String... keys) {
    138         mTunables.add(t);
    139         mTunerService.addTunable(t, keys);
    140     }
    141 
    142     public static ActivityInfo getActivityinfo(Context context, String value) {
    143         ComponentName component = ComponentName.unflattenFromString(value);
    144         try {
    145             return context.getPackageManager().getActivityInfo(component, 0);
    146         } catch (NameNotFoundException e) {
    147             return null;
    148         }
    149     }
    150 
    151     public static Shortcut getShortcutInfo(Context context, String value) {
    152         return Shortcut.create(context, value);
    153     }
    154 
    155     public static class Holder extends ViewHolder {
    156         public final ImageView icon;
    157         public final TextView title;
    158         public final ExpandableIndicator expand;
    159 
    160         public Holder(View itemView) {
    161             super(itemView);
    162             icon = (ImageView) itemView.findViewById(android.R.id.icon);
    163             title = (TextView) itemView.findViewById(android.R.id.title);
    164             expand = (ExpandableIndicator) itemView.findViewById(R.id.expand);
    165         }
    166     }
    167 
    168     private static class StaticShortcut extends Item {
    169 
    170         private final Context mContext;
    171         private final Shortcut mShortcut;
    172 
    173 
    174         public StaticShortcut(Context context, Shortcut shortcut) {
    175             mContext = context;
    176             mShortcut = shortcut;
    177         }
    178 
    179         @Override
    180         public Drawable getDrawable() {
    181             return mShortcut.icon.loadDrawable(mContext);
    182         }
    183 
    184         @Override
    185         public String getLabel() {
    186             return mShortcut.label;
    187         }
    188 
    189         @Override
    190         public String getSettingValue() {
    191             return mShortcut.toString();
    192         }
    193 
    194         @Override
    195         public Boolean getExpando() {
    196             return null;
    197         }
    198     }
    199 
    200     private static class App extends Item {
    201 
    202         private final Context mContext;
    203         private final LauncherActivityInfo mInfo;
    204         private final ArrayList<Item> mChildren = new ArrayList<>();
    205         private boolean mExpanded;
    206 
    207         public App(Context context, LauncherActivityInfo info) {
    208             mContext = context;
    209             mInfo = info;
    210             mExpanded = false;
    211         }
    212 
    213         public void addChild(Item child) {
    214             mChildren.add(child);
    215         }
    216 
    217         @Override
    218         public Drawable getDrawable() {
    219             return mInfo.getBadgedIcon(mContext.getResources().getConfiguration().densityDpi);
    220         }
    221 
    222         @Override
    223         public String getLabel() {
    224             return mInfo.getLabel().toString();
    225         }
    226 
    227         @Override
    228         public String getSettingValue() {
    229             return mInfo.getComponentName().flattenToString();
    230         }
    231 
    232         @Override
    233         public Boolean getExpando() {
    234             return mChildren.size() != 0 ? mExpanded : null;
    235         }
    236 
    237         @Override
    238         public void toggleExpando(Adapter adapter) {
    239             mExpanded = !mExpanded;
    240             if (mExpanded) {
    241                 mChildren.forEach(child -> adapter.addItem(this, child));
    242             } else {
    243                 mChildren.forEach(child -> adapter.remItem(child));
    244             }
    245         }
    246     }
    247 
    248     private abstract static class Item {
    249         public abstract Drawable getDrawable();
    250 
    251         public abstract String getLabel();
    252 
    253         public abstract String getSettingValue();
    254 
    255         public abstract Boolean getExpando();
    256 
    257         public void toggleExpando(Adapter adapter) {
    258         }
    259     }
    260 
    261     public static class Adapter extends RecyclerView.Adapter<Holder> {
    262         private ArrayList<Item> mItems = new ArrayList<>();
    263         private final Context mContext;
    264         private final Consumer<Item> mCallback;
    265 
    266         public Adapter(Context context, Consumer<Item> callback) {
    267             mContext = context;
    268             mCallback = callback;
    269         }
    270 
    271         @Override
    272         public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
    273             return new Holder(LayoutInflater.from(parent.getContext())
    274                     .inflate(R.layout.tuner_shortcut_item, parent, false));
    275         }
    276 
    277         @Override
    278         public void onBindViewHolder(Holder holder, int position) {
    279             Item item = mItems.get(position);
    280             holder.icon.setImageDrawable(item.getDrawable());
    281             holder.title.setText(item.getLabel());
    282             holder.itemView.setOnClickListener(
    283                     v -> mCallback.accept(mItems.get(holder.getAdapterPosition())));
    284             Boolean expando = item.getExpando();
    285             if (expando != null) {
    286                 holder.expand.setVisibility(View.VISIBLE);
    287                 holder.expand.setExpanded(expando);
    288                 holder.expand.setOnClickListener(
    289                         v -> mItems.get(holder.getAdapterPosition()).toggleExpando(Adapter.this));
    290             } else {
    291                 holder.expand.setVisibility(View.GONE);
    292             }
    293         }
    294 
    295         @Override
    296         public int getItemCount() {
    297             return mItems.size();
    298         }
    299 
    300         public void addItem(Item item) {
    301             mItems.add(item);
    302             notifyDataSetChanged();
    303         }
    304 
    305         public void remItem(Item item) {
    306             int index = mItems.indexOf(item);
    307             mItems.remove(item);
    308             notifyItemRemoved(index);
    309         }
    310 
    311         public void addItem(Item parent, Item child) {
    312             int index = mItems.indexOf(parent);
    313             mItems.add(index + 1, child);
    314             notifyItemInserted(index + 1);
    315         }
    316     }
    317 
    318     public static class LockButtonFactory implements TunerFactory<IntentButton> {
    319 
    320         private final String mKey;
    321         private final Context mContext;
    322 
    323         public LockButtonFactory(Context context, String key) {
    324             mContext = context;
    325             mKey = key;
    326         }
    327 
    328         @Override
    329         public String[] keys() {
    330             return new String[]{mKey};
    331         }
    332 
    333         @Override
    334         public IntentButton create(Map<String, String> settings) {
    335             String buttonStr = settings.get(mKey);
    336             if (!TextUtils.isEmpty(buttonStr)) {
    337                 if (buttonStr.contains("::")) {
    338                     Shortcut shortcut = getShortcutInfo(mContext, buttonStr);
    339                     if (shortcut != null) {
    340                         return new ShortcutButton(mContext, shortcut);
    341                     }
    342                 } else if (buttonStr.contains("/")) {
    343                     ActivityInfo info = getActivityinfo(mContext, buttonStr);
    344                     if (info != null) {
    345                         return new ActivityButton(mContext, info);
    346                     }
    347                 }
    348             }
    349             return null;
    350         }
    351     }
    352 
    353     private static class ShortcutButton implements IntentButton {
    354         private final Shortcut mShortcut;
    355         private final IconState mIconState;
    356 
    357         public ShortcutButton(Context context, Shortcut shortcut) {
    358             mShortcut = shortcut;
    359             mIconState = new IconState();
    360             mIconState.isVisible = true;
    361             mIconState.drawable = shortcut.icon.loadDrawable(context).mutate();
    362             mIconState.contentDescription = mShortcut.label;
    363             int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32,
    364                     context.getResources().getDisplayMetrics());
    365             mIconState.drawable = new ScalingDrawableWrapper(mIconState.drawable,
    366                     size / (float) mIconState.drawable.getIntrinsicWidth());
    367             mIconState.tint = false;
    368         }
    369 
    370         @Override
    371         public IconState getIcon() {
    372             return mIconState;
    373         }
    374 
    375         @Override
    376         public Intent getIntent() {
    377             return mShortcut.intent;
    378         }
    379     }
    380 
    381     private static class ActivityButton implements IntentButton {
    382         private final Intent mIntent;
    383         private final IconState mIconState;
    384 
    385         public ActivityButton(Context context, ActivityInfo info) {
    386             mIntent = new Intent().setComponent(new ComponentName(info.packageName, info.name));
    387             mIconState = new IconState();
    388             mIconState.isVisible = true;
    389             mIconState.drawable = info.loadIcon(context.getPackageManager()).mutate();
    390             mIconState.contentDescription = info.loadLabel(context.getPackageManager());
    391             int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32,
    392                     context.getResources().getDisplayMetrics());
    393             mIconState.drawable = new ScalingDrawableWrapper(mIconState.drawable,
    394                     size / (float) mIconState.drawable.getIntrinsicWidth());
    395             mIconState.tint = false;
    396         }
    397 
    398         @Override
    399         public IconState getIcon() {
    400             return mIconState;
    401         }
    402 
    403         @Override
    404         public Intent getIntent() {
    405             return mIntent;
    406         }
    407     }
    408 }
    409