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