Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d64b75d5 authored by Nicolò Mazzucato's avatar Nicolò Mazzucato Committed by Android (Google) Code Review
Browse files

Merge "Refactor screenshot logging in case of external display" into main

parents 65c64840 fe1006fb
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -209,6 +209,8 @@
    <string name="screenshot_saved_title">Screenshot saved</string>
    <string name="screenshot_saved_title">Screenshot saved</string>
    <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
    <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
    <string name="screenshot_failed_title">Couldn\'t save screenshot</string>
    <string name="screenshot_failed_title">Couldn\'t save screenshot</string>
    <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] -->
    <string name="screenshot_failed_external_display_indication">External Display</string>
    <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
    <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->
    <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
    <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>
    <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
    <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
+16 −14
Original line number Original line Diff line number Diff line
@@ -20,11 +20,10 @@ import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.function.Consumer
import java.util.function.Consumer
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch


/** Processes a screenshot request sent from [ScreenshotHelper]. */
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
interface ScreenshotRequestProcessor {
@@ -36,14 +35,13 @@ interface ScreenshotRequestProcessor {
    suspend fun process(screenshot: ScreenshotData): ScreenshotData
    suspend fun process(screenshot: ScreenshotData): ScreenshotData
}
}


/**
/** Implementation of [ScreenshotRequestProcessor] */
 * Implementation of [ScreenshotRequestProcessor]
 */
@SysUISingleton
@SysUISingleton
class RequestProcessor @Inject constructor(
class RequestProcessor
@Inject
constructor(
    private val capture: ImageCapture,
    private val capture: ImageCapture,
    private val policy: ScreenshotPolicy,
    private val policy: ScreenshotPolicy,
        private val flags: FeatureFlags,
    /** For the Java Async version, to invoke the callback. */
    /** For the Java Async version, to invoke the callback. */
    @Application private val mainScope: CoroutineScope
    @Application private val mainScope: CoroutineScope
) : ScreenshotRequestProcessor {
) : ScreenshotRequestProcessor {
@@ -67,8 +65,9 @@ class RequestProcessor @Inject constructor(
            result.userHandle = info.user
            result.userHandle = info.user


            if (policy.isManagedProfile(info.user.identifier)) {
            if (policy.isManagedProfile(info.user.identifier)) {
                val image = capture.captureTask(info.taskId)
                val image =
                    ?: error("Task snapshot returned a null Bitmap!")
                    capture.captureTask(info.taskId)
                        ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")


                // Provide the task snapshot as the screenshot
                // Provide the task snapshot as the screenshot
                result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
                result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE
@@ -97,3 +96,6 @@ class RequestProcessor @Inject constructor(
}
}


private const val TAG = "RequestProcessor"
private const val TAG = "RequestProcessor"

/** Exception thrown by [RequestProcessor] if something goes wrong. */
class RequestProcessorException(message: String) : IllegalStateException(message)
+2 −2
Original line number Original line Diff line number Diff line
@@ -325,7 +325,7 @@ public class ScreenshotController {
            Context context,
            Context context,
            FeatureFlags flags,
            FeatureFlags flags,
            ScreenshotSmartActions screenshotSmartActions,
            ScreenshotSmartActions screenshotSmartActions,
            ScreenshotNotificationsController screenshotNotificationsController,
            ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
            ScrollCaptureClient scrollCaptureClient,
            ScrollCaptureClient scrollCaptureClient,
            UiEventLogger uiEventLogger,
            UiEventLogger uiEventLogger,
            ImageExporter imageExporter,
            ImageExporter imageExporter,
@@ -346,7 +346,7 @@ public class ScreenshotController {
            @Assisted boolean showUIOnExternalDisplay
            @Assisted boolean showUIOnExternalDisplay
    ) {
    ) {
        mScreenshotSmartActions = screenshotSmartActions;
        mScreenshotSmartActions = screenshotSmartActions;
        mNotificationsController = screenshotNotificationsController;
        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
        mScrollCaptureClient = scrollCaptureClient;
        mScrollCaptureClient = scrollCaptureClient;
        mUiEventLogger = uiEventLogger;
        mUiEventLogger = uiEventLogger;
        mImageExporter = imageExporter;
        mImageExporter = imageExporter;
+0 −96
Original line number Original line Diff line number Diff line
/*
 * 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.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.WindowManager;

import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.res.R;
import com.android.systemui.SystemUIApplication;
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;

    @Inject
    ScreenshotNotificationsController(Context context, WindowManager windowManager) {
        mContext = context;
        mResources = context.getResources();
        mNotificationManager =
                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);

        DisplayMetrics displayMetrics = new DisplayMetrics();
        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
    }

    /**
     * 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, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
            b.setContentIntent(pendingIntent);
        }

        SystemUIApplication.overrideNotificationAppName(mContext, b, true);

        Notification n = new Notification.BigTextStyle(b)
                .bigText(errorMsg)
                .build();
        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
    }
}
+111 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.os.UserHandle
import android.view.Display
import com.android.internal.R
import com.android.internal.messages.nano.SystemMessageProto
import com.android.systemui.SystemUIApplication
import com.android.systemui.util.NotificationChannels
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

/** Convenience class to handle showing and hiding notifications while taking a screenshot. */
class ScreenshotNotificationsController
@AssistedInject
internal constructor(
    @Assisted private val displayId: Int,
    private val context: Context,
    private val notificationManager: NotificationManager,
    private val devicePolicyManager: DevicePolicyManager,
) {
    private val res = context.resources

    /**
     * Sends a notification that the screenshot capture has failed.
     *
     * Errors for the non-default display are shown in a unique separate notification.
     */
    fun notifyScreenshotError(msgResId: Int) {
        val displayErrorString =
            if (displayId != Display.DEFAULT_DISPLAY) {
                " ($externalDisplayString)"
            } else {
                ""
            }
        val errorMsg = res.getString(msgResId) + displayErrorString

        // Repurpose the existing notification or create a new one
        val builder =
            Notification.Builder(context, NotificationChannels.ALERTS)
                .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title))
                .setContentTitle(
                    res.getString(com.android.systemui.res.R.string.screenshot_failed_title)
                )
                .setContentText(errorMsg)
                .setSmallIcon(com.android.systemui.res.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(context.getColor(R.color.system_notification_accent_color))
        val intent =
            devicePolicyManager.createAdminSupportIntent(
                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE
            )
        if (intent != null) {
            val pendingIntent =
                PendingIntent.getActivityAsUser(
                    context,
                    0,
                    intent,
                    PendingIntent.FLAG_IMMUTABLE,
                    null,
                    UserHandle.CURRENT
                )
            builder.setContentIntent(pendingIntent)
        }
        SystemUIApplication.overrideNotificationAppName(context, builder, true)
        val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build()
        // A different id for external displays to keep the 2 error notifications separated.
        val id =
            if (displayId == Display.DEFAULT_DISPLAY) {
                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT
            } else {
                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY
            }
        notificationManager.notify(id, notification)
    }

    private val externalDisplayString: String
        get() =
            res.getString(
                com.android.systemui.res.R.string.screenshot_failed_external_display_indication
            )

    /** Factory for [ScreenshotNotificationsController]. */
    @AssistedFactory
    interface Factory {
        fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController
    }
}
Loading