/*
* 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.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
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.TextView;
import android.widget.Toast;
import com.android.internal.widget.LockPatternUtils;
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}.
*
* 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 {
private static final String TAG = "KeyguardAppWidgetPickActivity";
private static final int REQUEST_PICK_APPWIDGET = 126;
private static final int REQUEST_CREATE_APPWIDGET = 127;
private AppWidgetLoader- mAppWidgetLoader;
private List
- mItems;
private GridView mGridView;
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 boolean mSuccess;
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.keyguard_appwidget_pick_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();
}
mGridView = (GridView) findViewById(R.id.widget_list);
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetLoader = new AppWidgetLoader
- (this, mAppWidgetManager, this);
mItems = mAppWidgetLoader.getItems(getIntent());
AppWidgetAdapter adapter = new AppWidgetAdapter(this, mItems);
mGridView.setAdapter(adapter);
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);
}
private static class EmptyDrawable extends Drawable {
private final int mWidth;
private final int mHeight;
EmptyDrawable(int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public int getIntrinsicWidth() {
return mWidth;
}
@Override
public int getIntrinsicHeight() {
return mHeight;
}
@Override
public int getMinimumWidth() {
return mWidth;
}
@Override
public int getMinimumHeight() {
return mHeight;
}
@Override
public void draw(Canvas canvas) {
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter cf) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
/**
* Utility class to resize icons to match default icon size. Code is mostly
* borrowed from Launcher.
*/
private static class IconResizer {
private final int mIconWidth;
private final int mIconHeight;
private final DisplayMetrics mMetrics;
private final Rect mOldBounds = new Rect();
private final Canvas mCanvas = new Canvas();
public IconResizer(int width, int height, DisplayMetrics metrics) {
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
mMetrics = metrics;
mIconWidth = width;
mIconHeight = height;
}
/**
* Returns a Drawable representing the thumbnail of the specified Drawable.
* The size of the thumbnail is defined by the dimension
* android.R.dimen.launcher_application_icon_size.
*
* This method is not thread-safe and should be invoked on the UI thread only.
*
* @param icon The icon to get a thumbnail of.
*
* @return A thumbnail for the specified icon or the icon itself if the
* thumbnail could not be created.
*/
public Drawable createIconThumbnail(Drawable icon) {
int width = mIconWidth;
int height = mIconHeight;
if (icon == null) {
return new EmptyDrawable(width, height);
}
try {
if (icon instanceof PaintDrawable) {
PaintDrawable painter = (PaintDrawable) icon;
painter.setIntrinsicWidth(width);
painter.setIntrinsicHeight(height);
} else if (icon instanceof BitmapDrawable) {
// Ensure the bitmap has a density.
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
bitmapDrawable.setTargetDensity(mMetrics);
}
}
int iconWidth = icon.getIntrinsicWidth();
int iconHeight = icon.getIntrinsicHeight();
if (iconWidth > 0 && iconHeight > 0) {
if (width < iconWidth || height < iconHeight) {
final float ratio = (float) iconWidth / iconHeight;
if (iconWidth > iconHeight) {
height = (int) (width / ratio);
} else if (iconHeight > iconWidth) {
width = (int) (height * ratio);
}
final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ?
Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
final Canvas canvas = mCanvas;
canvas.setBitmap(thumb);
// Copy the old bounds to restore them later
// If we were to do oldBounds = icon.getBounds(),
// the call to setBounds() that follows would
// change the same instance and we would lose the
// old bounds
mOldBounds.set(icon.getBounds());
final int x = (mIconWidth - width) / 2;
final int y = (mIconHeight - height) / 2;
icon.setBounds(x, y, x + width, y + height);
icon.draw(canvas);
icon.setBounds(mOldBounds);
//noinspection deprecation
icon = new BitmapDrawable(thumb);
((BitmapDrawable) icon).setTargetDensity(mMetrics);
canvas.setBitmap(null);
} else if (iconWidth < width && iconHeight < height) {
final Bitmap.Config c = Bitmap.Config.ARGB_8888;
final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c);
final Canvas canvas = mCanvas;
canvas.setBitmap(thumb);
mOldBounds.set(icon.getBounds());
final int x = (width - iconWidth) / 2;
final int y = (height - iconHeight) / 2;
icon.setBounds(x, y, x + iconWidth, y + iconHeight);
icon.draw(canvas);
icon.setBounds(mOldBounds);
//noinspection deprecation
icon = new BitmapDrawable(thumb);
((BitmapDrawable) icon).setTargetDensity(mMetrics);
canvas.setBitmap(null);
}
}
} catch (Throwable t) {
icon = new EmptyDrawable(width, height);
}
return icon;
}
}
/**
* Item that appears in the AppWidget picker grid.
*/
public static class Item implements AppWidgetLoader.LabelledItem {
protected static IconResizer sResizer;
protected IconResizer getResizer(Context context) {
if (sResizer == null) {
final Resources resources = context.getResources();
int size = (int) resources.getDimension(android.R.dimen.app_icon_size);
sResizer = new IconResizer(size, size, resources.getDisplayMetrics());
}
return sResizer;
}
CharSequence label;
Drawable icon;
String packageName;
String className;
Bundle extras;
/**
* Create a list item from given label and icon.
*/
Item(Context context, CharSequence label, Drawable icon) {
this.label = label;
this.icon = getResizer(context).createIconThumbnail(icon);
}
/**
* Create a list item and fill it with details from the given
* {@link ResolveInfo} object.
*/
Item(Context context, PackageManager pm, ResolveInfo resolveInfo) {
label = resolveInfo.loadLabel(pm);
if (label == null && resolveInfo.activityInfo != null) {
label = resolveInfo.activityInfo.name;
}
icon = getResizer(context).createIconThumbnail(resolveInfo.loadIcon(pm));
packageName = resolveInfo.activityInfo.applicationInfo.packageName;
className = resolveInfo.activityInfo.name;
}
/**
* 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;
}
}
@Override
public Item createItem(Context context, AppWidgetProviderInfo info, Bundle extras) {
CharSequence label = info.label;
Drawable icon = null;
if (info.icon != 0) {
try {
final Resources res = context.getResources();
final int density = res.getDisplayMetrics().densityDpi;
int iconDensity;
switch (density) {
case DisplayMetrics.DENSITY_MEDIUM:
iconDensity = DisplayMetrics.DENSITY_LOW;
case DisplayMetrics.DENSITY_TV:
iconDensity = DisplayMetrics.DENSITY_MEDIUM;
case DisplayMetrics.DENSITY_HIGH:
iconDensity = DisplayMetrics.DENSITY_MEDIUM;
case DisplayMetrics.DENSITY_XHIGH:
iconDensity = DisplayMetrics.DENSITY_HIGH;
case DisplayMetrics.DENSITY_XXHIGH:
iconDensity = DisplayMetrics.DENSITY_XHIGH;
default:
// The density is some abnormal value. Return some other
// abnormal value that is a reasonable scaling of it.
iconDensity = (int)((density*0.75f)+.5f);
}
Resources packageResources = getPackageManager().
getResourcesForApplication(info.provider.getPackageName());
icon = packageResources.getDrawableForDensity(info.icon, iconDensity);
} catch (NameNotFoundException e) {
Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+ " for provider: " + info.provider);
}
if (icon == null) {
Log.w(TAG, "Can't load icon drawable 0x" + Integer.toHexString(info.icon)
+ " for provider: " + info.provider);
}
}
Item item = new Item(context, label, 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
- mItems;
/**
* Create an adapter for the given items.
*/
public AppWidgetAdapter(Context context, List
- 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.icon_and_label);
textView.setText(item.label);
textView.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null);
return convertView;
}
}
/**
* {@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 {
Bundle options = null;
if (intent.getExtras() != null) {
options = intent.getExtras().getBundle(
AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
}
mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent(), options);
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 (!mSuccess && mAddingToKeyguard &&
mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
AppWidgetHost.deleteAppWidgetIdForSystem(mAppWidgetId);
}
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 = (data == null) ? -1 : data.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
if ((requestCode == REQUEST_PICK_APPWIDGET) &&
resultCode == Activity.RESULT_OK) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
boolean defaultWidget =
data.getBooleanExtra(LockPatternUtils.EXTRA_DEFAULT_WIDGET, false);
AppWidgetProviderInfo appWidget = null;
if (!defaultWidget) {
appWidget = appWidgetManager.getAppWidgetInfo(appWidgetId);
}
if (!defaultWidget && 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
if (defaultWidget) {
// If we selected "none", delete the allocated id
AppWidgetHost.deleteAppWidgetIdForSystem(appWidgetId);
data.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
LockPatternUtils.ID_DEFAULT_STATUS_WIDGET);
}
onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
}
} else if (requestCode == REQUEST_CREATE_APPWIDGET && resultCode == Activity.RESULT_OK) {
mSuccess = true;
mLockPatternUtils.addAppWidget(appWidgetId, 0);
finishDelayedAndShowLockScreen();
} else {
finishDelayedAndShowLockScreen();
}
}
}
private void finishDelayedAndShowLockScreen() {
IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE);
IWindowManager iWm = IWindowManager.Stub.asInterface(b);
try {
iWm.lockNow(null);
} 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);
}
}
}