blob: fbcd6ba0ff47076313f00e4da365b05369cbe046 [file] [log] [blame]
/*
* Copyright (C) 2019 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.systemui.screenshot;
import static android.content.Context.NOTIFICATION_SERVICE;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.util.NotificationChannels;
import javax.inject.Inject;
/**
* Convenience class to handle showing and hiding notifications while taking a screenshot.
*/
public class ScreenshotNotificationsController {
private static final String TAG = "ScreenshotNotificationManager";
private final Context mContext;
private final Resources mResources;
private final NotificationManager mNotificationManager;
private final Notification.BigPictureStyle mNotificationStyle;
private int mIconSize;
private int mPreviewWidth, mPreviewHeight;
private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
@Inject
ScreenshotNotificationsController(Context context, WindowManager windowManager) {
mContext = context;
mResources = context.getResources();
mNotificationManager =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
mIconSize = mResources.getDimensionPixelSize(
android.R.dimen.notification_large_icon_height);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
// determine the optimal preview size
int panelWidth = 0;
try {
panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
} catch (Resources.NotFoundException e) {
}
if (panelWidth <= 0) {
// includes notification_panel_width==match_parent (-1)
panelWidth = displayMetrics.widthPixels;
}
mPreviewWidth = panelWidth;
mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height);
// Setup the notification
mNotificationStyle = new Notification.BigPictureStyle();
}
/**
* Resets the notification builders.
*/
public void reset() {
// The public notification will show similar info but with the actual screenshot omitted
mPublicNotificationBuilder =
new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
mNotificationBuilder =
new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
}
/**
* Sets the current screenshot bitmap.
*
* @param image the bitmap of the current screenshot (used for preview)
*/
public void setImage(Bitmap image) {
// Create the large notification icon
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
Paint paint = new Paint();
ColorMatrix desat = new ColorMatrix();
desat.setSaturation(0.25f);
paint.setColorFilter(new ColorMatrixColorFilter(desat));
Matrix matrix = new Matrix();
int overlayColor = 0x40FFFFFF;
matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f);
Bitmap picture = generateAdjustedHwBitmap(
image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor);
mNotificationStyle.bigPicture(picture.createAshmemBitmap());
// Note, we can't use the preview for the small icon, since it is non-square
float scale = (float) mIconSize / Math.min(imageWidth, imageHeight);
matrix.setScale(scale, scale);
matrix.postTranslate(
(mIconSize - (scale * imageWidth)) / 2,
(mIconSize - (scale * imageHeight)) / 2);
Bitmap icon =
generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor);
/**
* NOTE: The following code prepares the notification builder for updating the
* notification after the screenshot has been written to disk.
*/
// On the tablet, the large icon makes the notification appear as if it is clickable
// (and on small devices, the large icon is not shown) so defer showing the large icon
// until we compose the final post-save notification below.
mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
// But we still don't set it for the expanded view, allowing the smallIcon to show here.
mNotificationStyle.bigLargeIcon((Bitmap) null);
}
/**
* Shows a notification to inform the user that a screenshot is currently being saved.
*/
public void showSavingScreenshotNotification() {
final long now = System.currentTimeMillis();
mPublicNotificationBuilder
.setContentTitle(mResources.getString(R.string.screenshot_saving_title))
.setSmallIcon(R.drawable.stat_notify_image)
.setCategory(Notification.CATEGORY_PROGRESS)
.setWhen(now)
.setShowWhen(true)
.setColor(mResources.getColor(
com.android.internal.R.color.system_notification_accent_color));
SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
mNotificationBuilder
.setContentTitle(mResources.getString(R.string.screenshot_saving_title))
.setSmallIcon(R.drawable.stat_notify_image)
.setWhen(now)
.setShowWhen(true)
.setColor(mResources.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setStyle(mNotificationStyle)
.setPublicVersion(mPublicNotificationBuilder.build());
mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
mNotificationBuilder.build());
}
/**
* Shows a notification with the saved screenshot and actions that can be taken with it.
*
* @param actionData SavedImageData struct with image URI and actions
*/
public void showScreenshotActionsNotification(
GlobalScreenshot.SavedImageData actionData) {
mNotificationBuilder.addAction(actionData.shareAction);
mNotificationBuilder.addAction(actionData.editAction);
mNotificationBuilder.addAction(actionData.deleteAction);
for (Notification.Action smartAction : actionData.smartActions) {
mNotificationBuilder.addAction(smartAction);
}
// Create the intent to show the screenshot in gallery
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
launchIntent.setDataAndType(actionData.uri, "image/png");
launchIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
final long now = System.currentTimeMillis();
// Update the text and the icon for the existing notification
mPublicNotificationBuilder
.setContentTitle(mResources.getString(R.string.screenshot_saved_title))
.setContentText(mResources.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
.setWhen(now)
.setAutoCancel(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
mNotificationBuilder
.setContentTitle(mResources.getString(R.string.screenshot_saved_title))
.setContentText(mResources.getString(R.string.screenshot_saved_text))
.setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
.setWhen(now)
.setAutoCancel(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setPublicVersion(mPublicNotificationBuilder.build())
.setFlag(Notification.FLAG_NO_CLEAR, false);
mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
mNotificationBuilder.build());
}
/**
* Sends a notification that the screenshot capture has failed.
*/
public void notifyScreenshotError(int msgResId) {
Resources res = mContext.getResources();
String errorMsg = res.getString(msgResId);
// Repurpose the existing notification to notify the user of the error
Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
.setTicker(res.getString(R.string.screenshot_failed_title))
.setContentTitle(res.getString(R.string.screenshot_failed_title))
.setContentText(errorMsg)
.setSmallIcon(R.drawable.stat_notify_image_error)
.setWhen(System.currentTimeMillis())
.setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
.setCategory(Notification.CATEGORY_ERROR)
.setAutoCancel(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
final DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
final Intent intent =
dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
if (intent != null) {
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
mContext, 0, intent, 0, null, UserHandle.CURRENT);
b.setContentIntent(pendingIntent);
}
SystemUI.overrideNotificationAppName(mContext, b, true);
Notification n = new Notification.BigTextStyle(b)
.bigText(errorMsg)
.build();
mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
}
/**
* Cancels the current screenshot notification.
*/
public void cancelNotification() {
mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
}
/**
* Generates a new hardware bitmap with specified values, copying the content from the
* passed in bitmap.
*/
private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
Paint paint, int color) {
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(width, height);
canvas.drawColor(color);
canvas.drawBitmap(bitmap, matrix, paint);
picture.endRecording();
return Bitmap.createBitmap(picture);
}
static void cancelScreenshotNotification(Context context) {
final NotificationManager nm =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
}
}