--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+
+ android:layout_width="match_parent"
+ android:layout_height="64dp">
+ <ImageView
+ android:id="@+id/provider_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginLeft="20dp"
+ android:maxWidth="32dp"
+ android:maxHeight="32dp"
+ android:scaleType="fitCenter"
+ android:src="@drawable/ic_launcher_application" />
+ <TextView
+ android:id="@+id/provider"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="5dp"
+ android:gravity="center_vertical"
+ android:textSize="18sp" />
+</LinearLayout>
<!-- The format string for the dimensions of a widget in the drawer -->
<string name="widget_dims_format" translatable="false">%1$d x %2$d</string>
+ <!-- External-drop widget pick label format string [CHAR_LIMIT=25] -->
+ <string name="external_drop_widget_pick_format" translatable="false">%1$s (%2$d x %3$d)</string>
+ <!-- External-drop widget error string -->
+ <string name="external_drop_widget_error">Could not install clipboard item</string>
+ <!-- External-drop widget pick title -->
+ <string name="external_drop_widget_pick_title">Select widget to create</string>
+
<!-- Folders -->
<skip />
<!-- Label of Folder name field in Rename folder dialog box -->
final int count = list.size();
layout.removeAllViews();
for (int i = 0; i < count; ++i) {
- AppWidgetProviderInfo info = (AppWidgetProviderInfo) list.get(i);
- PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo();
- createItemInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
- createItemInfo.componentName = info.provider;
- createItemInfo.minWidth = info.minWidth;
- createItemInfo.minHeight = info.minHeight;
+ final AppWidgetProviderInfo info = (AppWidgetProviderInfo) list.get(i);
+ final PendingAddWidgetInfo createItemInfo = new PendingAddWidgetInfo(info, null, null);
LinearLayout l = (LinearLayout) mInflater.inflate(
R.layout.customize_paged_view_widget, layout, false);
public static final String ACTION_INSTALL_SHORTCUT =
"com.android.launcher.action.INSTALL_SHORTCUT";
+ // A mime-type representing shortcut data
+ public static final String SHORTCUT_MIMETYPE =
+ "com.android.launcher/shortcut";
+
private final int[] mCoordinates = new int[2];
public void onReceive(Context context, Intent data) {
package com.android.launcher2;
+import java.util.List;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.launcher.R;
+
/**
* We will likely flesh this out later, to handle allow external apps to place widgets, but for now,
public class InstallWidgetReceiver {
public static final String ACTION_INSTALL_WIDGET =
"com.android.launcher.action.INSTALL_WIDGET";
+ public static final String ACTION_SUPPORTS_CLIPDATA_MIMETYPE =
+ "com.android.launcher.action.SUPPORTS_CLIPDATA_MIMETYPE";
// Currently not exposed. Put into Intent when we want to make it public.
// TEMP: Should we call this "EXTRA_APPWIDGET_PROVIDER"?
public static final String EXTRA_APPWIDGET_COMPONENT =
"com.android.launcher.extra.widget.COMPONENT";
+ public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE =
+ "com.android.launcher.extra.widget.CONFIGURATION_DATA_MIME_TYPE";
public static final String EXTRA_APPWIDGET_CONFIGURATION_DATA =
"com.android.launcher.extra.widget.CONFIGURATION_DATA";
+
+ /**
+ * A simple data class that contains per-item information that the adapter below can reference.
+ */
+ public static class WidgetMimeTypeHandlerData {
+ public ResolveInfo resolveInfo;
+ public AppWidgetProviderInfo widgetInfo;
+
+ public WidgetMimeTypeHandlerData(ResolveInfo rInfo, AppWidgetProviderInfo wInfo) {
+ resolveInfo = rInfo;
+ widgetInfo = wInfo;
+ }
+ }
+
+ /**
+ * The ListAdapter which presents all the valid widgets that can be created for a given drop.
+ */
+ public static class WidgetListAdapter implements ListAdapter, DialogInterface.OnClickListener {
+ private LayoutInflater mInflater;
+ private Launcher mLauncher;
+ private String mMimeType;
+ private ClipData mClipData;
+ private List<WidgetMimeTypeHandlerData> mActivities;
+ private CellLayout mTargetLayout;
+ private int mTargetLayoutScreen;
+ private int[] mTargetLayoutPos;
+
+ public WidgetListAdapter(Launcher l, String mimeType, ClipData data,
+ List<WidgetMimeTypeHandlerData> list, CellLayout target,
+ int targetScreen, int[] targetPos) {
+ mLauncher = l;
+ mMimeType = mimeType;
+ mClipData = data;
+ mActivities = list;
+ mTargetLayout = target;
+ mTargetLayoutScreen = targetScreen;
+ mTargetLayoutPos = targetPos;
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ }
+
+ @Override
+ public int getCount() {
+ return mActivities.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Context context = parent.getContext();
+ final PackageManager packageManager = context.getPackageManager();
+
+ // Lazy-create inflater
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(context);
+ }
+
+ // Use the convert-view where possible
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.external_widget_drop_list_item, parent,
+ false);
+ }
+
+ final WidgetMimeTypeHandlerData data = mActivities.get(position);
+ final ResolveInfo resolveInfo = data.resolveInfo;
+ final AppWidgetProviderInfo widgetInfo = data.widgetInfo;
+
+ // Set the icon
+ Drawable d = resolveInfo.loadIcon(packageManager);
+ ImageView i = (ImageView) convertView.findViewById(R.id.provider_icon);
+ i.setImageDrawable(d);
+
+ // Set the text
+ final CharSequence component = resolveInfo.loadLabel(packageManager);
+ final int[] widgetSpan = new int[2];
+ mTargetLayout.rectToCell(widgetInfo.minWidth, widgetInfo.minHeight, widgetSpan);
+ TextView t = (TextView) convertView.findViewById(R.id.provider);
+ t.setText(context.getString(R.string.external_drop_widget_pick_format,
+ component, widgetSpan[0], widgetSpan[1]));
+
+ return convertView;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mActivities.isEmpty();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return true;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final LauncherModel model = mLauncher.getModel();
+ final AppWidgetProviderInfo widgetInfo = mActivities.get(which).widgetInfo;
+
+ final PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo(widgetInfo, mMimeType,
+ mClipData);
+ mLauncher.addAppWidgetFromDrop(createInfo, mTargetLayoutScreen, mTargetLayoutPos);
+ }
+ }
}
package com.android.launcher2;
-import com.android.common.Search;
-import com.android.launcher.R;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.Intent.ShortcutIconResource;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.WindowManager;
-import android.view.View.OnLongClickListener;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TabHost;
-import android.widget.TextView;
-import android.widget.Toast;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabHost.TabContentFactory;
+import android.widget.TextView;
+import android.widget.Toast;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
+import com.android.common.Search;
+import com.android.launcher.R;
/**
* Default launcher application.
intent.setComponent(appWidget.configure);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
if (info != null) {
- intent.putExtra(InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA,
- info.configurationData);
+ if (info.mimeType != null && !info.mimeType.isEmpty()) {
+ intent.putExtra(
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA_MIME_TYPE,
+ info.mimeType);
+
+ final String mimeType = info.mimeType;
+ final ClipData clipData = (ClipData) info.configurationData;
+ final ClipDescription clipDesc = clipData.getDescription();
+ for (int i = 0; i < clipDesc.getMimeTypeCount(); ++i) {
+ if (clipDesc.getMimeType(i).equals(mimeType)) {
+ final ClipData.Item item = clipData.getItem(i);
+ final CharSequence stringData = item.getText();
+ final Uri uriData = item.getUri();
+ final Intent intentData = item.getIntent();
+ final String key =
+ InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA;
+ if (uriData != null) {
+ intent.putExtra(key, uriData);
+ } else if (intentData != null) {
+ intent.putExtra(key, intentData);
+ } else if (stringData != null) {
+ intent.putExtra(key, stringData);
+ }
+ break;
+ }
+ }
+ }
}
startActivityForResultSafely(intent, REQUEST_CREATE_APPWIDGET);
package com.android.launcher2;
-import com.android.launcher.R;
+import java.lang.ref.WeakReference;
+import java.net.URISyntaxException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.os.SystemClock;
import android.util.Log;
-import java.lang.ref.WeakReference;
-import java.net.URISyntaxException;
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
+import com.android.launcher.R;
+import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
}
/**
- * Ensures that a given shortcut intent actually has all the fields that we need to create a
- * proper ShortcutInfo.
- */
- boolean validateShortcutIntent(Intent data) {
- // We don't require Intent.EXTRA_SHORTCUT_ICON, since we can pull a default fallback icon
- return InstallShortcutReceiver.ACTION_INSTALL_SHORTCUT.equals(data.getAction()) &&
- (data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME) != null) &&
- (data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT) != null);
- }
-
- /**
- * Ensures that a given widget intent actually has all the fields that we need to create a
- * proper widget.
- */
- boolean validateWidgetIntent(Intent data) {
- // We don't require Intent.EXTRA_APPWIDGET_CONFIGURATION_DATA, since that will just be
- // forwarded onto the widget's configuration activity if it exists
- return InstallWidgetReceiver.ACTION_INSTALL_WIDGET.equals(data.getAction()) &&
- (data.getStringExtra(InstallWidgetReceiver.EXTRA_APPWIDGET_COMPONENT) != null);
- }
-
- /**
* Attempts to find an AppWidgetProviderInfo that matches the given component.
*/
AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,
return null;
}
+ /**
+ * Returns a list of all the widgets that can handle configuration with a particular mimeType.
+ */
+ List<WidgetMimeTypeHandlerData> resolveWidgetsForMimeType(Context context, String mimeType) {
+ final PackageManager packageManager = context.getPackageManager();
+ final List<WidgetMimeTypeHandlerData> supportedConfigurationActivities =
+ new ArrayList<WidgetMimeTypeHandlerData>();
+
+ final Intent supportsIntent =
+ new Intent(InstallWidgetReceiver.ACTION_SUPPORTS_CLIPDATA_MIMETYPE);
+ supportsIntent.setType(mimeType);
+
+ // Create a set of widget configuration components that we can test against
+ final List<AppWidgetProviderInfo> widgets =
+ AppWidgetManager.getInstance(context).getInstalledProviders();
+ final HashMap<ComponentName, AppWidgetProviderInfo> configurationComponentToWidget =
+ new HashMap<ComponentName, AppWidgetProviderInfo>();
+ for (AppWidgetProviderInfo info : widgets) {
+ configurationComponentToWidget.put(info.configure, info);
+ }
+
+ // Run through each of the intents that can handle this type of clip data, and cross
+ // reference them with the components that are actual configuration components
+ final List<ResolveInfo> activities = packageManager.queryIntentActivities(supportsIntent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ for (ResolveInfo info : activities) {
+ final ActivityInfo activityInfo = info.activityInfo;
+ final ComponentName infoComponent = new ComponentName(activityInfo.packageName,
+ activityInfo.name);
+ if (configurationComponentToWidget.containsKey(infoComponent)) {
+ supportedConfigurationActivities.add(
+ new InstallWidgetReceiver.WidgetMimeTypeHandlerData(info,
+ configurationComponentToWidget.get(infoComponent)));
+ }
+ }
+ return supportedConfigurationActivities;
+ }
+
ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
package com.android.launcher2;
+import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.os.Parcelable;
// Any configuration data that we want to pass to a configuration activity when
// starting up a widget
+ String mimeType;
Parcelable configurationData;
+
+ public PendingAddWidgetInfo(AppWidgetProviderInfo i, String dataMimeType, Parcelable data) {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+ componentName = i.provider;
+ minWidth = i.minWidth;
+ minHeight = i.minHeight;
+ if (dataMimeType != null && data != null) {
+ mimeType = dataMimeType;
+ configurationData = data;
+ }
+ }
}
\ No newline at end of file
package com.android.launcher2;
-import com.android.launcher.R;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.app.AlertDialog;
import android.app.WallpaperManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.widget.TextView;
import android.widget.Toast;
-import java.util.ArrayList;
-import java.util.HashSet;
+import com.android.launcher.R;
+import com.android.launcher2.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
*/
@Override
public boolean onDragEvent(DragEvent event) {
+ final ClipDescription desc = event.getClipDescription();
final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
final int[] pos = new int[2];
layout.getLocationOnScreen(pos);
return false;
}
- ClipDescription desc = event.getClipDescription();
- if (desc.filterMimeTypes(ClipDescription.MIMETYPE_TEXT_INTENT) != null) {
- // Create the drag outline
- // We need to add extra padding to the bitmap to make room for the glow effect
- final Canvas canvas = new Canvas();
- final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
- mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
+ // Create the drag outline
+ // We need to add extra padding to the bitmap to make room for the glow effect
+ final Canvas canvas = new Canvas();
+ final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
+ mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
- // Show the current page outlines to indicate that we can accept this drop
- showOutlines();
- layout.setHover(true);
- layout.onDragEnter();
- layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
+ // Show the current page outlines to indicate that we can accept this drop
+ showOutlines();
+ layout.setHover(true);
+ layout.onDragEnter();
+ layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
- return true;
- }
- break;
+ return true;
case DragEvent.ACTION_DRAG_LOCATION:
// Visualize the drop location
layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
int newDropCount = 0;
final LauncherModel model = mLauncher.getModel();
final ClipData data = event.getClipData();
- final int itemCount = data.getItemCount();
- for (int i = 0; i < itemCount; ++i) {
- final Intent intent = data.getItem(i).getIntent();
- if (intent != null) {
- Object info = null;
- if (model.validateShortcutIntent(intent)) {
- info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
- } else if (model.validateWidgetIntent(intent)) {
- final ComponentName component = ComponentName.unflattenFromString(
- intent.getStringExtra(InstallWidgetReceiver.EXTRA_APPWIDGET_COMPONENT));
- final AppWidgetProviderInfo appInfo =
- model.findAppWidgetProviderInfoWithComponent(mContext, component);
-
- PendingAddWidgetInfo createInfo = new PendingAddWidgetInfo();
- createInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
- createInfo.componentName = appInfo.provider;
- createInfo.minWidth = appInfo.minWidth;
- createInfo.minHeight = appInfo.minHeight;
- createInfo.configurationData = intent.getParcelableExtra(
- InstallWidgetReceiver.EXTRA_APPWIDGET_CONFIGURATION_DATA);
- info = createInfo;
- }
- if (info != null) {
- onDropExternal(x, y, info, layout);
- newDropCount++;
+ // We assume that the mime types are ordered in descending importance of
+ // representation. So we enumerate the list of mime types and alert the
+ // user if any widgets can handle the drop. Only the most preferred
+ // representation will be handled.
+ pos[0] = x;
+ pos[1] = y;
+ final int mimeTypeCount = desc.getMimeTypeCount();
+ for (int j = 0; j < mimeTypeCount; ++j) {
+ final String mimeType = desc.getMimeType(j);
+
+ if (mimeType.equals(InstallShortcutReceiver.SHORTCUT_MIMETYPE)) {
+ final Intent intent = data.getItem(j).getIntent();
+ Object info = model.infoFromShortcutIntent(mContext, intent, data.getIcon());
+ onDropExternal(x, y, info, layout);
+ } else {
+ final List<WidgetMimeTypeHandlerData> widgets =
+ model.resolveWidgetsForMimeType(mContext, mimeType);
+ final int numWidgets = widgets.size();
+
+ if (numWidgets == 0) {
+ continue;
+ } else if (numWidgets == 1) {
+ // If there is only one item, then go ahead and add and configure
+ // that widget
+ final AppWidgetProviderInfo widgetInfo = widgets.get(0).widgetInfo;
+ final PendingAddWidgetInfo createInfo =
+ new PendingAddWidgetInfo(widgetInfo, mimeType, data);
+ mLauncher.addAppWidgetFromDrop(createInfo, mCurrentPage, pos);
+ } else if (numWidgets > 1) {
+ // Show the widget picker dialog if there is more than one widget
+ // that can handle this data type
+ final InstallWidgetReceiver.WidgetListAdapter adapter =
+ new InstallWidgetReceiver.WidgetListAdapter(mLauncher, mimeType,
+ data, widgets, layout, mCurrentPage, pos);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(mContext);
+ builder.setAdapter(adapter, adapter);
+ builder.setCancelable(true);
+ builder.setTitle(mContext.getString(
+ R.string.external_drop_widget_pick_title));
+ builder.setIcon(R.drawable.ic_no_applications);
+ builder.show();
}
}
+ newDropCount++;
+ break;
}
// Show error message if we couldn't accept any of the items
if (newDropCount <= 0) {
- Toast.makeText(mContext, "Only Shortcut Intents accepted.",
+ Toast.makeText(mContext, mContext.getString(R.string.external_drop_widget_error),
Toast.LENGTH_SHORT).show();
}