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