1 /*
2  * Copyright (C) 2010 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.widgetpreview;
18 
19 import android.app.Activity;
20 import android.appwidget.AppWidgetHost;
21 import android.appwidget.AppWidgetHostView;
22 import android.appwidget.AppWidgetManager;
23 import android.appwidget.AppWidgetProviderInfo;
24 import android.content.ActivityNotFoundException;
25 import android.content.Intent;
26 import android.content.res.Configuration;
27 import android.content.res.Resources;
28 import android.graphics.Bitmap;
29 import android.graphics.Bitmap.CompressFormat;
30 import android.graphics.Bitmap.Config;
31 import android.graphics.Canvas;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Environment;
35 import android.util.Log;
36 import android.view.View;
37 import android.view.View.OnClickListener;
38 import android.view.ViewGroup;
39 import android.widget.Button;
40 import android.widget.FrameLayout;
41 import android.widget.Toast;
42 
43 import java.io.File;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 
47 public class WidgetPreviewActivity extends Activity implements OnClickListener {
48 
49     private static final String LOG_TAG = "WidgetPreviewActivity";
50     private static final boolean DEBUG = true;
51     private static final int APPWIDGET_HOST_ID = 2048;
52     private static final int REQUEST_WIDGET = 0;
53     private static final int REQUEST_CONFIGURE = 1;
54 
55     private AppWidgetHost mAppWidgetHost = null;
56     private FrameLayout mAppWidgetFrame = null;
57     private AppWidgetHostView mAppWidgetView = null;
58     private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
59     private String mAppWidgetName;
60     private int mPreviewWidth;
61     private int mPreviewHeight;
62 
63     private Button mSnapshotButton = null;
64     private Button mEmailButton = null;
65 
66     @Override
onCreate(Bundle savedInstanceState)67     public void onCreate(Bundle savedInstanceState) {
68         super.onCreate(savedInstanceState);
69         setContentView(R.layout.activity_main);
70         mAppWidgetFrame = (FrameLayout)findViewById(R.id.main_frame);
71         mSnapshotButton = (Button)findViewById(R.id.snapshot_button);
72         mSnapshotButton.setOnClickListener(this);
73         mEmailButton = (Button)findViewById(R.id.email_button);
74         mEmailButton.setOnClickListener(this);
75 
76         mAppWidgetHost = new AppWidgetHost(getApplicationContext(), APPWIDGET_HOST_ID);
77 
78         final Object retainedObj = getLastNonConfigurationInstance();
79         if (retainedObj instanceof AppWidgetProviderInfo) {
80             AppWidgetProviderInfo info = (AppWidgetProviderInfo) retainedObj;
81             int id = mAppWidgetHost.allocateAppWidgetId();
82             AppWidgetManager.getInstance(getBaseContext()).bindAppWidgetIdIfAllowed(id, info.provider);
83             setAppWidget(id);
84         } else {
85             startChooseActivity();
86         }
87     }
88 
89     @Override
onStart()90     public void onStart() {
91         super.onStart();
92         mAppWidgetHost.startListening();
93     }
94 
95     @Override
onRetainNonConfigurationInstance()96     public Object onRetainNonConfigurationInstance() {
97         AppWidgetProviderInfo info = AppWidgetManager.getInstance(
98                 getBaseContext()).getAppWidgetInfo(mAppWidgetId);
99         return info;
100     }
101 
startChooseActivity()102     private void startChooseActivity() {
103         int id = mAppWidgetHost.allocateAppWidgetId();
104         Intent selectIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
105         selectIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id);
106         startActivityForResult(selectIntent, REQUEST_WIDGET);
107     }
108 
109     @Override
onActivityResult(int requestCode, int resultCode, Intent data)110     public void onActivityResult(int requestCode, int resultCode, Intent data) {
111         if (requestCode == REQUEST_WIDGET) {
112             if (data != null) {
113                 int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
114                 if (data.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
115                     appWidgetId = data.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
116                 }
117 
118                 if (resultCode == RESULT_OK) {
119                     setAppWidget(appWidgetId);
120                 } else {
121                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
122                     finish();
123                 }
124             } else {
125                 finish();
126             }
127         } else if (requestCode == REQUEST_CONFIGURE) {
128             if (data != null) {
129                 int appWidgetId = data.getExtras().getInt(
130                         AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
131                 if (resultCode == RESULT_OK) {
132                     finishSetAppWidget(appWidgetId);
133                 } else {
134                     mAppWidgetHost.deleteAppWidgetId(appWidgetId);
135                 }
136             }
137         }
138     }
139 
setAppWidget(int appWidgetId)140     private void setAppWidget(int appWidgetId) {
141         if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
142             mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
143         }
144 
145         /* Check for configuration */
146         AppWidgetProviderInfo providerInfo =
147             AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId);
148 
149         if (providerInfo.configure != null) {
150             Intent configureIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
151             configureIntent.setComponent(providerInfo.configure);
152             configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
153 
154             if (configureIntent != null) {
155                 try {
156                     startActivityForResult(configureIntent, REQUEST_CONFIGURE);
157                 } catch (ActivityNotFoundException e) {
158                     Log.d(LOG_TAG, "Configuration activity not found: " + e);
159                     Toast errorToast = Toast.makeText(
160                             getBaseContext(), R.string.configure_error, Toast.LENGTH_SHORT);
161                     errorToast.show();
162                 }
163             }
164         } else {
165             finishSetAppWidget(appWidgetId);
166         }
167     }
168 
finishSetAppWidget(int appWidgetId)169     private void finishSetAppWidget(int appWidgetId) {
170         AppWidgetProviderInfo providerInfo =
171             AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId);
172         if (providerInfo != null) {
173             mAppWidgetView =
174                     mAppWidgetHost.createView(getBaseContext(), appWidgetId, providerInfo);
175 
176             int [] dimensions =
177                     getLauncherCellDimensions(providerInfo.minWidth, providerInfo.minHeight);
178 
179             mPreviewWidth = dimensions[0];
180             mPreviewHeight = dimensions[1];
181 
182             mAppWidgetName =
183                 AppWidgetManager.getInstance(getBaseContext()).getAppWidgetInfo(appWidgetId).label;
184             mAppWidgetName = mAppWidgetName.replaceAll("[^a-zA-Z0-9]", "_");
185 
186             ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(
187                     ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
188             mAppWidgetView.setLayoutParams(p);
189             mAppWidgetFrame.removeAllViews();
190             mAppWidgetHost.deleteAppWidgetId(mAppWidgetId);
191             mAppWidgetFrame.addView(mAppWidgetView, mPreviewWidth, mPreviewHeight);
192             mAppWidgetId = appWidgetId;
193         }
194     }
195 
196     // Taken from CellLayout.java
getLauncherCellDimensions(int width, int height)197     public int[] getLauncherCellDimensions(int width, int height) {
198         // Always assume we're working with the smallest span to make sure we
199         // reserve enough space in both orientations.
200         Resources resources = getResources();
201         int cellWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
202         int cellHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
203         int widthGap = resources.getDimensionPixelSize(R.dimen.workspace_width_gap);
204         int heightGap = resources.getDimensionPixelSize(R.dimen.workspace_height_gap);
205         int previewCellSize = resources.getDimensionPixelSize(R.dimen.preview_cell_size);
206 
207         // This logic imitates Launcher's CellLayout.rectToCell.
208         // Always round up to next largest cell
209         int smallerSize = Math.min(cellWidth, cellHeight);
210         int spanX = (width + smallerSize) / smallerSize;
211         int spanY = (height + smallerSize) / smallerSize;
212 
213         // We use a fixed preview cell size so that you get the same preview image for
214         // the same cell-sized widgets across all devices
215         width = spanX * previewCellSize + ((spanX - 1) * widthGap);
216         height = spanY * previewCellSize + ((spanY - 1) * heightGap);
217         return new int[] { width, height };
218     }
219 
buildFile(String name)220     private File buildFile(String name) {
221         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
222             return null;
223         }
224 
225         File path = Environment.getExternalStoragePublicDirectory(
226                 Environment.DIRECTORY_DOWNLOADS);
227         int orientationCode = getResources().getConfiguration().orientation;
228         String orientation;
229         if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
230             orientation = "landscape";
231         } else if (orientationCode == Configuration.ORIENTATION_PORTRAIT) {
232             orientation = "portrait";
233         } else if (orientationCode == Configuration.ORIENTATION_SQUARE) {
234             orientation = "square";
235         } else {
236             orientation = "undefined";
237         }
238         return new File(path, name + "_ori_" + orientation + ".png");
239     }
240 
getPreviewBitmap()241     public Bitmap getPreviewBitmap() {
242         mAppWidgetView.invalidate();
243         Bitmap bmp = Bitmap.createBitmap(
244                 mAppWidgetView.getWidth(), mAppWidgetView.getHeight(), Config.ARGB_8888);
245         Canvas c = new Canvas(bmp);
246         mAppWidgetView.draw(c);
247         return bmp;
248     }
249 
saveImage(Bitmap bmp, String name)250     private boolean saveImage(Bitmap bmp, String name) {
251         File pic = buildFile(mAppWidgetName);
252         if (pic == null) {
253             Log.d(LOG_TAG, "External storage not present");
254             return false;
255         }
256 
257         pic.getParentFile().mkdirs();
258         FileOutputStream fout = null;
259         try {
260             fout = new FileOutputStream(pic);
261             if (!bmp.compress(CompressFormat.PNG, 100, fout)) {
262                 Log.d(LOG_TAG, "Failed to compress image");
263                 return false;
264             }
265             return true;
266         } catch (IOException e) {
267             Log.d(LOG_TAG, "Error writing to disk: " + e);
268         } finally {
269             try {
270                 if (fout != null) {
271                     fout.close();
272                 }
273             } catch (IOException e) {
274                 Log.d(LOG_TAG, "Could not close file: " + e);
275             }
276         }
277         return false;
278     }
279 
280     @Override
onBackPressed()281     public void onBackPressed() {
282         if (!getFragmentManager().popBackStackImmediate()) {
283             startChooseActivity();
284         }
285     }
286 
287     @Override
onClick(View v)288     public void onClick(View v) {
289         if (v == mSnapshotButton) {
290             int textId = R.string.saving_preview;
291 
292             Toast preToast = Toast.makeText(getBaseContext(), textId, Toast.LENGTH_SHORT);
293             preToast.show();
294 
295             Bitmap bmp = getPreviewBitmap();
296             if (saveImage(bmp, mAppWidgetName)) {
297                 textId = R.string.preview_saved;
298             } else {
299                 textId = R.string.preview_save_error;
300             }
301 
302             Toast postToast = Toast.makeText(getBaseContext(), textId, Toast.LENGTH_SHORT);
303             postToast.show();
304         } else if (v == mEmailButton) {
305             File file = buildFile(mAppWidgetName);
306             if (file.exists()) {
307                 Intent emailIntent = new Intent(Intent.ACTION_SEND);
308                 emailIntent.setType("image/png");
309                 emailIntent.putExtra(Intent.EXTRA_SUBJECT,
310                         getResources().getString(R.string.email_subject));
311                 emailIntent.putExtra(Intent.EXTRA_TEXT,
312                         getResources().getString(R.string.email_body));
313                 emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
314                 startActivity(emailIntent);
315             } else {
316                 Toast postToast = Toast.makeText(
317                         getBaseContext(), R.string.no_preview, Toast.LENGTH_SHORT);
318                 postToast.show();
319             }
320         }
321     }
322 }
323