/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings; import android.app.Activity; import android.app.ActivityManager; import android.app.LauncherActivity.IconResizer; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.util.DisplayMetrics; import android.util.Log; import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.android.internal.widget.LockPatternUtils; import java.lang.ref.WeakReference; import java.util.List; /** * Displays a list of {@link AppWidgetProviderInfo} widgets, along with any * injected special widgets specified through * {@link AppWidgetManager#EXTRA_CUSTOM_INFO} and * {@link AppWidgetManager#EXTRA_CUSTOM_EXTRAS}. * <p> * When an installed {@link AppWidgetProviderInfo} is selected, this activity * will bind it to the given {@link AppWidgetManager#EXTRA_APPWIDGET_ID}, * otherwise it will return the requested extras. */ public class KeyguardAppWidgetPickActivity extends Activity implements GridView.OnItemClickListener, AppWidgetLoader.ItemConstructor<KeyguardAppWidgetPickActivity.Item> { private static final String TAG = "KeyguardAppWidgetPickActivity"; private static final int REQUEST_PICK_APPWIDGET = 126; private static final int REQUEST_CREATE_APPWIDGET = 127; private AppWidgetLoader<Item> mAppWidgetLoader; private List<Item> mItems; private GridView mGridView; private AppWidgetAdapter mAppWidgetAdapter; private AppWidgetManager mAppWidgetManager; private int mAppWidgetId; // Might make it possible to make this be false in future private boolean mAddingToKeyguard = true; private Intent mResultData; private LockPatternUtils mLockPatternUtils; private Bundle mExtraConfigureOptions; @Override protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.keyguard_appwidget_picker_layout); super.onCreate(savedInstanceState); // Set default return data setResultData(RESULT_CANCELED, null); // Read the appWidgetId passed our direction, otherwise bail if not found final Intent intent = getIntent(); if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } else { finish(); } mExtraConfigureOptions = intent.getBundleExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS); mGridView = (GridView) findViewById(R.id.widget_list); DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); int maxGridWidth = getResources().getDimensionPixelSize( R.dimen.keyguard_appwidget_picker_max_width); if (maxGridWidth < dm.widthPixels) { mGridView.getLayoutParams().width = maxGridWidth; } mAppWidgetManager = AppWidgetManager.getInstance(this); mAppWidgetLoader = new AppWidgetLoader<Item>(this, mAppWidgetManager, this); mItems = mAppWidgetLoader.getItems(getIntent()); mAppWidgetAdapter = new AppWidgetAdapter(this, mItems); mGridView.setAdapter(mAppWidgetAdapter); mGridView.setOnItemClickListener(this); mLockPatternUtils = new LockPatternUtils(this); // TEMP-- we want to delete this } /** * Convenience method for setting the result code and intent. This method * correctly injects the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} that * most hosts expect returned. */ void setResultData(int code, Intent intent) { Intent result = intent != null ? intent : new Intent(); result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); mResultData = result; setResult(code, result); } /** * Item that appears in the AppWidget picker grid. */ public static class Item implements AppWidgetLoader.LabelledItem { protected static IconResizer sResizer; CharSequence label; int appWidgetPreviewId; int iconId; String packageName; String className; Bundle extras; private WidgetPreviewLoader mWidgetPreviewLoader; private Context mContext; /** * Create a list item from given label and icon. */ Item(Context context, CharSequence label) { this.label = label; mContext = context; } void loadWidgetPreview(ImageView v) { mWidgetPreviewLoader = new WidgetPreviewLoader(mContext, v); mWidgetPreviewLoader.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); } void cancelLoadingWidgetPreview() { if (mWidgetPreviewLoader != null) { mWidgetPreviewLoader.cancel(false); mWidgetPreviewLoader = null; } } /** * Build the {@link Intent} described by this item. If this item * can't create a valid {@link android.content.ComponentName}, it will return * {@link Intent#ACTION_CREATE_SHORTCUT} filled with the item label. */ Intent getIntent() { Intent intent = new Intent(); if (packageName != null && className != null) { // Valid package and class, so fill details as normal intent intent.setClassName(packageName, className); if (extras != null) { intent.putExtras(extras); } } else { // No valid package or class, so treat as shortcut with label intent.setAction(Intent.ACTION_CREATE_SHORTCUT); intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); } return intent; } public CharSequence getLabel() { return label; } class WidgetPreviewLoader extends AsyncTask<Void, Bitmap, Void> { private Resources mResources; private PackageManager mPackageManager; private int mIconDpi; private ImageView mView; public WidgetPreviewLoader(Context context, ImageView v) { super(); mResources = context.getResources(); mPackageManager = context.getPackageManager(); ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mIconDpi = activityManager.getLauncherLargeIconDensity(); mView = v; } public Void doInBackground(Void... params) { if (!isCancelled()) { int appWidgetPreviewWidth = mResources.getDimensionPixelSize(R.dimen.appwidget_preview_width); int appWidgetPreviewHeight = mResources.getDimensionPixelSize(R.dimen.appwidget_preview_height); Bitmap b = getWidgetPreview(new ComponentName(packageName, className), appWidgetPreviewId, iconId, appWidgetPreviewWidth, appWidgetPreviewHeight); publishProgress(b); } return null; } public void onProgressUpdate(Bitmap... values) { if (!isCancelled()) { Bitmap b = values[0]; mView.setImageBitmap(b); } } abstract class WeakReferenceThreadLocal<T> { private ThreadLocal<WeakReference<T>> mThreadLocal; public WeakReferenceThreadLocal() { mThreadLocal = new ThreadLocal<WeakReference<T>>(); } abstract T initialValue(); public void set(T t) { mThreadLocal.set(new WeakReference<T>(t)); } public T get() { WeakReference<T> reference = mThreadLocal.get(); T obj; if (reference == null) { obj = initialValue(); mThreadLocal.set(new WeakReference<T>(obj)); return obj; } else { obj = reference.get(); if (obj == null) { obj = initialValue(); mThreadLocal.set(new WeakReference<T>(obj)); } return obj; } } } class CanvasCache extends WeakReferenceThreadLocal<Canvas> { @Override protected Canvas initialValue() { return new Canvas(); } } class PaintCache extends WeakReferenceThreadLocal<Paint> { @Override protected Paint initialValue() { return null; } } class BitmapCache extends WeakReferenceThreadLocal<Bitmap> { @Override protected Bitmap initialValue() { return null; } } class RectCache extends WeakReferenceThreadLocal<Rect> { @Override protected Rect initialValue() { return new Rect(); } } // Used for drawing widget previews CanvasCache sCachedAppWidgetPreviewCanvas = new CanvasCache(); RectCache sCachedAppWidgetPreviewSrcRect = new RectCache(); RectCache sCachedAppWidgetPreviewDestRect = new RectCache(); PaintCache sCachedAppWidgetPreviewPaint = new PaintCache(); private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId, int maxWidth, int maxHeight) { // Load the preview image if possible String packageName = provider.getPackageName(); if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; int appIconSize = mResources.getDimensionPixelSize(R.dimen.app_icon_size); Drawable drawable = null; if (previewImage != 0) { drawable = mPackageManager.getDrawable(packageName, previewImage, null); if (drawable == null) { Log.w(TAG, "Can't load widget preview drawable 0x" + Integer.toHexString(previewImage) + " for provider: " + provider); } } int bitmapWidth; int bitmapHeight; Bitmap defaultPreview = null; boolean widgetPreviewExists = (drawable != null); if (widgetPreviewExists) { bitmapWidth = drawable.getIntrinsicWidth(); bitmapHeight = drawable.getIntrinsicHeight(); } else { // Generate a preview image if we couldn't load one bitmapWidth = appIconSize; bitmapHeight = appIconSize; defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888); try { Drawable icon = null; if (iconId > 0) icon = getFullResIcon(packageName, iconId); if (icon != null) { renderDrawableToBitmap(icon, defaultPreview, 0, 0, appIconSize, appIconSize); } } catch (Resources.NotFoundException e) { } } // Scale to fit width only - let the widget preview be clipped in the // vertical dimension float scale = 1f; if (bitmapWidth > maxWidth) { scale = maxWidth / (float) bitmapWidth; } int finalPreviewWidth = (int) (scale * bitmapWidth); int finalPreviewHeight = (int) (scale * bitmapHeight); bitmapWidth = finalPreviewWidth; bitmapHeight = Math.min(finalPreviewHeight, maxHeight); Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Config.ARGB_8888); // Draw the scaled preview into the final bitmap if (widgetPreviewExists) { renderDrawableToBitmap(drawable, preview, 0, 0, finalPreviewWidth, finalPreviewHeight); } else { final Canvas c = sCachedAppWidgetPreviewCanvas.get(); final Rect src = sCachedAppWidgetPreviewSrcRect.get(); final Rect dest = sCachedAppWidgetPreviewDestRect.get(); c.setBitmap(preview); src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); dest.set(0, 0, finalPreviewWidth, finalPreviewHeight); Paint p = sCachedAppWidgetPreviewPaint.get(); if (p == null) { p = new Paint(); p.setFilterBitmap(true); sCachedAppWidgetPreviewPaint.set(p); } c.drawBitmap(defaultPreview, src, dest, p); c.setBitmap(null); } return preview; } public Drawable getFullResDefaultActivityIcon() { return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); } public Drawable getFullResIcon(Resources resources, int iconId) { Drawable d; try { d = resources.getDrawableForDensity(iconId, mIconDpi); } catch (Resources.NotFoundException e) { d = null; } return (d != null) ? d : getFullResDefaultActivityIcon(); } public Drawable getFullResIcon(String packageName, int iconId) { Resources resources; try { resources = mPackageManager.getResourcesForApplication(packageName); } catch (PackageManager.NameNotFoundException e) { resources = null; } if (resources != null) { if (iconId != 0) { return getFullResIcon(resources, iconId); } } return getFullResDefaultActivityIcon(); } private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); } private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, float scale) { if (bitmap != null) { Canvas c = new Canvas(bitmap); c.scale(scale, scale); Rect oldBounds = d.copyBounds(); d.setBounds(x, y, x + w, y + h); d.draw(c); d.setBounds(oldBounds); // Restore the bounds c.setBitmap(null); } } } } @Override public Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) { CharSequence label = info.label; Item item = new Item(context, label); item.appWidgetPreviewId = info.previewImage; item.iconId = info.icon; item.packageName = info.provider.getPackageName(); item.className = info.provider.getClassName(); item.extras = extras; return item; } protected static class AppWidgetAdapter extends BaseAdapter { private final LayoutInflater mInflater; private final List<Item> mItems; /** * Create an adapter for the given items. */ public AppWidgetAdapter(Context context, List<Item> items) { mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mItems = items; } /** * {@inheritDoc} */ public int getCount() { return mItems.size(); } /** * {@inheritDoc} */ public Object getItem(int position) { return mItems.get(position); } /** * {@inheritDoc} */ public long getItemId(int position) { return position; } /** * {@inheritDoc} */ public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.keyguard_appwidget_item, parent, false); } Item item = (Item) getItem(position); TextView textView = (TextView) convertView.findViewById(R.id.label); textView.setText(item.label); ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); iconView.setImageDrawable(null); item.loadWidgetPreview(iconView); return convertView; } public void cancelAllWidgetPreviewLoaders() { for (int i = 0; i < mItems.size(); i++) { mItems.get(i).cancelLoadingWidgetPreview(); } } } /** * {@inheritDoc} */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Item item = mItems.get(position); Intent intent = item.getIntent(); int result; if (item.extras != null) { // If these extras are present it's because this entry is custom. // Don't try to bind it, just pass it back to the app. result = RESULT_OK; setResultData(result, intent); } else { try { if (mAddingToKeyguard && mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { // Found in KeyguardHostView.java final int KEYGUARD_HOST_ID = 0x4B455947; mAppWidgetId = AppWidgetHost.allocateAppWidgetIdForSystem(KEYGUARD_HOST_ID); } mAppWidgetManager.bindAppWidgetId( mAppWidgetId, intent.getComponent(), mExtraConfigureOptions); result = RESULT_OK; } catch (IllegalArgumentException e) { // This is thrown if they're already bound, or otherwise somehow // bogus. Set the result to canceled, and exit. The app *should* // clean up at this point. We could pass the error along, but // it's not clear that that's useful -- the widget will simply not // appear. result = RESULT_CANCELED; } setResultData(result, null); } if (mAddingToKeyguard) { onActivityResult(REQUEST_PICK_APPWIDGET, result, mResultData); } else { finish(); } } protected void onDestroy() { if (mAppWidgetAdapter != null) { mAppWidgetAdapter.cancelAllWidgetPreviewLoaders(); } super.onDestroy(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET) { int appWidgetId; if (data == null) { appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID ; } else { appWidgetId = data.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == Activity.RESULT_OK) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); AppWidgetProviderInfo appWidget = null; appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId); if (appWidget.configure != null) { // Launch over to configure widget, if needed Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); intent.setComponent(appWidget.configure); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET); } else { // Otherwise just add it onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data); } } else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) { mLockPatternUtils.addAppWidget(appWidgetId, 0); finishDelayedAndShowLockScreen(appWidgetId); } else { if (mAddingToKeyguard && mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { AppWidgetHost.deleteAppWidgetIdForSystem(mAppWidgetId); } finishDelayedAndShowLockScreen(AppWidgetManager.INVALID_APPWIDGET_ID); } } } private void finishDelayedAndShowLockScreen(int appWidgetId) { IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE); IWindowManager iWm = IWindowManager.Stub.asInterface(b); Bundle opts = null; if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { opts = new Bundle(); opts.putInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, appWidgetId); } try { iWm.lockNow(opts); } catch (RemoteException e) { } // Change background to all black ViewGroup root = (ViewGroup) findViewById(R.id.layout_root); root.setBackgroundColor(0xFF000000); // Hide all children final int childCount = root.getChildCount(); for (int i = 0; i < childCount; i++) { root.getChildAt(i).setVisibility(View.INVISIBLE); } mGridView.postDelayed(new Runnable() { public void run() { finish(); } }, 500); } void startActivityForResultSafely(Intent intent, int requestCode) { try { startActivityForResult(intent, requestCode); } catch (ActivityNotFoundException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); } catch (SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Settings does not have the permission to launch " + intent, e); } } }