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

Commit a3783bcb authored by Ajinkya Chalke's avatar Ajinkya Chalke Committed by Android (Google) Code Review
Browse files

Merge changes from topic "screenshot-permission"

* changes:
  Implement the new screenshot activities.
  WM changes to support new screenshot functionality.
  Add flag for the new App Clips screenshot feature.
  Add screenshot permission, action, and API.
parents 6f2834f4 618e46f6
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -121,6 +121,7 @@ package android {
    field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
    field public static final String INTERNET = "android.permission.INTERNET";
    field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
    field public static final String LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
    field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
    field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
    field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
@@ -7197,6 +7198,7 @@ package android.app {
  }
  public class StatusBarManager {
    method @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public boolean canLaunchCaptureContentActivityForNote(@NonNull android.app.Activity);
    method public void requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
    field public static final int TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND = 1004; // 0x3ec
    field public static final int TILE_ADD_REQUEST_ERROR_BAD_COMPONENT = 1002; // 0x3ea
@@ -10711,6 +10713,7 @@ package android.content {
    field public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
    field public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE";
    field @Deprecated public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
    field @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
    field public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
    field public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
    field public static final String ACTION_MAIN = "android.intent.action.MAIN";
@@ -10804,6 +10807,11 @@ package android.content {
    field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
    field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
    field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
    field public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4; // 0x4
    field public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1; // 0x1
    field public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0; // 0x0
    field public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2; // 0x2
    field public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3; // 0x3
    field public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET = "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
    field public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
    field public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
@@ -10859,6 +10867,7 @@ package android.content {
    field public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
    field public static final String EXTRA_BCC = "android.intent.extra.BCC";
    field public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
    field public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE = "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";
    field public static final String EXTRA_CC = "android.intent.extra.CC";
    field @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME = "android.intent.extra.changed_component_name";
    field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
+33 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.media.INearbyMediaDevicesProvider;
import android.media.INearbyMediaDevicesUpdateCallback;
@@ -47,6 +48,7 @@ import android.util.Pair;
import android.util.Slog;
import android.view.View;

import com.android.internal.statusbar.AppClipsServiceConnector;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -1190,6 +1192,37 @@ public class StatusBarManager {
        return CompatChanges.isChangeEnabled(MEDIA_CONTROL_SESSION_ACTIONS, packageName, user);
    }

    /**
     * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
     * a system activity that captures content on the screen to take a screenshot.
     *
     * <p>Note: The result should not be cached.
     *
     * <p>The system activity displays an editing tool that allows user to edit the screenshot, save
     * it on device, and return the edited screenshot as {@link android.net.Uri} to the calling
     * activity. User interaction is required to return the edited screenshot to the calling
     * activity.
     *
     * <p>When {@code true}, callers can use {@link Activity#startActivityForResult(Intent, int)}
     * to start start the content capture activity using
     * {@link Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
     *
     * @param activity Calling activity
     * @return true if the activity supports launching the capture content activity for note.
     *
     * @see Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
     * @see Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
     * @see android.app.role.RoleManager#ROLE_NOTES
     */
    @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
    public boolean canLaunchCaptureContentActivityForNote(@NonNull Activity activity) {
        Objects.requireNonNull(activity);
        IBinder activityToken = activity.getActivityToken();
        int taskId = ActivityClient.getInstance().getTaskForActivity(activityToken, false);
        return new AppClipsServiceConnector(mContext)
                .canLaunchCaptureContentActivityForNote(taskId);
    }

    /** @hide */
    public static String windowStateToString(int state) {
        if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
+82 −0
Original line number Diff line number Diff line
@@ -31,8 +31,10 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.StatusBarManager;
import android.bluetooth.BluetoothDevice;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
@@ -5136,6 +5138,86 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";

    /**
     * Activity Action: Use with startActivityForResult to start a system activity that captures
     * content on the screen to take a screenshot and present it to the user for editing. The
     * edited screenshot is saved on device and returned to the calling activity as a {@link Uri}
     * through {@link #getData()}. User interaction is required to return the edited screenshot to
     * the calling activity.
     *
     * <p>This intent action requires the permission
     * {@link android.Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
     *
     * <p>Callers should query
     * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} before showing a UI
     * element that allows users to trigger this flow.
     */
    @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE =
            "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";

    /**
     * An int extra used by activity started with
     * {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} to indicate status of the response.
     * This extra is used along with result code set to {@link android.app.Activity#RESULT_OK}.
     *
     * <p>The value for this extra can be one of the following:
     * <ul>
     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_SUCCESS}</li>
     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_FAILED}</li>
     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED}</li>
     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED}</li>
     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN}</li>
     * </ul>
     */
    public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE =
            "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";

    /**
     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
     * that the request was a success.
     *
     * <p>This code will only be returned after the user has interacted with the system screenshot
     * activity to consent to sharing the data with the note.
     *
     * <p>The captured screenshot is returned as a {@link Uri} through {@link #getData()}.
     */
    public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0;

    /**
     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
     * that something went wrong.
     */
    public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1;

    /**
     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
     * that user canceled the content capture flow.
     */
    public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2;

    /**
     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
     * that the intent action {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} was started
     * by an activity that is running in a non-supported window mode.
     */
    public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3;

    /**
     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
     * that screenshot is blocked by IT admin.
     */
    public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4;

    /** @hide */
    @IntDef(value = {
            CAPTURE_CONTENT_FOR_NOTE_SUCCESS, CAPTURE_CONTENT_FOR_NOTE_FAILED,
            CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED,
            CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CaptureContentForNoteStatusCodes {}

    /**
     * Broadcast Action: Sent to the integrity component when a package
     * needs to be verified. The data contains the package URI along with other relevant
+99 −0
Original line number 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.internal.statusbar;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;

import java.util.concurrent.CompletableFuture;

/**
 * A helper class to communicate with the App Clips service running in SystemUI.
 */
public class AppClipsServiceConnector {

    private static final String TAG = AppClipsServiceConnector.class.getSimpleName();

    private final Context mContext;
    private final Handler mHandler;

    public AppClipsServiceConnector(Context context) {
        mContext = context;
        HandlerThread handlerThread = new HandlerThread(TAG);
        handlerThread.start();
        mHandler = handlerThread.getThreadHandler();
    }

    /**
     * @return true if the task represented by {@code taskId} can launch App Clips screenshot flow,
     * false otherwise.
     */
    public boolean canLaunchCaptureContentActivityForNote(int taskId) {
        try {
            CompletableFuture<Boolean> future = new CompletableFuture<>();
            connectToServiceAndProcessRequest(taskId, future);
            return future.get();
        } catch (Exception e) {
            Log.d(TAG, "Exception from service\n" + e);
        }

        return false;
    }

    private void connectToServiceAndProcessRequest(int taskId, CompletableFuture<Boolean> future) {
        ServiceConnection serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                try {
                    future.complete(IAppClipsService.Stub.asInterface(
                            service).canLaunchCaptureContentActivityForNote(taskId));
                } catch (Exception e) {
                    Log.d(TAG, "Exception from service\n" + e);
                }
                future.complete(false);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                if (!future.isDone()) {
                    future.complete(false);
                }
            }
        };

        final ComponentName serviceComponent = ComponentName.unflattenFromString(
                mContext.getResources().getString(
                        com.android.internal.R.string.config_screenshotAppClipsServiceComponent));
        final Intent serviceIntent = new Intent();
        serviceIntent.setComponent(serviceComponent);

        boolean bindService = mContext.bindServiceAsUser(serviceIntent, serviceConnection,
                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, mHandler,
                mContext.getUser());

        // Complete the future early if service not bound.
        if (!bindService) {
            future.complete(false);
        }
    }
}
+26 −0
Original line number 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.internal.statusbar;

/**
 * A service that runs in SystemUI and helps determine if App Clips flow is supported in the
 * current state of device. This service needs to run in SystemUI in order to communicate with the
 * instance of app bubbles.
 */
interface IAppClipsService {
    boolean canLaunchCaptureContentActivityForNote(in int taskId);
}
 No newline at end of file
Loading