GlobalScreenshot.java revision fc8fa638617efb5695a1f89ea75375faebbe2a40
1/*
2 * Copyright (C) 2011 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
17package com.android.systemui.screenshot;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ObjectAnimator;
23import android.animation.TimeInterpolator;
24import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
26import android.app.Activity;
27import android.content.ContentResolver;
28import android.content.ContentValues;
29import android.content.Context;
30import android.graphics.Bitmap;
31import android.graphics.Canvas;
32import android.graphics.Matrix;
33import android.graphics.PixelFormat;
34import android.media.MediaScannerConnection;
35import android.net.Uri;
36import android.os.AsyncTask;
37import android.os.Binder;
38import android.os.Environment;
39import android.os.ServiceManager;
40import android.provider.MediaStore;
41import android.util.DisplayMetrics;
42import android.util.Log;
43import android.view.Display;
44import android.view.IWindowManager;
45import android.view.LayoutInflater;
46import android.view.MotionEvent;
47import android.view.Surface;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.WindowManager;
51import android.widget.FrameLayout;
52import android.widget.ImageView;
53import android.widget.TextView;
54import android.widget.Toast;
55
56import com.android.systemui.R;
57
58import java.io.File;
59import java.io.FileOutputStream;
60import java.io.IOException;
61import java.io.OutputStream;
62import java.lang.Thread;
63import java.text.SimpleDateFormat;
64import java.util.Date;
65
66/**
67 * POD used in the AsyncTask which saves an image in the background.
68 */
69class SaveImageInBackgroundData {
70    Context context;
71    Bitmap image;
72    Runnable finisher;
73    int result;
74}
75
76/**
77 * An AsyncTask that saves an image to the media store in the background.
78 */
79class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
80        SaveImageInBackgroundData> {
81    private static final String TAG = "SaveImageInBackgroundTask";
82    private static final String SCREENSHOTS_DIR_NAME = "Screenshots";
83    private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
84    private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s";
85
86    @Override
87    protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
88        if (params.length != 1) return null;
89
90        Context context = params[0].context;
91        Bitmap image = params[0].image;
92
93        try {
94            long currentTime = System.currentTimeMillis();
95            String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(currentTime));
96            String imageDir = Environment.getExternalStoragePublicDirectory(
97                    Environment.DIRECTORY_PICTURES).getAbsolutePath();
98            String imageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, date);
99            String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir,
100                    SCREENSHOTS_DIR_NAME, imageFileName);
101
102            // Save the screenshot to the MediaStore
103            ContentValues values = new ContentValues();
104            ContentResolver resolver = context.getContentResolver();
105            values.put(MediaStore.Images.ImageColumns.DATA, imageFilePath);
106            values.put(MediaStore.Images.ImageColumns.TITLE, imageFileName);
107            values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, imageFileName);
108            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, currentTime);
109            values.put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime);
110            values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, currentTime);
111            values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
112            Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
113
114            OutputStream out = resolver.openOutputStream(uri);
115            image.compress(Bitmap.CompressFormat.PNG, 100, out);
116            out.flush();
117            out.close();
118
119            // update file size in the database
120            values.clear();
121            values.put(MediaStore.Images.ImageColumns.SIZE, new File(imageFilePath).length());
122            resolver.update(uri, values, null, null);
123
124            params[0].result = 0;
125        } catch (Exception e) {
126            // IOException/UnsupportedOperationException may be thrown if external storage is not
127            // mounted
128            params[0].result = 1;
129        }
130
131        return params[0];
132    };
133
134    @Override
135    protected void onPostExecute(SaveImageInBackgroundData params) {
136        if (params.result > 0) {
137            // Show a message that we've failed to save the image to disk
138            Toast.makeText(params.context, R.string.screenshot_failed_toast,
139                    Toast.LENGTH_SHORT).show();
140        } else {
141            // Show a message that we've saved the screenshot to disk
142            Toast.makeText(params.context, R.string.screenshot_saving_toast,
143                    Toast.LENGTH_SHORT).show();
144        }
145        params.finisher.run();
146    };
147}
148
149/**
150 * TODO:
151 *   - Performance when over gl surfaces? Ie. Gallery
152 *   - what do we say in the Toast? Which icon do we get if the user uses another
153 *     type of gallery?
154 */
155class GlobalScreenshot {
156    private static final String TAG = "GlobalScreenshot";
157    private static final int SCREENSHOT_FADE_IN_DURATION = 900;
158    private static final int SCREENSHOT_FADE_OUT_DELAY = 1000;
159    private static final int SCREENSHOT_FADE_OUT_DURATION = 450;
160    private static final int TOAST_FADE_IN_DURATION = 500;
161    private static final int TOAST_FADE_OUT_DELAY = 1000;
162    private static final int TOAST_FADE_OUT_DURATION = 500;
163    private static final float BACKGROUND_ALPHA = 0.65f;
164    private static final float SCREENSHOT_SCALE = 0.85f;
165    private static final float SCREENSHOT_MIN_SCALE = 0.7f;
166    private static final float SCREENSHOT_ROTATION = -6.75f; // -12.5f;
167
168    private Context mContext;
169    private LayoutInflater mLayoutInflater;
170    private IWindowManager mIWindowManager;
171    private WindowManager mWindowManager;
172    private WindowManager.LayoutParams mWindowLayoutParams;
173    private Display mDisplay;
174    private DisplayMetrics mDisplayMetrics;
175    private Matrix mDisplayMatrix;
176
177    private Bitmap mScreenBitmap;
178    private View mScreenshotLayout;
179    private ImageView mBackgroundView;
180    private FrameLayout mScreenshotContainerView;
181    private ImageView mScreenshotView;
182
183    private AnimatorSet mScreenshotAnimation;
184
185    // General use cubic interpolator
186    final TimeInterpolator mCubicInterpolator = new TimeInterpolator() {
187        public float getInterpolation(float t) {
188            return t*t*t;
189        }
190    };
191    // The interpolator used to control the background alpha at the start of the animation
192    final TimeInterpolator mBackgroundViewAlphaInterpolator = new TimeInterpolator() {
193        public float getInterpolation(float t) {
194            float tStep = 0.35f;
195            if (t < tStep) {
196                return t * (1f / tStep);
197            } else {
198                return 1f;
199            }
200        }
201    };
202
203    /**
204     * @param context everything needs a context :(
205     */
206    public GlobalScreenshot(Context context) {
207        mContext = context;
208        mLayoutInflater = (LayoutInflater)
209                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
210
211        // Inflate the screenshot layout
212        mDisplayMetrics = new DisplayMetrics();
213        mDisplayMatrix = new Matrix();
214        mScreenshotLayout = mLayoutInflater.inflate(R.layout.global_screenshot, null);
215        mBackgroundView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot_background);
216        mScreenshotContainerView = (FrameLayout) mScreenshotLayout.findViewById(R.id.global_screenshot_container);
217        mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.global_screenshot);
218        mScreenshotLayout.setFocusable(true);
219        mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
220            @Override
221            public boolean onTouch(View v, MotionEvent event) {
222                // Intercept and ignore all touch events
223                return true;
224            }
225        });
226
227        // Setup the window that we are going to use
228        mIWindowManager = IWindowManager.Stub.asInterface(
229                ServiceManager.getService(Context.WINDOW_SERVICE));
230        mWindowLayoutParams = new WindowManager.LayoutParams(
231                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
232                WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY,
233                WindowManager.LayoutParams.FLAG_FULLSCREEN
234                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
235                    | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM
236                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
237                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
238                PixelFormat.TRANSLUCENT);
239        mWindowLayoutParams.setTitle("ScreenshotAnimation");
240        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
241        mDisplay = mWindowManager.getDefaultDisplay();
242    }
243
244    /**
245     * Creates a new worker thread and saves the screenshot to the media store.
246     */
247    private void saveScreenshotInWorkerThread(Runnable finisher) {
248        SaveImageInBackgroundData data = new SaveImageInBackgroundData();
249        data.context = mContext;
250        data.image = mScreenBitmap;
251        data.finisher = finisher;
252        new SaveImageInBackgroundTask().execute(data);
253    }
254
255    /**
256     * @return the current display rotation in degrees
257     */
258    private float getDegreesForRotation(int value) {
259        switch (value) {
260        case Surface.ROTATION_90:
261            return 90f;
262        case Surface.ROTATION_180:
263            return 180f;
264        case Surface.ROTATION_270:
265            return 270f;
266        }
267        return 0f;
268    }
269
270    /**
271     * Takes a screenshot of the current display and shows an animation.
272     */
273    void takeScreenshot(Runnable finisher) {
274        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
275        // only in the natural orientation of the device :!)
276        mDisplay.getRealMetrics(mDisplayMetrics);
277        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
278        float degrees = getDegreesForRotation(mDisplay.getRotation());
279        boolean requiresRotation = (degrees > 0);
280        if (requiresRotation) {
281            // Get the dimensions of the device in its native orientation
282            mDisplayMatrix.reset();
283            mDisplayMatrix.preRotate(-degrees);
284            mDisplayMatrix.mapPoints(dims);
285            dims[0] = Math.abs(dims[0]);
286            dims[1] = Math.abs(dims[1]);
287        }
288        mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
289        if (requiresRotation) {
290            // Rotate the screenshot to the current orientation
291            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
292                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
293            Canvas c = new Canvas(ss);
294            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
295            c.rotate(360f - degrees);
296            c.translate(-dims[0] / 2, -dims[1] / 2);
297            c.drawBitmap(mScreenBitmap, 0, 0, null);
298            c.setBitmap(null);
299            mScreenBitmap = ss;
300        }
301
302        // If we couldn't take the screenshot, notify the user
303        if (mScreenBitmap == null) {
304            Toast.makeText(mContext, R.string.screenshot_failed_toast,
305                    Toast.LENGTH_SHORT).show();
306            finisher.run();
307            return;
308        }
309
310        // Start the post-screenshot animation
311        startAnimation(finisher);
312    }
313
314
315    /**
316     * Starts the animation after taking the screenshot
317     */
318    private void startAnimation(final Runnable finisher) {
319        // Add the view for the animation
320        mScreenshotView.setImageBitmap(mScreenBitmap);
321        mScreenshotLayout.requestFocus();
322
323        // Setup the animation with the screenshot just taken
324        if (mScreenshotAnimation != null) {
325            mScreenshotAnimation.end();
326        }
327
328        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
329        ValueAnimator screenshotFadeInAnim = createScreenshotFadeInAnimation();
330        ValueAnimator screenshotFadeOutAnim = createScreenshotFadeOutAnimation();
331        mScreenshotAnimation = new AnimatorSet();
332        mScreenshotAnimation.play(screenshotFadeInAnim).before(screenshotFadeOutAnim);
333        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
334            @Override
335            public void onAnimationEnd(Animator animation) {
336                // Save the screenshot once we have a bit of time now
337                saveScreenshotInWorkerThread(finisher);
338                mWindowManager.removeView(mScreenshotLayout);
339            }
340        });
341        mScreenshotAnimation.start();
342    }
343    private ValueAnimator createScreenshotFadeInAnimation() {
344        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
345        anim.setInterpolator(mCubicInterpolator);
346        anim.setDuration(SCREENSHOT_FADE_IN_DURATION);
347        anim.addListener(new AnimatorListenerAdapter() {
348            @Override
349            public void onAnimationStart(Animator animation) {
350                mBackgroundView.setVisibility(View.VISIBLE);
351                mScreenshotContainerView.setVisibility(View.VISIBLE);
352            }
353        });
354        anim.addUpdateListener(new AnimatorUpdateListener() {
355            @Override
356            public void onAnimationUpdate(ValueAnimator animation) {
357                float t = ((Float) animation.getAnimatedValue()).floatValue();
358                mBackgroundView.setAlpha(mBackgroundViewAlphaInterpolator.getInterpolation(t) *
359                        BACKGROUND_ALPHA);
360                float scaleT = SCREENSHOT_SCALE + (1f - t) * SCREENSHOT_SCALE;
361                mScreenshotContainerView.setAlpha(t*t*t*t);
362                mScreenshotContainerView.setScaleX(scaleT);
363                mScreenshotContainerView.setScaleY(scaleT);
364                mScreenshotContainerView.setRotation(t * SCREENSHOT_ROTATION);
365            }
366        });
367        return anim;
368    }
369    private ValueAnimator createScreenshotFadeOutAnimation() {
370        ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
371        anim.setInterpolator(mCubicInterpolator);
372        anim.setStartDelay(SCREENSHOT_FADE_OUT_DELAY);
373        anim.setDuration(SCREENSHOT_FADE_OUT_DURATION);
374        anim.addListener(new AnimatorListenerAdapter() {
375            @Override
376            public void onAnimationEnd(Animator animation) {
377                mBackgroundView.setVisibility(View.GONE);
378                mScreenshotContainerView.setVisibility(View.GONE);
379            }
380        });
381        anim.addUpdateListener(new AnimatorUpdateListener() {
382            @Override
383            public void onAnimationUpdate(ValueAnimator animation) {
384                float t = ((Float) animation.getAnimatedValue()).floatValue();
385                float scaleT = SCREENSHOT_MIN_SCALE +
386                        t*(SCREENSHOT_SCALE - SCREENSHOT_MIN_SCALE);
387                mScreenshotContainerView.setAlpha(t);
388                mScreenshotContainerView.setScaleX(scaleT);
389                mScreenshotContainerView.setScaleY(scaleT);
390                mBackgroundView.setAlpha(t * t * BACKGROUND_ALPHA);
391            }
392        });
393        return anim;
394    }
395}
396