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