1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.settings; 18 19import android.app.Activity; 20import android.app.ActivityManager; 21import android.app.LauncherActivity.IconResizer; 22import android.appwidget.AppWidgetHost; 23import android.appwidget.AppWidgetManager; 24import android.appwidget.AppWidgetProviderInfo; 25import android.content.ActivityNotFoundException; 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.pm.PackageManager; 30import android.content.res.Resources; 31import android.graphics.Bitmap; 32import android.graphics.Bitmap.Config; 33import android.graphics.Canvas; 34import android.graphics.Paint; 35import android.graphics.Rect; 36import android.graphics.drawable.Drawable; 37import android.os.AsyncTask; 38import android.os.Bundle; 39import android.os.IBinder; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.UserHandle; 43import android.util.DisplayMetrics; 44import android.util.Log; 45import android.view.IWindowManager; 46import android.view.LayoutInflater; 47import android.view.View; 48import android.view.ViewGroup; 49import android.widget.AdapterView; 50import android.widget.BaseAdapter; 51import android.widget.GridView; 52import android.widget.ImageView; 53import android.widget.TextView; 54import android.widget.Toast; 55 56import com.android.internal.widget.LockPatternUtils; 57 58import java.lang.ref.WeakReference; 59import java.util.List; 60 61/** 62 * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any 63 * injected special widgets specified through 64 * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and 65 * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}. 66 * <p> 67 * When an installed {@link AppWidgetProviderInfo} is selected, this activity 68 * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID}, 69 * otherwise it will return the requested extras. 70 */ 71public class KeyguardAppWidgetPickActivity extends Activity 72 implements GridView.OnItemClickListener, 73 AppWidgetLoader.ItemConstructor<KeyguardAppWidgetPickActivity.Item> { 74 private static final String TAG = "KeyguardAppWidgetPickActivity"; 75 private static final int REQUEST_PICK_APPWIDGET = 126; 76 private static final int REQUEST_CREATE_APPWIDGET = 127; 77 78 private AppWidgetLoader<Item> mAppWidgetLoader; 79 private List<Item> mItems; 80 private GridView mGridView; 81 private AppWidgetAdapter mAppWidgetAdapter; 82 private AppWidgetManager mAppWidgetManager; 83 private int mAppWidgetId; 84 // Might make it possible to make this be false in future 85 private boolean mAddingToKeyguard = true; 86 private Intent mResultData; 87 private LockPatternUtils mLockPatternUtils; 88 private Bundle mExtraConfigureOptions; 89 90 @Override 91 protected void onCreate(Bundle savedInstanceState) { 92 setContentView(R.layout.keyguard_appwidget_picker_layout); 93 super.onCreate(savedInstanceState); 94 95 // Set default return data 96 setResultData(RESULT_CANCELED, null); 97 98 // Read the appWidgetId passed our direction, otherwise bail if not found 99 final Intent intent = getIntent(); 100 if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { 101 mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 102 AppWidgetManager.INVALID_APPWIDGET_ID); 103 } else { 104 finish(); 105 } 106 mExtraConfigureOptions = intent.getBundleExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS); 107 108 mGridView = (GridView) findViewById(R.id.widget_list); 109 DisplayMetrics dm = new DisplayMetrics(); 110 getWindowManager().getDefaultDisplay().getMetrics(dm); 111 int maxGridWidth = getResources().getDimensionPixelSize( 112 R.dimen.keyguard_appwidget_picker_max_width); 113 114 if (maxGridWidth < dm.widthPixels) { 115 mGridView.getLayoutParams().width = maxGridWidth; 116 } 117 mAppWidgetManager = AppWidgetManager.getInstance(this); 118 mAppWidgetLoader = new AppWidgetLoader<Item>(this, mAppWidgetManager, this); 119 mItems = mAppWidgetLoader.getItems(getIntent()); 120 mAppWidgetAdapter = new AppWidgetAdapter(this, mItems); 121 mGridView.setAdapter(mAppWidgetAdapter); 122 mGridView.setOnItemClickListener(this); 123 124 mLockPatternUtils = new LockPatternUtils(this); // TEMP-- we want to delete this 125 } 126 127 /** 128 * Convenience method for setting the result code and intent. This method 129 * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that 130 * most hosts expect returned. 131 */ 132 void setResultData(int code, Intent intent) { 133 Intent result = intent != null ? intent : new Intent(); 134 result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); 135 mResultData = result; 136 setResult(code, result); 137 } 138 139 /** 140 * Item that appears in the AppWidget picker grid. 141 */ 142 public static class Item implements AppWidgetLoader.LabelledItem { 143 protected static IconResizer sResizer; 144 145 146 CharSequence label; 147 int appWidgetPreviewId; 148 int iconId; 149 String packageName; 150 String className; 151 Bundle extras; 152 private WidgetPreviewLoader mWidgetPreviewLoader; 153 private Context mContext; 154 155 /** 156 * Create a list item from given label and icon. 157 */ 158 Item(Context context, CharSequence label) { 159 this.label = label; 160 mContext = context; 161 } 162 163 void loadWidgetPreview(ImageView v) { 164 mWidgetPreviewLoader = new WidgetPreviewLoader(mContext, v); 165 mWidgetPreviewLoader.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); 166 } 167 168 void cancelLoadingWidgetPreview() { 169 if (mWidgetPreviewLoader != null) { 170 mWidgetPreviewLoader.cancel(false); 171 mWidgetPreviewLoader = null; 172 } 173 } 174 175 /** 176 * Build the {@link Intent} described by this item. If this item 177 * can't create a valid {@link android.content.ComponentName}, it will return 178 * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label. 179 */ 180 Intent getIntent() { 181 Intent intent = new Intent(); 182 if (packageName != null && className != null) { 183 // Valid package and class, so fill details as normal intent 184 intent.setClassName(packageName, className); 185 if (extras != null) { 186 intent.putExtras(extras); 187 } 188 } else { 189 // No valid package or class, so treat as shortcut with label 190 intent.setAction(Intent.ACTION_CREATE_SHORTCUT); 191 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); 192 } 193 return intent; 194 } 195 196 public CharSequence getLabel() { 197 return label; 198 } 199 200 class WidgetPreviewLoader extends AsyncTask<Void, Bitmap, Void> { 201 private Resources mResources; 202 private PackageManager mPackageManager; 203 private int mIconDpi; 204 private ImageView mView; 205 public WidgetPreviewLoader(Context context, ImageView v) { 206 super(); 207 mResources = context.getResources(); 208 mPackageManager = context.getPackageManager(); 209 ActivityManager activityManager = 210 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 211 mIconDpi = activityManager.getLauncherLargeIconDensity(); 212 mView = v; 213 } 214 public Void doInBackground(Void... params) { 215 if (!isCancelled()) { 216 int appWidgetPreviewWidth = 217 mResources.getDimensionPixelSize(R.dimen.appwidget_preview_width); 218 int appWidgetPreviewHeight = 219 mResources.getDimensionPixelSize(R.dimen.appwidget_preview_height); 220 Bitmap b = getWidgetPreview(new ComponentName(packageName, className), 221 appWidgetPreviewId, iconId, 222 appWidgetPreviewWidth, appWidgetPreviewHeight); 223 publishProgress(b); 224 } 225 return null; 226 } 227 public void onProgressUpdate(Bitmap... values) { 228 if (!isCancelled()) { 229 Bitmap b = values[0]; 230 mView.setImageBitmap(b); 231 } 232 } 233 abstract class WeakReferenceThreadLocal<T> { 234 private ThreadLocal<WeakReference<T>> mThreadLocal; 235 public WeakReferenceThreadLocal() { 236 mThreadLocal = new ThreadLocal<WeakReference<T>>(); 237 } 238 239 abstract T initialValue(); 240 241 public void set(T t) { 242 mThreadLocal.set(new WeakReference<T>(t)); 243 } 244 245 public T get() { 246 WeakReference<T> reference = mThreadLocal.get(); 247 T obj; 248 if (reference == null) { 249 obj = initialValue(); 250 mThreadLocal.set(new WeakReference<T>(obj)); 251 return obj; 252 } else { 253 obj = reference.get(); 254 if (obj == null) { 255 obj = initialValue(); 256 mThreadLocal.set(new WeakReference<T>(obj)); 257 } 258 return obj; 259 } 260 } 261 } 262 263 class CanvasCache extends WeakReferenceThreadLocal<Canvas> { 264 @Override 265 protected Canvas initialValue() { 266 return new Canvas(); 267 } 268 } 269 270 class PaintCache extends WeakReferenceThreadLocal<Paint> { 271 @Override 272 protected Paint initialValue() { 273 return null; 274 } 275 } 276 277 class BitmapCache extends WeakReferenceThreadLocal<Bitmap> { 278 @Override 279 protected Bitmap initialValue() { 280 return null; 281 } 282 } 283 284 class RectCache extends WeakReferenceThreadLocal<Rect> { 285 @Override 286 protected Rect initialValue() { 287 return new Rect(); 288 } 289 } 290 291 // Used for drawing widget previews 292 CanvasCache sCachedAppWidgetPreviewCanvas = new CanvasCache(); 293 RectCache sCachedAppWidgetPreviewSrcRect = new RectCache(); 294 RectCache sCachedAppWidgetPreviewDestRect = new RectCache(); 295 PaintCache sCachedAppWidgetPreviewPaint = new PaintCache(); 296 297 private Bitmap getWidgetPreview(ComponentName provider, int previewImage, 298 int iconId, int maxWidth, int maxHeight) { 299 // Load the preview image if possible 300 String packageName = provider.getPackageName(); 301 if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; 302 if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; 303 304 305 int appIconSize = mResources.getDimensionPixelSize(R.dimen.app_icon_size); 306 307 Drawable drawable = null; 308 if (previewImage != 0) { 309 drawable = mPackageManager.getDrawable(packageName, previewImage, null); 310 if (drawable == null) { 311 Log.w(TAG, "Can't load widget preview drawable 0x" + 312 Integer.toHexString(previewImage) + " for provider: " + provider); 313 } 314 } 315 316 int bitmapWidth; 317 int bitmapHeight; 318 Bitmap defaultPreview = null; 319 boolean widgetPreviewExists = (drawable != null); 320 if (widgetPreviewExists) { 321 bitmapWidth = drawable.getIntrinsicWidth(); 322 bitmapHeight = drawable.getIntrinsicHeight(); 323 } else { 324 // Generate a preview image if we couldn't load one 325 bitmapWidth = appIconSize; 326 bitmapHeight = appIconSize; 327 defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, 328 Config.ARGB_8888); 329 330 try { 331 Drawable icon = null; 332 if (iconId > 0) 333 icon = getFullResIcon(packageName, iconId); 334 if (icon != null) { 335 renderDrawableToBitmap(icon, defaultPreview, 0, 336 0, appIconSize, appIconSize); 337 } 338 } catch (Resources.NotFoundException e) { 339 } 340 } 341 342 // Scale to fit width only - let the widget preview be clipped in the 343 // vertical dimension 344 float scale = 1f; 345 if (bitmapWidth > maxWidth) { 346 scale = maxWidth / (float) bitmapWidth; 347 } 348 int finalPreviewWidth = (int) (scale * bitmapWidth); 349 int finalPreviewHeight = (int) (scale * bitmapHeight); 350 351 bitmapWidth = finalPreviewWidth; 352 bitmapHeight = Math.min(finalPreviewHeight, maxHeight); 353 354 Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, 355 Config.ARGB_8888); 356 357 // Draw the scaled preview into the final bitmap 358 if (widgetPreviewExists) { 359 renderDrawableToBitmap(drawable, preview, 0, 0, finalPreviewWidth, 360 finalPreviewHeight); 361 } else { 362 final Canvas c = sCachedAppWidgetPreviewCanvas.get(); 363 final Rect src = sCachedAppWidgetPreviewSrcRect.get(); 364 final Rect dest = sCachedAppWidgetPreviewDestRect.get(); 365 c.setBitmap(preview); 366 src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); 367 dest.set(0, 0, finalPreviewWidth, finalPreviewHeight); 368 369 Paint p = sCachedAppWidgetPreviewPaint.get(); 370 if (p == null) { 371 p = new Paint(); 372 p.setFilterBitmap(true); 373 sCachedAppWidgetPreviewPaint.set(p); 374 } 375 c.drawBitmap(defaultPreview, src, dest, p); 376 c.setBitmap(null); 377 } 378 return preview; 379 } 380 public Drawable getFullResDefaultActivityIcon() { 381 return getFullResIcon(Resources.getSystem(), 382 android.R.mipmap.sym_def_app_icon); 383 } 384 385 public Drawable getFullResIcon(Resources resources, int iconId) { 386 Drawable d; 387 try { 388 d = resources.getDrawableForDensity(iconId, mIconDpi); 389 } catch (Resources.NotFoundException e) { 390 d = null; 391 } 392 393 return (d != null) ? d : getFullResDefaultActivityIcon(); 394 } 395 396 public Drawable getFullResIcon(String packageName, int iconId) { 397 Resources resources; 398 try { 399 resources = mPackageManager.getResourcesForApplication(packageName); 400 } catch (PackageManager.NameNotFoundException e) { 401 resources = null; 402 } 403 if (resources != null) { 404 if (iconId != 0) { 405 return getFullResIcon(resources, iconId); 406 } 407 } 408 return getFullResDefaultActivityIcon(); 409 } 410 411 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { 412 renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); 413 } 414 415 private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, 416 float scale) { 417 if (bitmap != null) { 418 Canvas c = new Canvas(bitmap); 419 c.scale(scale, scale); 420 Rect oldBounds = d.copyBounds(); 421 d.setBounds(x, y, x + w, y + h); 422 d.draw(c); 423 d.setBounds(oldBounds); // Restore the bounds 424 c.setBitmap(null); 425 } 426 } 427 } 428 } 429 430 @Override 431 public Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) { 432 CharSequence label = info.label; 433 434 Item item = new Item(context, label); 435 item.appWidgetPreviewId = info.previewImage; 436 item.iconId = info.icon; 437 item.packageName = info.provider.getPackageName(); 438 item.className = info.provider.getClassName(); 439 item.extras = extras; 440 return item; 441 } 442 443 protected static class AppWidgetAdapter extends BaseAdapter { 444 private final LayoutInflater mInflater; 445 private final List<Item> mItems; 446 447 /** 448 * Create an adapter for the given items. 449 */ 450 public AppWidgetAdapter(Context context, List<Item> items) { 451 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 452 mItems = items; 453 } 454 455 /** 456 * {@inheritDoc} 457 */ 458 public int getCount() { 459 return mItems.size(); 460 } 461 462 /** 463 * {@inheritDoc} 464 */ 465 public Object getItem(int position) { 466 return mItems.get(position); 467 } 468 469 /** 470 * {@inheritDoc} 471 */ 472 public long getItemId(int position) { 473 return position; 474 } 475 476 /** 477 * {@inheritDoc} 478 */ 479 public View getView(int position, View convertView, ViewGroup parent) { 480 if (convertView == null) { 481 convertView = mInflater.inflate(R.layout.keyguard_appwidget_item, parent, false); 482 } 483 484 Item item = (Item) getItem(position); 485 TextView textView = (TextView) convertView.findViewById(R.id.label); 486 textView.setText(item.label); 487 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); 488 iconView.setImageDrawable(null); 489 item.loadWidgetPreview(iconView); 490 return convertView; 491 } 492 493 public void cancelAllWidgetPreviewLoaders() { 494 for (int i = 0; i < mItems.size(); i++) { 495 mItems.get(i).cancelLoadingWidgetPreview(); 496 } 497 } 498 } 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override 504 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 505 Item item = mItems.get(position); 506 Intent intent = item.getIntent(); 507 508 int result; 509 if (item.extras != null) { 510 // If these extras are present it's because this entry is custom. 511 // Don't try to bind it, just pass it back to the app. 512 result = RESULT_OK; 513 setResultData(result, intent); 514 } else { 515 try { 516 if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { 517 // Found in KeyguardHostView.java 518 final int KEYGUARD_HOST_ID = 0x4B455947; 519 int userId = ActivityManager.getCurrentUser(); 520 mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForSystem(KEYGUARD_HOST_ID, 521 userId); 522 } 523 mAppWidgetManager.bindAppWidgetId( 524 mAppWidgetId, intent.getComponent(), mExtraConfigureOptions); 525 result = RESULT_OK; 526 } catch (IllegalArgumentException e) { 527 // This is thrown if they're already bound, or otherwise somehow 528 // bogus. Set the result to canceled, and exit. The app *should* 529 // clean up at this point. We could pass the error along, but 530 // it's not clear that that's useful -- the widget will simply not 531 // appear. 532 result = RESULT_CANCELED; 533 } 534 setResultData(result, null); 535 } 536 if (mAddingToKeyguard) { 537 onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData); 538 } else { 539 finish(); 540 } 541 } 542 543 protected void onDestroy() { 544 if (mAppWidgetAdapter != null) { 545 mAppWidgetAdapter.cancelAllWidgetPreviewLoaders(); 546 } 547 super.onDestroy(); 548 } 549 550 @Override 551 public void onActivityResult(int requestCode, int resultCode, Intent data) { 552 super.onActivityResult(requestCode, resultCode, data); 553 if (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET) { 554 int appWidgetId; 555 if (data == null) { 556 appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID ; 557 } else { 558 appWidgetId = data.getIntExtra( 559 AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 560 } 561 if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == Activity.RESULT_OK) { 562 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); 563 564 AppWidgetProviderInfo appWidget = null; 565 appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId); 566 567 if (appWidget.configure != null) { 568 // Launch over to configure widget, if needed 569 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); 570 intent.setComponent(appWidget.configure); 571 intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 572 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); 573 574 startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); 575 } else { 576 // Otherwise just add it 577 onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data); 578 } 579 } else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) { 580 mLockPatternUtils.addAppWidget(appWidgetId, 0); 581 finishDelayedAndShowLockScreen(appWidgetId); 582 } else { 583 if (mAddingToKeyguard && 584 mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 585 int userId = ActivityManager.getCurrentUser(); 586 AppWidgetHost.deleteAppWidgetIdForSystem(mAppWidgetId, userId); 587 } 588 finishDelayedAndShowLockScreen(AppWidgetManager.INVALID_APPWIDGET_ID); 589 } 590 } 591 } 592 593 private void finishDelayedAndShowLockScreen(int appWidgetId) { 594 IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE); 595 IWindowManager iWm = IWindowManager.Stub.asInterface(b); 596 Bundle opts = null; 597 if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { 598 opts = new Bundle(); 599 opts.putInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, appWidgetId); 600 } 601 try { 602 iWm.lockNow(opts); 603 } catch (RemoteException e) { 604 } 605 606 // Change background to all black 607 ViewGroup root = (ViewGroup) findViewById(R.id.layout_root); 608 root.setBackgroundColor(0xFF000000); 609 // Hide all children 610 final int childCount = root.getChildCount(); 611 for (int i = 0; i < childCount; i++) { 612 root.getChildAt(i).setVisibility(View.INVISIBLE); 613 } 614 mGridView.postDelayed(new Runnable() { 615 public void run() { 616 finish(); 617 } 618 }, 500); 619 } 620 621 void startActivityForResultSafely(Intent intent, int requestCode) { 622 try { 623 startActivityForResult(intent, requestCode); 624 } catch (ActivityNotFoundException e) { 625 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 626 } catch (SecurityException e) { 627 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 628 Log.e(TAG, "Settings does not have the permission to launch " + intent, e); 629 } 630 } 631} 632