Add screenshot permission, action, and API.
- Permisison restricts the API to NOTES_ROLE. - Callers can use the SBM API to check if they should surface. a UI element that allows user to launch flow which apps can do using the intent action with startActivityForResult. Test: CTS test Bug: 251204849 Bug: 251205791 Change-Id: I9e27f6ed5a3cc62292a9e9839c0b71faa5fda190
This commit is contained in:
@@ -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";
|
||||
@@ -7190,6 +7191,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
|
||||
@@ -10699,6 +10701,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";
|
||||
@@ -10792,6 +10795,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";
|
||||
@@ -10847,6 +10855,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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -2579,6 +2579,14 @@
|
||||
<!-- ==================================================== -->
|
||||
<eat-comment />
|
||||
|
||||
<!-- Allows an application to capture screen content to perform a screenshot using the intent
|
||||
action {@link android.content.Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
|
||||
<p>Protection level: internal|role
|
||||
<p>Intended for use by ROLE_NOTES only.
|
||||
-->
|
||||
<permission android:name="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
|
||||
android:protectionLevel="internal|role" />
|
||||
|
||||
<!-- Allows an application to get notified when a screen capture of its windows is attempted.
|
||||
<p>Protection level: normal
|
||||
-->
|
||||
|
||||
@@ -3220,6 +3220,12 @@
|
||||
<string name="config_somnambulatorComponent" translatable="false"
|
||||
>com.android.systemui/com.android.systemui.Somnambulator</string>
|
||||
|
||||
<!-- The component name of the screenshot App Clips service that communicates with SystemUI to
|
||||
evaluate certain aspects of App Clips flow such as whether a calling activity can launch
|
||||
capture content for note activity. -->
|
||||
<string name="config_screenshotAppClipsServiceComponent" translatable="false"
|
||||
>com.android.systemui/com.android.systemui.screenshot.appclips.AppClipsService</string>
|
||||
|
||||
<!-- The component name of a special dock app that merely launches a dream.
|
||||
We don't want to launch this app when docked because it causes an unnecessary
|
||||
activity transition. We just want to start the dream.. -->
|
||||
|
||||
@@ -370,6 +370,7 @@
|
||||
<java-symbol type="string" name="config_controlsPackage" />
|
||||
<java-symbol type="string" name="config_screenRecorderComponent" />
|
||||
<java-symbol type="string" name="config_somnambulatorComponent" />
|
||||
<java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
|
||||
<java-symbol type="string" name="config_screenshotServiceComponent" />
|
||||
<java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
|
||||
<java-symbol type="string" name="config_slicePermissionComponent" />
|
||||
|
||||
Reference in New Issue
Block a user