Loading packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +3 −4 Original line number Diff line number Diff line Loading @@ -293,10 +293,9 @@ public class ImageExporter { final ContentValues values = createMetadata(time, format, fileName); Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; if (UserHandle.myUserId() != owner.getIdentifier()) { baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); } Uri uri = resolver.insert(baseUri, values); Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); Uri uri = resolver.insert(uriWithUserId, values); if (uri == null) { throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } Loading packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +6 −1 Original line number Diff line number Diff line Loading @@ -247,7 +247,7 @@ public class AppClipsActivity extends ComponentActivity { } updateImageDimensions(); mViewModel.saveScreenshotThenFinish(drawable, bounds); mViewModel.saveScreenshotThenFinish(drawable, bounds, getUser()); } private void setResultThenFinish(Uri uri) { Loading @@ -255,6 +255,11 @@ public class AppClipsActivity extends ComponentActivity { return; } // Grant permission here instead of in the trampoline activity because this activity can run // as work profile user so the URI can belong to the work profile user while the trampoline // activity always runs as main user. grantUriPermission(mCallingPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); Bundle data = new Bundle(); data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS); Loading packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java +14 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ package com.android.systemui.screenshot.appclips; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.view.Display; import android.os.UserManager; import androidx.annotation.Nullable; Loading @@ -27,6 +27,7 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.settings.DisplayTracker; import javax.inject.Inject; Loading @@ -35,14 +36,20 @@ import javax.inject.Inject; class AppClipsCrossProcessHelper { private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector; private final DisplayTracker mDisplayTracker; @Inject AppClipsCrossProcessHelper(@Application Context context) { mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context, AppClipsCrossProcessHelper(@Application Context context, UserManager userManager, DisplayTracker displayTracker) { // Start a service as main user so that even if the app clips activity is running as work // profile user the service is able to use correct instance of Bubbles to grab a screenshot // excluding the bubble layer. mProxyConnector = new ServiceConnector.Impl<>(context, new Intent(context, AppClipsScreenshotHelperService.class), Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE, context.getUserId(), | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(), IAppClipsScreenshotHelperService.Stub::asInterface); mDisplayTracker = displayTracker; } /** Loading @@ -56,7 +63,9 @@ class AppClipsCrossProcessHelper { try { AndroidFuture<ScreenshotHardwareBufferInternal> future = mProxyConnector.postForResult( service -> service.takeScreenshot(Display.DEFAULT_DISPLAY)); service -> // Take a screenshot of the default display of the user. service.takeScreenshot(mDisplayTracker.getDefaultDisplayId())); return future.get().createBitmapThenCloseBuffer(); } catch (Exception e) { return null; Loading packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +28 −8 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED; import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE; import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED; Loading @@ -34,6 +33,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; Loading Loading @@ -82,6 +82,8 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; @VisibleForTesting static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; Loading @@ -98,6 +100,7 @@ public class AppClipsTrampolineActivity extends Activity { private final ResultReceiver mResultReceiver; private Intent mKillAppClipsBroadcastIntent; private UserHandle mNotesAppUser; @Inject public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Loading Loading @@ -165,15 +168,21 @@ public class AppClipsTrampolineActivity extends Activity { return; } mNotesAppUser = getUser(); if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) { // Get the work profile user internally instead of passing around via intent extras as // this activity is exported apps could potentially mess around with intent extras. mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser); } String callingPackageName = getCallingPackage(); Intent intent = new Intent().setComponent(componentName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver) .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName); try { // Start the App Clips activity. startActivity(intent); // Start the App Clips activity for the user corresponding to the notes app user. startActivityAsUser(intent, mNotesAppUser); // Set up the broadcast intent that will inform the above App Clips activity to finish // when this trampoline activity is finished. Loading @@ -198,6 +207,13 @@ public class AppClipsTrampolineActivity extends Activity { } } private Optional<UserHandle> getWorkProfileUser() { return mUserTracker.getUserProfiles().stream() .filter(profile -> mUserManager.isManagedProfile(profile.id)) .findFirst() .map(UserInfo::getUserHandle); } private void maybeStartActivityForWPUser() { UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { Loading @@ -205,9 +221,13 @@ public class AppClipsTrampolineActivity extends Activity { return; } // Start the activity as the main user with activity result forwarding. // Start the activity as the main user with activity result forwarding. Set the intent extra // so that the newly started trampoline activity starts the actual app clips activity as the // work profile user. Starting the app clips activity as the work profile user is required // to save the screenshot in work profile user storage and grant read permission to the URI. startActivityAsUser( new Intent(this, AppClipsTrampolineActivity.class) .putExtra(EXTRA_USE_WP_USER, /* value= */ true) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser); } Loading @@ -221,7 +241,7 @@ public class AppClipsTrampolineActivity extends Activity { int callingPackageUid = 0; try { callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName, APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid; APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid; } catch (NameNotFoundException e) { Log.d(TAG, "Couldn't find notes app UID " + e); } Loading Loading @@ -254,14 +274,14 @@ public class AppClipsTrampolineActivity extends Activity { if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) { Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class); convertedData.setData(uri).addFlags(FLAG_GRANT_READ_URI_PERMISSION); convertedData.setData(uri); } // Broadcast no longer required, setting it to null. mKillAppClipsBroadcastIntent = null; // Expand the note bubble before returning the result. mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS); mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser); setResult(RESULT_OK, convertedData); finish(); } Loading packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java +3 −5 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.UserHandle; import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; Loading Loading @@ -110,16 +110,14 @@ final class AppClipsViewModel extends ViewModel { * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to * {@link LiveData}. */ void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) { void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds, UserHandle user) { mBgExecutor.execute(() -> { // Render the screenshot bitmap in background. Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds); // Export and save the screenshot in background. // TODO(b/267310185): Save to work profile UserHandle. ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBgExecutor, UUID.randomUUID(), screenshotBitmap, Process.myUserHandle()); mBgExecutor, UUID.randomUUID(), screenshotBitmap, user); // Get the result and update state on main thread. exportFuture.addListener(() -> { Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +3 −4 Original line number Diff line number Diff line Loading @@ -293,10 +293,9 @@ public class ImageExporter { final ContentValues values = createMetadata(time, format, fileName); Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; if (UserHandle.myUserId() != owner.getIdentifier()) { baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); } Uri uri = resolver.insert(baseUri, values); Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); Uri uri = resolver.insert(uriWithUserId, values); if (uri == null) { throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL); } Loading
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +6 −1 Original line number Diff line number Diff line Loading @@ -247,7 +247,7 @@ public class AppClipsActivity extends ComponentActivity { } updateImageDimensions(); mViewModel.saveScreenshotThenFinish(drawable, bounds); mViewModel.saveScreenshotThenFinish(drawable, bounds, getUser()); } private void setResultThenFinish(Uri uri) { Loading @@ -255,6 +255,11 @@ public class AppClipsActivity extends ComponentActivity { return; } // Grant permission here instead of in the trampoline activity because this activity can run // as work profile user so the URI can belong to the work profile user while the trampoline // activity always runs as main user. grantUriPermission(mCallingPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); Bundle data = new Bundle(); data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS); Loading
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java +14 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ package com.android.systemui.screenshot.appclips; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.view.Display; import android.os.UserManager; import androidx.annotation.Nullable; Loading @@ -27,6 +27,7 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.settings.DisplayTracker; import javax.inject.Inject; Loading @@ -35,14 +36,20 @@ import javax.inject.Inject; class AppClipsCrossProcessHelper { private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector; private final DisplayTracker mDisplayTracker; @Inject AppClipsCrossProcessHelper(@Application Context context) { mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context, AppClipsCrossProcessHelper(@Application Context context, UserManager userManager, DisplayTracker displayTracker) { // Start a service as main user so that even if the app clips activity is running as work // profile user the service is able to use correct instance of Bubbles to grab a screenshot // excluding the bubble layer. mProxyConnector = new ServiceConnector.Impl<>(context, new Intent(context, AppClipsScreenshotHelperService.class), Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE, context.getUserId(), | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(), IAppClipsScreenshotHelperService.Stub::asInterface); mDisplayTracker = displayTracker; } /** Loading @@ -56,7 +63,9 @@ class AppClipsCrossProcessHelper { try { AndroidFuture<ScreenshotHardwareBufferInternal> future = mProxyConnector.postForResult( service -> service.takeScreenshot(Display.DEFAULT_DISPLAY)); service -> // Take a screenshot of the default display of the user. service.takeScreenshot(mDisplayTracker.getDefaultDisplayId())); return future.get().createBitmapThenCloseBuffer(); } catch (Exception e) { return null; Loading
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +28 −8 Original line number Diff line number Diff line Loading @@ -21,7 +21,6 @@ import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS; import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED; import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE; import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED; Loading @@ -34,6 +33,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; Loading Loading @@ -82,6 +82,8 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; @VisibleForTesting static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; Loading @@ -98,6 +100,7 @@ public class AppClipsTrampolineActivity extends Activity { private final ResultReceiver mResultReceiver; private Intent mKillAppClipsBroadcastIntent; private UserHandle mNotesAppUser; @Inject public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Loading Loading @@ -165,15 +168,21 @@ public class AppClipsTrampolineActivity extends Activity { return; } mNotesAppUser = getUser(); if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) { // Get the work profile user internally instead of passing around via intent extras as // this activity is exported apps could potentially mess around with intent extras. mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser); } String callingPackageName = getCallingPackage(); Intent intent = new Intent().setComponent(componentName) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver) .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName); try { // Start the App Clips activity. startActivity(intent); // Start the App Clips activity for the user corresponding to the notes app user. startActivityAsUser(intent, mNotesAppUser); // Set up the broadcast intent that will inform the above App Clips activity to finish // when this trampoline activity is finished. Loading @@ -198,6 +207,13 @@ public class AppClipsTrampolineActivity extends Activity { } } private Optional<UserHandle> getWorkProfileUser() { return mUserTracker.getUserProfiles().stream() .filter(profile -> mUserManager.isManagedProfile(profile.id)) .findFirst() .map(UserInfo::getUserHandle); } private void maybeStartActivityForWPUser() { UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { Loading @@ -205,9 +221,13 @@ public class AppClipsTrampolineActivity extends Activity { return; } // Start the activity as the main user with activity result forwarding. // Start the activity as the main user with activity result forwarding. Set the intent extra // so that the newly started trampoline activity starts the actual app clips activity as the // work profile user. Starting the app clips activity as the work profile user is required // to save the screenshot in work profile user storage and grant read permission to the URI. startActivityAsUser( new Intent(this, AppClipsTrampolineActivity.class) .putExtra(EXTRA_USE_WP_USER, /* value= */ true) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser); } Loading @@ -221,7 +241,7 @@ public class AppClipsTrampolineActivity extends Activity { int callingPackageUid = 0; try { callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName, APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid; APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid; } catch (NameNotFoundException e) { Log.d(TAG, "Couldn't find notes app UID " + e); } Loading Loading @@ -254,14 +274,14 @@ public class AppClipsTrampolineActivity extends Activity { if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) { Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class); convertedData.setData(uri).addFlags(FLAG_GRANT_READ_URI_PERMISSION); convertedData.setData(uri); } // Broadcast no longer required, setting it to null. mKillAppClipsBroadcastIntent = null; // Expand the note bubble before returning the result. mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS); mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser); setResult(RESULT_OK, convertedData); finish(); } Loading
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java +3 −5 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.UserHandle; import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; Loading Loading @@ -110,16 +110,14 @@ final class AppClipsViewModel extends ViewModel { * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to * {@link LiveData}. */ void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) { void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds, UserHandle user) { mBgExecutor.execute(() -> { // Render the screenshot bitmap in background. Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds); // Export and save the screenshot in background. // TODO(b/267310185): Save to work profile UserHandle. ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export( mBgExecutor, UUID.randomUUID(), screenshotBitmap, Process.myUserHandle()); mBgExecutor, UUID.randomUUID(), screenshotBitmap, user); // Get the result and update state on main thread. exportFuture.addListener(() -> { Loading