diff --git a/apps/WidgetPreview/Android.mk b/apps/WidgetPreview/Android.mk
new file mode 100644
index 000000000..04a492798
--- /dev/null
+++ b/apps/WidgetPreview/Android.mk
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := WidgetPreview
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/apps/WidgetPreview/AndroidManifest.xml b/apps/WidgetPreview/AndroidManifest.xml
new file mode 100644
index 000000000..3d50188b1
--- /dev/null
+++ b/apps/WidgetPreview/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/WidgetPreview/res/drawable-hdpi/ic_widget_preview.png b/apps/WidgetPreview/res/drawable-hdpi/ic_widget_preview.png
new file mode 100644
index 000000000..168cdda46
Binary files /dev/null and b/apps/WidgetPreview/res/drawable-hdpi/ic_widget_preview.png differ
diff --git a/apps/WidgetPreview/res/drawable-mdpi/ic_widget_preview.png b/apps/WidgetPreview/res/drawable-mdpi/ic_widget_preview.png
new file mode 100644
index 000000000..55c7661de
Binary files /dev/null and b/apps/WidgetPreview/res/drawable-mdpi/ic_widget_preview.png differ
diff --git a/apps/WidgetPreview/res/layout/activity_main.xml b/apps/WidgetPreview/res/layout/activity_main.xml
new file mode 100644
index 000000000..e43d2463e
--- /dev/null
+++ b/apps/WidgetPreview/res/layout/activity_main.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/WidgetPreview/res/values-land/dimens.xml b/apps/WidgetPreview/res/values-land/dimens.xml
new file mode 100644
index 000000000..dea2576b2
--- /dev/null
+++ b/apps/WidgetPreview/res/values-land/dimens.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 106dip
+ 74dip
+ 0dp
+ 0dp
+
diff --git a/apps/WidgetPreview/res/values-port/dimens.xml b/apps/WidgetPreview/res/values-port/dimens.xml
new file mode 100644
index 000000000..0276ffb82
--- /dev/null
+++ b/apps/WidgetPreview/res/values-port/dimens.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 80dip
+ 100dip
+ 0dp
+ 0dp
+
diff --git a/apps/WidgetPreview/res/values-xlarge/dimens.xml b/apps/WidgetPreview/res/values-xlarge/dimens.xml
new file mode 100644
index 000000000..b6b5dfe04
--- /dev/null
+++ b/apps/WidgetPreview/res/values-xlarge/dimens.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 96dip
+ 96dip
+ 0dp
+ 0dp
+
diff --git a/apps/WidgetPreview/res/values/dimens.xml b/apps/WidgetPreview/res/values/dimens.xml
new file mode 100644
index 000000000..ccd71d3ae
--- /dev/null
+++ b/apps/WidgetPreview/res/values/dimens.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ 196dip
+
diff --git a/apps/WidgetPreview/res/values/strings.xml b/apps/WidgetPreview/res/values/strings.xml
new file mode 100644
index 000000000..2cc9ccfc0
--- /dev/null
+++ b/apps/WidgetPreview/res/values/strings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ Widget Preview
+ Saving widget preview...
+ Widget preview saved!
+ Please take a snapshot first
+ Error saving preview
+ Error configuring, no configuration activity found
+ Widget preview
+ Attached is the preview of your AppWidget
+
+ Take Snapshot
+ Email Preview
+
diff --git a/apps/WidgetPreview/src/com/android/widgetpreview/WidgetPreviewActivity.java b/apps/WidgetPreview/src/com/android/widgetpreview/WidgetPreviewActivity.java
new file mode 100644
index 000000000..1026ba830
--- /dev/null
+++ b/apps/WidgetPreview/src/com/android/widgetpreview/WidgetPreviewActivity.java
@@ -0,0 +1,322 @@
+/*
+ * 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.
+ */
+
+package com.android.widgetpreview;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class WidgetPreviewActivity extends Activity implements OnClickListener {
+
+ private static final String LOG_TAG = "WidgetPreviewActivity";
+ private static final boolean DEBUG = true;
+ private static final int APPWIDGET_HOST_ID = 2048;
+ private static final int REQUEST_WIDGET = 0;
+ private static final int REQUEST_CONFIGURE = 1;
+
+ private AppWidgetHost mAppWidgetHost = null;
+ private FrameLayout mAppWidgetFrame = null;
+ private AppWidgetHostView mAppWidgetView = null;
+ private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+ private String mAppWidgetName;
+ private int mPreviewWidth;
+ private int mPreviewHeight;
+
+ private Button mSnapshotButton = null;
+ private Button mEmailButton = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mAppWidgetFrame = (FrameLayout)findViewById(R.id.main_frame);
+ mSnapshotButton = (Button)findViewById(R.id.snapshot_button);
+ mSnapshotButton.setOnClickListener(this);
+ mEmailButton = (Button)findViewById(R.id.email_button);
+ mEmailButton.setOnClickListener(this);
+
+ mAppWidgetHost = new AppWidgetHost(getApplicationContext(), APPWIDGET_HOST_ID);
+
+ final Object retainedObj = getLastNonConfigurationInstance();
+ if (retainedObj instanceof AppWidgetProviderInfo) {
+ AppWidgetProviderInfo info = (AppWidgetProviderInfo) retainedObj;
+ int id = mAppWidgetHost.allocateAppWidgetId();
+ AppWidgetManager.getInstance(getBaseContext()).bindAppWidgetId(id, info.provider);
+ setAppWidget(id);
+ } else {
+ startChooseActivity();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mAppWidgetHost.startListening();
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ AppWidgetProviderInfo info = AppWidgetManager.getInstance(
+ getBaseContext()).getAppWidgetInfo(mAppWidgetId);
+ return info;
+ }
+
+ private void startChooseActivity() {
+ int id = mAppWidgetHost.allocateAppWidgetId();
+ Intent selectIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
+ selectIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id);
+ startActivityForResult(selectIntent, REQUEST_WIDGET);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_WIDGET) {
+ if (data != null) {
+ int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+ if (data.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ appWidgetId = data.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
+ }
+
+ if (resultCode == RESULT_OK) {
+ setAppWidget(appWidgetId);
+ } else {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ finish();
+ }
+ } else {
+ finish();
+ }
+ } else if (requestCode == REQUEST_CONFIGURE) {
+ if (data != null) {
+ int appWidgetId = data.getExtras().getInt(
+ AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ if (resultCode == RESULT_OK) {
+ finishSetAppWidget(appWidgetId);
+ } else {
+ mAppWidgetHost.deleteAppWidgetId(appWidgetId);
+ }
+ }
+ }
+ }
+
+ private void setAppWidget(int appWidgetId) {
+ if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
+ mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
+ }
+
+ /* Check for configuration */
+ AppWidgetProviderInfo providerInfo =
+ AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId);
+
+ if (providerInfo.configure != null) {
+ Intent configureIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
+ configureIntent.setComponent(providerInfo.configure);
+ configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+
+ if (configureIntent != null) {
+ try {
+ startActivityForResult(configureIntent, REQUEST_CONFIGURE);
+ } catch (ActivityNotFoundException e) {
+ Log.d(LOG_TAG, "Configuration activity not found: " + e);
+ Toast errorToast = Toast.makeText(
+ getBaseContext(), R.string.configure_error, Toast.LENGTH_SHORT);
+ errorToast.show();
+ }
+ }
+ } else {
+ finishSetAppWidget(appWidgetId);
+ }
+ }
+
+ private void finishSetAppWidget(int appWidgetId) {
+ AppWidgetProviderInfo providerInfo =
+ AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId);
+ if (providerInfo != null) {
+ mAppWidgetView =
+ mAppWidgetHost.createView(getBaseContext(), appWidgetId, providerInfo);
+
+ int [] dimensions =
+ getLauncherCellDimensions(providerInfo.minWidth, providerInfo.minHeight);
+
+ mPreviewWidth = dimensions[0];
+ mPreviewHeight = dimensions[1];
+
+ mAppWidgetName =
+ AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId).label;
+ mAppWidgetName = mAppWidgetName.replaceAll("[^a-zA-Z0-9]", "_");
+
+ ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mAppWidgetView.setLayoutParams(p);
+ mAppWidgetFrame.removeAllViews();
+ mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
+ mAppWidgetFrame.addView(mAppWidgetView, mPreviewWidth, mPreviewHeight);
+ mAppWidgetId = appWidgetId;
+ }
+ }
+
+ // Taken from CellLayout.java
+ public int[] getLauncherCellDimensions(int width, int height) {
+ // Always assume we're working with the smallest span to make sure we
+ // reserve enough space in both orientations.
+ Resources resources = getResources();
+ int cellWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
+ int cellHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
+ int widthGap = resources.getDimensionPixelSize(R.dimen.workspace_width_gap);
+ int heightGap = resources.getDimensionPixelSize(R.dimen.workspace_height_gap);
+ int previewCellSize = resources.getDimensionPixelSize(R.dimen.preview_cell_size);
+
+ // This logic imitates Launcher's CellLayout.rectToCell.
+ // Always round up to next largest cell
+ int smallerSize = Math.min(cellWidth, cellHeight);
+ int spanX = (width + smallerSize) / smallerSize;
+ int spanY = (height + smallerSize) / smallerSize;
+
+ // We use a fixed preview cell size so that you get the same preview image for
+ // the same cell-sized widgets across all devices
+ width = spanX * previewCellSize + ((spanX - 1) * widthGap);
+ height = spanY * previewCellSize + ((spanY - 1) * heightGap);
+ return new int[] { width, height };
+ }
+
+ private File buildFile(String name) {
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ return null;
+ }
+
+ File path = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS);
+ int orientationCode = getResources().getConfiguration().orientation;
+ String orientation;
+ if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ orientation = "landscape";
+ } else if (orientationCode == Configuration.ORIENTATION_PORTRAIT) {
+ orientation = "portrait";
+ } else if (orientationCode == Configuration.ORIENTATION_SQUARE) {
+ orientation = "square";
+ } else {
+ orientation = "undefined";
+ }
+ return new File(path, name + "_ori_" + orientation + ".png");
+ }
+
+ public Bitmap getPreviewBitmap() {
+ mAppWidgetView.invalidate();
+ Bitmap bmp = Bitmap.createBitmap(
+ mAppWidgetView.getWidth(), mAppWidgetView.getHeight(), Config.ARGB_8888);
+ Canvas c = new Canvas(bmp);
+ mAppWidgetView.draw(c);
+ return bmp;
+ }
+
+ private boolean saveImage(Bitmap bmp, String name) {
+ File pic = buildFile(mAppWidgetName);
+ if (pic == null) {
+ Log.d(LOG_TAG, "External storage not present");
+ return false;
+ }
+
+ pic.getParentFile().mkdirs();
+ FileOutputStream fout = null;
+ try {
+ fout = new FileOutputStream(pic);
+ if (!bmp.compress(CompressFormat.PNG, 100, fout)) {
+ Log.d(LOG_TAG, "Failed to compress image");
+ return false;
+ }
+ return true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Error writing to disk: " + e);
+ } finally {
+ try {
+ if (fout != null) {
+ fout.close();
+ }
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not close file: " + e);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!getFragmentManager().popBackStackImmediate()) {
+ startChooseActivity();
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mSnapshotButton) {
+ int textId = R.string.saving_preview;
+
+ Toast preToast = Toast.makeText(getBaseContext(), textId, Toast.LENGTH_SHORT);
+ preToast.show();
+
+ Bitmap bmp = getPreviewBitmap();
+ if (saveImage(bmp, mAppWidgetName)) {
+ textId = R.string.preview_saved;
+ } else {
+ textId = R.string.preview_save_error;
+ }
+
+ Toast postToast = Toast.makeText(getBaseContext(), textId, Toast.LENGTH_SHORT);
+ postToast.show();
+ } else if (v == mEmailButton) {
+ File file = buildFile(mAppWidgetName);
+ if (file.exists()) {
+ Intent emailIntent = new Intent(Intent.ACTION_SEND);
+ emailIntent.setType("image/png");
+ emailIntent.putExtra(Intent.EXTRA_SUBJECT,
+ getResources().getString(R.string.email_subject));
+ emailIntent.putExtra(Intent.EXTRA_TEXT,
+ getResources().getString(R.string.email_body));
+ emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
+ startActivity(emailIntent);
+ } else {
+ Toast postToast = Toast.makeText(
+ getBaseContext(), R.string.no_preview, Toast.LENGTH_SHORT);
+ postToast.show();
+ }
+ }
+ }
+}
diff --git a/build/sdk.atree b/build/sdk.atree
index 842a30e02..17a39edd8 100644
--- a/build/sdk.atree
+++ b/build/sdk.atree
@@ -171,6 +171,7 @@ development/samples/SpinnerTest samples/${PLATFORM_NAME}/SpinnerTes
development/samples/TicTacToeLib samples/${PLATFORM_NAME}/TicTacToeLib
development/samples/TicTacToeMain samples/${PLATFORM_NAME}/TicTacToeMain
development/samples/VoiceRecognitionService samples/${PLATFORM_NAME}/VoiceRecognitionService
+development/apps/WidgetPreview samples/${PLATFORM_NAME}/WidgetPreview
development/samples/Wiktionary samples/${PLATFORM_NAME}/Wiktionary
development/samples/WiktionarySimple samples/${PLATFORM_NAME}/WiktionarySimple
development/samples/XmlAdapters samples/${PLATFORM_NAME}/XmlAdapters