Merge "Showing notifications after taking a screenshot (Bug: 5333706)" into ics-mr0

This commit is contained in:
Winson Chung
2011-10-17 10:20:46 -07:00
committed by Android (Google) Code Review
7 changed files with 166 additions and 130 deletions

View File

@@ -25,6 +25,7 @@
android:id="@+id/global_screenshot_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/global_screenshot_background"
android:visibility="gone">
<ImageView android:id="@+id/global_screenshot"
@@ -32,9 +33,4 @@
android:layout_height="wrap_content"
android:adjustViewBounds="true" />
</FrameLayout>
<ImageView android:id="@+id/global_screenshot_flash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFFFF"
android:visibility="gone" />
</FrameLayout>

View File

@@ -168,10 +168,20 @@
<!-- Compatibility mode help screen: body text. [CHAR LIMIT=150] -->
<string name="compat_mode_help_body">When an app was designed for a smaller screen, a zoom control will appear by the clock.</string>
<!-- toast message displayed when a screenshot is saved to the Gallery. -->
<string name="screenshot_saving_toast">Screenshot saved to Gallery</string>
<!-- toast message displayed when we fail to take a screenshot. -->
<string name="screenshot_failed_toast">Could not save screenshot. External storage may be in use.</string>
<!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] -->
<string name="screenshot_saving_ticker">Saving...</string>
<!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saving_title">Saving screenshot...</string>
<!-- Notification text displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=100] -->
<string name="screenshot_saving_text">Please wait for screenshot to be saved</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saved_title">Screenshot captured</string>
<!-- Notification text displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=100] -->
<string name="screenshot_saved_text">Touch to view your screenshot</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
<string name="screenshot_failed_title">Screenshot failed</string>
<!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
<string name="screenshot_failed_text">Failed to save screenshot. External storage may be in use.</string>
<!-- Title for the USB function chooser in UsbPreferenceActivity. [CHAR LIMIT=30] -->
<string name="usb_preference_title">USB file transfer options</string>

View File

@@ -19,27 +19,27 @@ package com.android.systemui.screenshot;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Environment;
import android.os.ServiceManager;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -50,16 +50,11 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.systemui.R;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.Thread;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -83,6 +78,46 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
private static final String SCREENSHOT_FILE_NAME_TEMPLATE = "Screenshot_%s.png";
private static final String SCREENSHOT_FILE_PATH_TEMPLATE = "%s/%s/%s";
private int mNotificationId;
private NotificationManager mNotificationManager;
private Notification.Builder mNotificationBuilder;
private Intent mLaunchIntent;
private String mImageDir;
private String mImageFileName;
private String mImageFilePath;
private String mImageDate;
private long mImageTime;
SaveImageInBackgroundTask(Context context, NotificationManager nManager, int nId) {
Resources r = context.getResources();
// Prepare all the output metadata
mImageTime = System.currentTimeMillis();
mImageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
mImageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath();
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, mImageDate);
mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, mImageDir,
SCREENSHOTS_DIR_NAME, mImageFileName);
// Show the intermediate notification
mLaunchIntent = new Intent(Intent.ACTION_VIEW);
mLaunchIntent.setDataAndType(Uri.fromFile(new File(mImageFilePath)), "image/png");
mLaunchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mNotificationId = nId;
mNotificationBuilder = new Notification.Builder(context)
.setTicker(r.getString(R.string.screenshot_saving_ticker))
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(android.R.drawable.ic_menu_gallery)
.setWhen(System.currentTimeMillis());
Notification n = mNotificationBuilder.getNotification();
n.flags |= Notification.FLAG_NO_CLEAR;
mNotificationManager = nManager;
mNotificationManager.notify(nId, n);
}
@Override
protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
if (params.length != 1) return null;
@@ -91,23 +126,15 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
Bitmap image = params[0].image;
try {
long currentTime = System.currentTimeMillis();
String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(currentTime));
String imageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath();
String imageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, date);
String imageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir,
SCREENSHOTS_DIR_NAME, imageFileName);
// Save the screenshot to the MediaStore
ContentValues values = new ContentValues();
ContentResolver resolver = context.getContentResolver();
values.put(MediaStore.Images.ImageColumns.DATA, imageFilePath);
values.put(MediaStore.Images.ImageColumns.TITLE, imageFileName);
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, imageFileName);
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, currentTime);
values.put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime);
values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, currentTime);
values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
values.put(MediaStore.Images.ImageColumns.DATE_ADDED, mImageTime);
values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, mImageTime);
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
@@ -118,7 +145,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
// update file size in the database
values.clear();
values.put(MediaStore.Images.ImageColumns.SIZE, new File(imageFilePath).length());
values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
resolver.update(uri, values, null, null);
params[0].result = 0;
@@ -135,12 +162,22 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
protected void onPostExecute(SaveImageInBackgroundData params) {
if (params.result > 0) {
// Show a message that we've failed to save the image to disk
Toast.makeText(params.context, R.string.screenshot_failed_toast,
Toast.LENGTH_SHORT).show();
GlobalScreenshot.notifyScreenshotError(params.context, mNotificationManager);
} else {
// Show a message that we've saved the screenshot to disk
Toast.makeText(params.context, R.string.screenshot_saving_toast,
Toast.LENGTH_SHORT).show();
// Show the final notification to indicate screenshot saved
Resources r = params.context.getResources();
mNotificationBuilder
.setTicker(r.getString(R.string.screenshot_saved_title))
.setContentTitle(r.getString(R.string.screenshot_saved_title))
.setContentText(r.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(params.context, 0, mLaunchIntent, 0))
.setWhen(System.currentTimeMillis())
.setAutoCancel(true);
Notification n = mNotificationBuilder.getNotification();
n.flags &= ~Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(mNotificationId, n);
}
params.finisher.run();
};
@@ -154,22 +191,21 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi
*/
class GlobalScreenshot {
private static final String TAG = "GlobalScreenshot";
private static final int SCREENSHOT_FADE_IN_DURATION = 900;
private static final int SCREENSHOT_NOTIFICATION_ID = 789;
private static final int SCREENSHOT_FADE_IN_DURATION = 500;
private static final int SCREENSHOT_FADE_OUT_DELAY = 1000;
private static final int SCREENSHOT_FADE_OUT_DURATION = 450;
private static final int TOAST_FADE_IN_DURATION = 500;
private static final int TOAST_FADE_OUT_DELAY = 1000;
private static final int TOAST_FADE_OUT_DURATION = 500;
private static final int SCREENSHOT_FADE_OUT_DURATION = 300;
private static final float BACKGROUND_ALPHA = 0.65f;
private static final float SCREENSHOT_SCALE = 0.85f;
private static final float SCREENSHOT_MIN_SCALE = 0.7f;
private static final float SCREENSHOT_ROTATION = -6.75f; // -12.5f;
private static final float SCREENSHOT_SCALE_FUDGE = 0.075f; // To account for the border padding
private static final float SCREENSHOT_SCALE = 0.8f;
private static final float SCREENSHOT_MIN_SCALE = 0.775f;
private Context mContext;
private LayoutInflater mLayoutInflater;
private IWindowManager mIWindowManager;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
private NotificationManager mNotificationManager;
private Display mDisplay;
private DisplayMetrics mDisplayMetrics;
private Matrix mDisplayMatrix;
@@ -182,10 +218,15 @@ class GlobalScreenshot {
private AnimatorSet mScreenshotAnimation;
// General use cubic interpolator
final TimeInterpolator mCubicInterpolator = new TimeInterpolator() {
// Fade interpolators
final TimeInterpolator mFadeInInterpolator = new TimeInterpolator() {
public float getInterpolation(float t) {
return t*t*t;
return (float) Math.pow(t, 1.5f);
}
};
final TimeInterpolator mFadeOutInterpolator = new TimeInterpolator() {
public float getInterpolation(float t) {
return (float) t;
}
};
// The interpolator used to control the background alpha at the start of the animation
@@ -237,6 +278,8 @@ class GlobalScreenshot {
PixelFormat.TRANSLUCENT);
mWindowLayoutParams.setTitle("ScreenshotAnimation");
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
}
@@ -248,7 +291,8 @@ class GlobalScreenshot {
data.context = mContext;
data.image = mScreenBitmap;
data.finisher = finisher;
new SaveImageInBackgroundTask().execute(data);
new SaveImageInBackgroundTask(mContext, mNotificationManager, SCREENSHOT_NOTIFICATION_ID)
.execute(data);
}
/**
@@ -300,8 +344,7 @@ class GlobalScreenshot {
// If we couldn't take the screenshot, notify the user
if (mScreenBitmap == null) {
Toast.makeText(mContext, R.string.screenshot_failed_toast,
Toast.LENGTH_SHORT).show();
notifyScreenshotError(mContext, mNotificationManager);
finisher.run();
return;
}
@@ -341,12 +384,13 @@ class GlobalScreenshot {
}
private ValueAnimator createScreenshotFadeInAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setInterpolator(mCubicInterpolator);
anim.setInterpolator(mFadeInInterpolator);
anim.setDuration(SCREENSHOT_FADE_IN_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mBackgroundView.setVisibility(View.VISIBLE);
mScreenshotContainerView.setTranslationY(0f);
mScreenshotContainerView.setVisibility(View.VISIBLE);
}
});
@@ -356,18 +400,19 @@ class GlobalScreenshot {
float t = ((Float) animation.getAnimatedValue()).floatValue();
mBackgroundView.setAlpha(mBackgroundViewAlphaInterpolator.getInterpolation(t) *
BACKGROUND_ALPHA);
float scaleT = SCREENSHOT_SCALE + (1f - t) * SCREENSHOT_SCALE;
float scaleT = SCREENSHOT_SCALE
+ (1f - t) * (1f - SCREENSHOT_SCALE)
+ SCREENSHOT_SCALE_FUDGE;
mScreenshotContainerView.setAlpha(t*t*t*t);
mScreenshotContainerView.setScaleX(scaleT);
mScreenshotContainerView.setScaleY(scaleT);
mScreenshotContainerView.setRotation(t * SCREENSHOT_ROTATION);
}
});
return anim;
}
private ValueAnimator createScreenshotFadeOutAnimation() {
ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
anim.setInterpolator(mCubicInterpolator);
anim.setInterpolator(mFadeOutInterpolator);
anim.setStartDelay(SCREENSHOT_FADE_OUT_DELAY);
anim.setDuration(SCREENSHOT_FADE_OUT_DURATION);
anim.addListener(new AnimatorListenerAdapter() {
@@ -381,8 +426,9 @@ class GlobalScreenshot {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float t = ((Float) animation.getAnimatedValue()).floatValue();
float scaleT = SCREENSHOT_MIN_SCALE +
t*(SCREENSHOT_SCALE - SCREENSHOT_MIN_SCALE);
float scaleT = SCREENSHOT_MIN_SCALE
+ t * (SCREENSHOT_SCALE - SCREENSHOT_MIN_SCALE)
+ SCREENSHOT_SCALE_FUDGE;
mScreenshotContainerView.setAlpha(t);
mScreenshotContainerView.setScaleX(scaleT);
mScreenshotContainerView.setScaleY(scaleT);
@@ -391,4 +437,19 @@ class GlobalScreenshot {
});
return anim;
}
static void notifyScreenshotError(Context context, NotificationManager nManager) {
Resources r = context.getResources();
// Clear all existing notification, compose the new notification and show it
Notification n = new Notification.Builder(context)
.setTicker(r.getString(R.string.screenshot_failed_title))
.setContentTitle(r.getString(R.string.screenshot_failed_title))
.setContentText(r.getString(R.string.screenshot_failed_text))
.setSmallIcon(android.R.drawable.ic_menu_report_image)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.getNotification();
nManager.notify(SCREENSHOT_NOTIFICATION_ID, n);
}
}

View File

@@ -17,26 +17,12 @@
package com.android.systemui.screenshot;
import android.app.Service;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
public class TakeScreenshotService extends Service {
private static final String TAG = "TakeScreenshotService";

View File

@@ -146,4 +146,26 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac
mDoNotDisturb = new DoNotDisturb(mContext);
}
protected View updateNotificationVetoButton(View row, StatusBarNotification n) {
View vetoButton = row.findViewById(R.id.veto);
if (n.isClearable()) {
final String _pkg = n.pkg;
final String _tag = n.tag;
final int _id = n.id;
vetoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
mBarService.onNotificationClear(_pkg, _tag, _id);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
}
});
vetoButton.setVisibility(View.VISIBLE);
} else {
vetoButton.setVisibility(View.GONE);
}
return vetoButton;
}
}

View File

@@ -618,6 +618,10 @@ public class PhoneStatusBar extends StatusBar {
boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
&& notification.priority == oldNotification.priority;
// priority now encompasses isOngoing()
boolean updateTicker = notification.notification.tickerText != null
&& !TextUtils.equals(notification.notification.tickerText,
oldEntry.notification.notification.tickerText);
boolean isFirstAnyway = rowParent.indexOfChild(oldEntry.row) == 0;
if (contentsUnchanged && (orderUnchanged || isFirstAnyway)) {
if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
@@ -665,10 +669,13 @@ public class PhoneStatusBar extends StatusBar {
addNotificationViews(key, notification);
}
// Update the veto button accordingly (and as a result, whether this row is
// swipe-dismissable)
updateNotificationVetoButton(oldEntry.row, notification);
// Restart the ticker if it's still running
if (notification.notification.tickerText != null
&& !TextUtils.equals(notification.notification.tickerText,
oldEntry.notification.notification.tickerText)) {
if (updateTicker) {
mTicker.halt();
tick(notification);
}
@@ -711,23 +718,7 @@ public class PhoneStatusBar extends StatusBar {
View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
// wire up the veto button
View vetoButton = row.findViewById(R.id.veto);
if (notification.isClearable()) {
final String _pkg = notification.pkg;
final String _tag = notification.tag;
final int _id = notification.id;
vetoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
mBarService.onNotificationClear(_pkg, _tag, _id);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
}
});
} else {
vetoButton.setVisibility(View.GONE);
}
View vetoButton = updateNotificationVetoButton(row, notification);
vetoButton.setContentDescription(mContext.getString(
R.string.accessibility_remove_notification));
@@ -897,23 +888,7 @@ public class PhoneStatusBar extends StatusBar {
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
View vetoButton = row.findViewById(R.id.veto);
if (entry.notification.isClearable()) {
final String _pkg = sbn.pkg;
final String _tag = sbn.tag;
final int _id = sbn.id;
vetoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
mBarService.onNotificationClear(_pkg, _tag, _id);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
}
});
} else {
vetoButton.setVisibility(View.GONE);
}
View vetoButton = updateNotificationVetoButton(row, sbn);
vetoButton.setContentDescription(mContext.getString(
R.string.accessibility_remove_notification));

View File

@@ -840,6 +840,9 @@ public class TabletStatusBar extends StatusBar implements
boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
&& notification.priority == oldNotification.priority;
// priority now encompasses isOngoing()
boolean updateTicker = notification.notification.tickerText != null
&& !TextUtils.equals(notification.notification.tickerText,
oldEntry.notification.notification.tickerText);
boolean isLastAnyway = rowParent.indexOfChild(oldEntry.row) == rowParent.getChildCount()-1;
if (contentsUnchanged && (orderUnchanged || isLastAnyway)) {
if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
@@ -896,9 +899,8 @@ public class TabletStatusBar extends StatusBar implements
}
// Restart the ticker if it's still running
if (notification.notification.tickerText != null
&& !TextUtils.equals(notification.notification.tickerText,
oldEntry.notification.notification.tickerText)) {
if (updateTicker) {
mTicker.halt();
tick(key, notification, false);
}
@@ -1736,23 +1738,7 @@ public class TabletStatusBar extends StatusBar implements
Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false);
workAroundBadLayerDrawableOpacity(row);
View vetoButton = row.findViewById(R.id.veto);
if (entry.notification.isClearable()) {
final String _pkg = sbn.pkg;
final String _tag = sbn.tag;
final int _id = sbn.id;
vetoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
mBarService.onNotificationClear(_pkg, _tag, _id);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
}
});
} else {
vetoButton.setVisibility(View.GONE);
}
View vetoButton = updateNotificationVetoButton(row, entry.notification);
vetoButton.setContentDescription(mContext.getString(
R.string.accessibility_remove_notification));