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