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

Commit e0250db7 authored by Marcello Galhardo's avatar Marcello Galhardo Committed by Android (Google) Code Review
Browse files

Merge "Add metrics logging for App Clips."

parents cfee2faa 24849ab1
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -93,7 +93,13 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
    @UiEvent(doc = "User has discarded the result of a long screenshot")
    SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
    @UiEvent(doc = "A screenshot has been taken and saved to work profile")
    SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
    SCREENSHOT_SAVED_TO_WORK_PROFILE(1240),
    @UiEvent(doc = "Notes application triggered the screenshot for notes")
    SCREENSHOT_FOR_NOTE_TRIGGERED(1308),
    @UiEvent(doc = "User accepted the screenshot to be sent to the notes app")
    SCREENSHOT_FOR_NOTE_ACCEPTED(1309),
    @UiEvent(doc = "User cancelled the screenshot for notes app flow")
    SCREENSHOT_FOR_NOTE_CANCELLED(1310);

    private final int mId;

+46 −2
Original line number Diff line number Diff line
@@ -17,15 +17,21 @@
package com.android.systemui.screenshot;

import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_CANCELLED;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
@@ -33,15 +39,20 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;

import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.settings.UserTracker;

import javax.inject.Inject;

@@ -64,9 +75,15 @@ import javax.inject.Inject;
 *
 * TODO(b/267309532): Polish UI and animations.
 */
public final class AppClipsActivity extends ComponentActivity {
public class AppClipsActivity extends ComponentActivity {

    private static final String TAG = AppClipsActivity.class.getSimpleName();
    private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);

    private final AppClipsViewModel.Factory mViewModelFactory;
    private final PackageManager mPackageManager;
    private final UserTracker mUserTracker;
    private final UiEventLogger mUiEventLogger;
    private final BroadcastReceiver mBroadcastReceiver;
    private final IntentFilter mIntentFilter;

@@ -80,10 +97,17 @@ public final class AppClipsActivity extends ComponentActivity {
    private AppClipsViewModel mViewModel;

    private ResultReceiver mResultReceiver;
    @Nullable
    private String mCallingPackageName;
    private int mCallingPackageUid;

    @Inject
    public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory) {
    public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory,
            PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger) {
        mViewModelFactory = viewModelFactory;
        mPackageManager = packageManager;
        mUserTracker = userTracker;
        mUiEventLogger = uiEventLogger;

        mBroadcastReceiver = new BroadcastReceiver() {
            @Override
@@ -113,6 +137,7 @@ public final class AppClipsActivity extends ComponentActivity {
                RECEIVER_NOT_EXPORTED);

        Intent intent = getIntent();
        setUpUiLogging(intent);
        mResultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER, ResultReceiver.class);
        if (mResultReceiver == null) {
            setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
@@ -169,6 +194,17 @@ public final class AppClipsActivity extends ComponentActivity {
        }
    }

    private void setUpUiLogging(Intent intent) {
        mCallingPackageName = intent.getStringExtra(EXTRA_CALLING_PACKAGE_NAME);
        mCallingPackageUid = 0;
        try {
            mCallingPackageUid = mPackageManager.getApplicationInfoAsUser(mCallingPackageName,
                    APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
        } catch (NameNotFoundException e) {
            Log.d(TAG, "Couldn't find notes app UID " + e);
        }
    }

    private void setScreenshot(Bitmap screenshot) {
        // Set background, status and navigation bar colors as the activity is no longer
        // translucent.
@@ -228,6 +264,7 @@ public final class AppClipsActivity extends ComponentActivity {
        data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
        try {
            mResultReceiver.send(Activity.RESULT_OK, data);
            logUiEvent(SCREENSHOT_FOR_NOTE_ACCEPTED);
        } catch (Exception e) {
            // Do nothing.
        }
@@ -251,6 +288,9 @@ public final class AppClipsActivity extends ComponentActivity {
        data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode);
        try {
            mResultReceiver.send(RESULT_OK, data);
            if (errorCode == Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED) {
                logUiEvent(SCREENSHOT_FOR_NOTE_CANCELLED);
            }
        } catch (Exception e) {
            // Do nothing.
        }
@@ -259,6 +299,10 @@ public final class AppClipsActivity extends ComponentActivity {
        mResultReceiver = null;
    }

    private void logUiEvent(UiEventEnum uiEvent) {
        mUiEventLogger.log(uiEvent, mCallingPackageUid, mCallingPackageName);
    }

    private void updateImageDimensions() {
        Drawable drawable = mPreview.getDrawable();
        if (drawable == null) {
+43 −5
Original line number Diff line number Diff line
@@ -24,26 +24,33 @@ 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.ScreenshotEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;

import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.util.Log;

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

import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.settings.UserTracker;
import com.android.wm.shell.bubbles.Bubbles;

import java.util.Optional;
@@ -70,15 +77,23 @@ import javax.inject.Inject;
public class AppClipsTrampolineActivity extends Activity {

    private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
    public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
    static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
    public static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
    static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
    static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
    private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);

    private final DevicePolicyManager mDevicePolicyManager;
    private final FeatureFlags mFeatureFlags;
    private final Optional<Bubbles> mOptionalBubbles;
    private final NoteTaskController mNoteTaskController;
    private final PackageManager mPackageManager;
    private final UserTracker mUserTracker;
    private final UiEventLogger mUiEventLogger;
    private final ResultReceiver mResultReceiver;

    private Intent mKillAppClipsBroadcastIntent;
@@ -86,11 +101,15 @@ public class AppClipsTrampolineActivity extends Activity {
    @Inject
    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
            Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
            PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
            @Main Handler mainHandler) {
        mDevicePolicyManager = devicePolicyManager;
        mFeatureFlags = flags;
        mOptionalBubbles = optionalBubbles;
        mNoteTaskController = noteTaskController;
        mPackageManager = packageManager;
        mUserTracker = userTracker;
        mUiEventLogger = uiEventLogger;

        mResultReceiver = createResultReceiver(mainHandler);
    }
@@ -138,8 +157,12 @@ public class AppClipsTrampolineActivity extends Activity {
            return;
        }

        Intent intent = new Intent().setComponent(componentName).addFlags(
                Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver);
        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);
@@ -150,6 +173,9 @@ public class AppClipsTrampolineActivity extends Activity {
                    new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
                            .setComponent(componentName)
                            .setPackage(componentName.getPackageName());

            // Log successful triggering of screenshot for notes.
            logScreenshotTriggeredUiEvent(callingPackageName);
        } catch (ActivityNotFoundException e) {
            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
        }
@@ -170,6 +196,18 @@ public class AppClipsTrampolineActivity extends Activity {
        finish();
    }

    private void logScreenshotTriggeredUiEvent(@Nullable String callingPackageName) {
        int callingPackageUid = 0;
        try {
            callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
                    APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
        } catch (NameNotFoundException e) {
            Log.d(TAG, "Couldn't find notes app UID " + e);
        }

        mUiEventLogger.log(SCREENSHOT_FOR_NOTE_TRIGGERED, callingPackageUid, callingPackageName);
    }

    private class AppClipsResultReceiver extends ResultReceiver {

        AppClipsResultReceiver(Handler handler) {
+17 −7
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.screenshot;

import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;

import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
@@ -29,6 +31,7 @@ import android.net.Uri;
import android.os.Process;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@@ -49,7 +52,8 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;

/** A {@link ViewModel} to help with the App Clips screenshot flow. */
final class AppClipsViewModel extends ViewModel {
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public final class AppClipsViewModel extends ViewModel {

    private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
    private final ImageExporter mImageExporter;
@@ -76,7 +80,8 @@ final class AppClipsViewModel extends ViewModel {
    }

    /** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
    void performScreenshot() {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public void performScreenshot() {
        mBgExecutor.execute(() -> {
            Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
            mMainExecutor.execute(() -> {
@@ -90,12 +95,14 @@ final class AppClipsViewModel extends ViewModel {
    }

    /** Returns a {@link LiveData} that holds the captured screenshot. */
    LiveData<Bitmap> getScreenshot() {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public LiveData<Bitmap> getScreenshot() {
        return mScreenshotLiveData;
    }

    /** Returns a {@link LiveData} that holds the {@link Uri} where screenshot is saved. */
    LiveData<Uri> getResultLiveData() {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public LiveData<Uri> getResultLiveData() {
        return mResultLiveData;
    }

@@ -103,7 +110,8 @@ final class AppClipsViewModel extends ViewModel {
     * Returns a {@link LiveData} that holds the error codes for
     * {@link Intent#EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE}.
     */
    LiveData<Integer> getErrorLiveData() {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public LiveData<Integer> getErrorLiveData() {
        return mErrorLiveData;
    }

@@ -111,7 +119,8 @@ 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) {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
        mBgExecutor.execute(() -> {
            // Render the screenshot bitmap in background.
            Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
@@ -151,7 +160,8 @@ final class AppClipsViewModel extends ViewModel {
    }

    /** Helper factory to help with injecting {@link AppClipsViewModel}. */
    static final class Factory implements ViewModelProvider.Factory {
    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
    public static final class Factory implements ViewModelProvider.Factory {

        private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
        private final ImageExporter mImageExporter;
+6 −0
Original line number Diff line number Diff line
@@ -165,6 +165,12 @@
            android:exported="false"
            android:permission="com.android.systemui.permission.SELF"
            android:excludeFromRecents="true" />

        <activity
            android:name="com.android.systemui.screenshot.appclips.AppClipsActivityTest$AppClipsActivityTestable"
            android:exported="false"
            android:permission="com.android.systemui.permission.SELF"
            android:excludeFromRecents="true" />
    </application>

    <instrumentation android:name="android.testing.TestableInstrumentation"
Loading