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