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

Commit 5a580b5b authored by Ajinkya Chalke's avatar Ajinkya Chalke
Browse files

Implement work profile considerations in app clips

Bug: 267310185
Bug: 279146114
Test: atest AppClipsTrampolineActivityTest AppClipsActivityTest AppClipsViewModelTest
Change-Id: I874cfd4967ead3a012b0f0e6efab8de9ec33ab06
parent a08ca180
Loading
Loading
Loading
Loading
+3 −4
Original line number Original line Diff line number Diff line
@@ -293,10 +293,9 @@ public class ImageExporter {
            final ContentValues values = createMetadata(time, format, fileName);
            final ContentValues values = createMetadata(time, format, fileName);


            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            if (UserHandle.myUserId() != owner.getIdentifier()) {
            Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
                baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());

            }
            Uri uri = resolver.insert(uriWithUserId, values);
            Uri uri = resolver.insert(baseUri, values);
            if (uri == null) {
            if (uri == null) {
                throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
                throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
            }
            }
+6 −1
Original line number Original line Diff line number Diff line
@@ -247,7 +247,7 @@ public class AppClipsActivity extends ComponentActivity {
        }
        }


        updateImageDimensions();
        updateImageDimensions();
        mViewModel.saveScreenshotThenFinish(drawable, bounds);
        mViewModel.saveScreenshotThenFinish(drawable, bounds, getUser());
    }
    }


    private void setResultThenFinish(Uri uri) {
    private void setResultThenFinish(Uri uri) {
@@ -255,6 +255,11 @@ public class AppClipsActivity extends ComponentActivity {
            return;
            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();
        Bundle data = new Bundle();
        data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
        data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
                Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
                Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+14 −5
Original line number Original line Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.systemui.screenshot.appclips;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.view.Display;
import android.os.UserManager;


import androidx.annotation.Nullable;
import androidx.annotation.Nullable;


@@ -27,6 +27,7 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.infra.ServiceConnector;
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.settings.DisplayTracker;


import javax.inject.Inject;
import javax.inject.Inject;


@@ -35,14 +36,20 @@ import javax.inject.Inject;
class AppClipsCrossProcessHelper {
class AppClipsCrossProcessHelper {


    private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
    private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
    private final DisplayTracker mDisplayTracker;


    @Inject
    @Inject
    AppClipsCrossProcessHelper(@Application Context context) {
    AppClipsCrossProcessHelper(@Application Context context, UserManager userManager,
        mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context,
            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),
                new Intent(context, AppClipsScreenshotHelperService.class),
                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
                        | Context.BIND_NOT_VISIBLE, context.getUserId(),
                        | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(),
                IAppClipsScreenshotHelperService.Stub::asInterface);
                IAppClipsScreenshotHelperService.Stub::asInterface);
        mDisplayTracker = displayTracker;
    }
    }


    /**
    /**
@@ -56,7 +63,9 @@ class AppClipsCrossProcessHelper {
        try {
        try {
            AndroidFuture<ScreenshotHardwareBufferInternal> future =
            AndroidFuture<ScreenshotHardwareBufferInternal> future =
                    mProxyConnector.postForResult(
                    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();
            return future.get().createBitmapThenCloseBuffer();
        } catch (Exception e) {
        } catch (Exception e) {
            return null;
            return null;
+28 −8
Original line number Original line Diff line number Diff line
@@ -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_SUCCESS;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
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.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.flags.Flags.SCREENSHOT_APP_CLIPS;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
@@ -34,6 +33,7 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.Resources;
import android.net.Uri;
import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
@@ -82,6 +82,8 @@ public class AppClipsTrampolineActivity extends Activity {
    private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
    private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
    static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
    static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
    static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
    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 ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
    static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
    static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
    static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
    static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
@@ -98,6 +100,7 @@ public class AppClipsTrampolineActivity extends Activity {
    private final ResultReceiver mResultReceiver;
    private final ResultReceiver mResultReceiver;


    private Intent mKillAppClipsBroadcastIntent;
    private Intent mKillAppClipsBroadcastIntent;
    private UserHandle mNotesAppUser;


    @Inject
    @Inject
    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
@@ -165,15 +168,21 @@ public class AppClipsTrampolineActivity extends Activity {
            return;
            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();
        String callingPackageName = getCallingPackage();
        Intent intent = new Intent().setComponent(componentName)
        Intent intent = new Intent().setComponent(componentName)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
                .putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
                .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
                .putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);

        try {
        try {
            // Start the App Clips activity.
            // Start the App Clips activity for the user corresponding to the notes app user.
            startActivity(intent);
            startActivityAsUser(intent, mNotesAppUser);


            // Set up the broadcast intent that will inform the above App Clips activity to finish
            // Set up the broadcast intent that will inform the above App Clips activity to finish
            // when this trampoline activity is finished.
            // when this trampoline activity is finished.
@@ -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() {
    private void maybeStartActivityForWPUser() {
        UserHandle mainUser = mUserManager.getMainUser();
        UserHandle mainUser = mUserManager.getMainUser();
        if (mainUser == null) {
        if (mainUser == null) {
@@ -205,9 +221,13 @@ public class AppClipsTrampolineActivity extends Activity {
            return;
            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(
        startActivityAsUser(
                new Intent(this, AppClipsTrampolineActivity.class)
                new Intent(this, AppClipsTrampolineActivity.class)
                        .putExtra(EXTRA_USE_WP_USER, /* value= */ true)
                        .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
                        .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
    }
    }


@@ -221,7 +241,7 @@ public class AppClipsTrampolineActivity extends Activity {
        int callingPackageUid = 0;
        int callingPackageUid = 0;
        try {
        try {
            callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
            callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
                    APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
                    APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid;
        } catch (NameNotFoundException e) {
        } catch (NameNotFoundException e) {
            Log.d(TAG, "Couldn't find notes app UID " + e);
            Log.d(TAG, "Couldn't find notes app UID " + e);
        }
        }
@@ -254,14 +274,14 @@ public class AppClipsTrampolineActivity extends Activity {


            if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
            if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
                Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class);
                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.
            // Broadcast no longer required, setting it to null.
            mKillAppClipsBroadcastIntent = null;
            mKillAppClipsBroadcastIntent = null;


            // Expand the note bubble before returning the result.
            // Expand the note bubble before returning the result.
            mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS);
            mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser);
            setResult(RESULT_OK, convertedData);
            setResult(RESULT_OK, convertedData);
            finish();
            finish();
        }
        }
+3 −5
Original line number Original line Diff line number Diff line
@@ -26,7 +26,7 @@ import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.Uri;
import android.os.Process;
import android.os.UserHandle;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.LiveData;
@@ -110,16 +110,14 @@ final class AppClipsViewModel extends ViewModel {
     * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
     * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
     * {@link LiveData}.
     * {@link LiveData}.
     */
     */
    void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
    void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds, UserHandle user) {
        mBgExecutor.execute(() -> {
        mBgExecutor.execute(() -> {
            // Render the screenshot bitmap in background.
            // Render the screenshot bitmap in background.
            Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
            Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);


            // Export and save the screenshot in background.
            // Export and save the screenshot in background.
            // TODO(b/267310185): Save to work profile UserHandle.
            ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
            ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
                    mBgExecutor, UUID.randomUUID(), screenshotBitmap,
                    mBgExecutor, UUID.randomUUID(), screenshotBitmap, user);
                    Process.myUserHandle());


            // Get the result and update state on main thread.
            // Get the result and update state on main thread.
            exportFuture.addListener(() -> {
            exportFuture.addListener(() -> {
Loading