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