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