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