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

Commit bae9a5ba authored by Andrey Yepin's avatar Andrey Yepin Committed by Android (Google) Code Review
Browse files

Merge "Chooser Interactive Session API, add setTargetsEnabled method." into main

parents 0bd4ed12 a6b13543
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -42016,10 +42016,11 @@ package android.service.chooser {
  @FlaggedApi("android.service.chooser.interactive_chooser") public final class ChooserSession {
    method public void addStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.service.chooser.ChooserSession.StateListener);
    method public void close();
    method @Nullable public android.graphics.Rect getSize();
    method @Nullable public android.graphics.Rect getBounds();
    method public int getState();
    method @NonNull public android.service.chooser.ChooserSessionToken getToken();
    method public void removeStateListener(@NonNull android.service.chooser.ChooserSession.StateListener);
    method public void setTargetsEnabled(boolean);
    method public void updateIntent(@NonNull android.content.Intent);
    field public static final int STATE_CLOSED = 2; // 0x2
    field public static final int STATE_INITIALIZED = 0; // 0x0
+54 −3
Original line number Diff line number Diff line
@@ -283,6 +283,7 @@ import android.view.translation.UiTranslationManager;
import android.webkit.WebViewBootstrapFrameworkInitializer;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
@@ -1857,8 +1858,15 @@ public final class SystemServiceRegistry {
                });

        if (interactiveChooser()) {
            registerService(Context.CHOOSER_SERVICE, ChooserManager.class,
            registerService(
                    Context.CHOOSER_SERVICE,
                    ChooserManager.class,
                    new StaticServiceFetcher<>() {
                        @Override
                        public boolean isServiceEnabled(ContextImpl ctx) {
                            return isChooserManagerSupported(ctx);
                        }

                        @Override
                        public ChooserManager createService() {
                            return new ChooserManager();
@@ -2008,12 +2016,29 @@ public final class SystemServiceRegistry {
                    }
                    break;
            }
            // TODO (b/404593897): make it a case of the switch statement above when the flag is
            //  removed.
            if (interactiveChooser()) {
                if (Context.CHOOSER_SERVICE.equals(name) && !isChooserManagerSupported(ctx)) {
                    return null;
                }
            }
            Slog.wtf(TAG, "Manager wrapper not available: " + name);
            return null;
        }
        return ret;
    }

    private static boolean isChooserManagerSupported(ContextImpl ctx) {
        PackageManager pm = ctx.getPackageManager();
        if (pm == null) {
            return true;
        }
        return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                && !pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
    }

    /**
     * Gets a system service which has opted-in to being fetched without a context.
     * @hide
@@ -2452,14 +2477,36 @@ public final class SystemServiceRegistry {
     * and should be cached and retained process-wide.
     */
    static abstract class StaticServiceFetcher<T> implements ServiceFetcher<T> {
        /**
         * Indicates whether a service reference has been cached.
         * The cached reference can be {@code null} if the service is not available i.e.
         * {@link #isServiceEnabled(ContextImpl)} returns {@code false}.
         */
        @GuardedBy("StaticServiceFetcher.this")
        private boolean mIsCached = false;
        @GuardedBy("StaticServiceFetcher.this")
        private T mCachedInstance;

        @Override
        public final T getService(ContextImpl ctx) {
            synchronized (StaticServiceFetcher.this) {
                if (mCachedInstance == null) {
                if (mIsCached) {
                    return mCachedInstance;
                }
            }
            // In case isServiceEnabled would require an IPC, run it outside a synchronized block.
            boolean isEnabled = isServiceEnabled(ctx);
            synchronized (StaticServiceFetcher.this) {
                if (!mIsCached) {
                    try {
                        if (isEnabled) {
                            mCachedInstance = createService();
                            // for a bug-to-bug compatibility, do not cache null-references
                            mIsCached = mCachedInstance != null;
                        } else {
                            mCachedInstance = null;
                            mIsCached = true;
                        }
                    } catch (ServiceNotFoundException e) {
                        onServiceNotFound(e);
                    }
@@ -2468,6 +2515,10 @@ public final class SystemServiceRegistry {
            }
        }

        protected boolean isServiceEnabled(ContextImpl ctx) {
            return true;
        }

        public abstract T createService() throws ServiceNotFoundException;

        // Services that do not need a Context can potentially be fetched without one, but the
+4 −0
Original line number Diff line number Diff line
@@ -7005,6 +7005,10 @@ public abstract class Context {
     * Use with {@link #getSystemService(String)} to retrieve a
     * {@link android.service.chooser.ChooserManager}.
     *
     * <p class="note"><b>Note:</b> This service is not available on Wear OS, Android TV, or Android
     * Auto devices. On these form factors, calls to {@code #getSystemService} for this service will
     * return {@code null}.
     *
     * @see #getSystemService(String)
     * @see android.service.chooser.ChooserManager
     */
+12 −11
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ public final class ChooserSession {
         * Gets invoked when the Chooser bounds are changed. The rect parameter represents Chooser
         * window bounds in pixels.
         */
        void onBoundsChanged(@NonNull Rect size);
        void onBoundsChanged(@NonNull Rect bounds);
    }

    private final ChooserSessionImpl mChooserSession = new ChooserSessionImpl();
@@ -177,25 +177,26 @@ public final class ChooserSession {
    }

    /**
     * Sets whether the targets in the chooser UI are enabled.
     * Sets whether the targets in the chooser UI are enabled. By default targets are enabled.
     * <p>
     * This method is primarily intended to allow for managing a transient state,
     * particularly useful during long-running operations. By disabling targets,
     * launching application can prevent unintended interactions.
     * <p>A no-op when the session is not in the {@link #STATE_STARTED}.</p>
     *
     * @hide
     */
    public void setTargetsEnabled(boolean isEnabled) {
        mChooserSession.setTargetsEnabled(isEnabled);
    }

    /**
     * Get last reported Chooser size or null.
     * Gets the last bounds reported by the Chooser.
     *
     * @return the most recently reported Chooser bounds, or {@code null} if bounds have not yet
     * been received via {@link ChooserSession.StateListener#onBoundsChanged(Rect)}.
     */
    @Nullable
    public Rect getSize() {
        return mChooserSession.mSize.get();
    public Rect getBounds() {
        return mChooserSession.mBounds.get();
    }

    /**
@@ -237,7 +238,7 @@ public final class ChooserSession {
        @ChooserSession.State
        private int mState = STATE_INITIALIZED;

        private final AtomicReference<Rect> mSize = new AtomicReference<>();
        private final AtomicReference<Rect> mBounds = new AtomicReference<>();

        @Override
        public void registerChooserController(
@@ -282,11 +283,11 @@ public final class ChooserSession {
        }

        @Override
        public void onBoundsChanged(Rect size) {
            mSize.set(size);
        public void onBoundsChanged(@NonNull Rect bounds) {
            mBounds.set(bounds);
            notifyListeners((listener) -> {
                if (isActive()) {
                    listener.onBoundsChanged(size);
                    listener.onBoundsChanged(bounds);
                }
            });
        }
+19 −19
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ class ChooserSessionTest {
                    assertThat(state).isEqualTo(ChooserSession.STATE_STARTED)
                }

                override fun onBoundsChanged(size: Rect) {}
                override fun onBoundsChanged(bounds: Rect) {}
            }
        session.addStateListener(ImmediateExecutor(), stateListener)

@@ -98,28 +98,28 @@ class ChooserSessionTest {

    @EnableFlags(Flags.FLAG_INTERACTIVE_CHOOSER)
    @Test
    fun test_chooserSizeChanged_sizeReported() {
    fun test_chooserBoundsChanged_boundsReported() {
        val (session, controllerCallback) = prepareChooserSession()
        val sizes = listOf(Rect(1, 2, 3, 4), Rect(5, 6, 7, 8))
        val sizeUpdates = mutableListOf<Rect>()
        val bounds = listOf(Rect(1, 2, 3, 4), Rect(5, 6, 7, 8))
        val boundsUpdates = mutableListOf<Rect>()
        val stateListener =
            object : ChooserSession.StateListener {
                override fun onStateChanged(state: Int) {}

                override fun onBoundsChanged(size: Rect) {
                    assertThat(session.size).isEqualTo(size)
                    sizeUpdates.add(size)
                override fun onBoundsChanged(bounds: Rect) {
                    assertThat(session.bounds).isEqualTo(bounds)
                    boundsUpdates.add(bounds)
                }
            }
        session.addStateListener(ImmediateExecutor(), stateListener)

        assertThat(session.size).isNull()
        assertThat(session.bounds).isNull()

        for (size in sizes) {
            controllerCallback.onBoundsChanged(size)
        for (b in bounds) {
            controllerCallback.onBoundsChanged(b)
        }

        assertThat(sizeUpdates).containsExactlyElementsIn(sizes).inOrder()
        assertThat(boundsUpdates).containsExactlyElementsIn(bounds).inOrder()
    }

    @EnableFlags(Flags.FLAG_INTERACTIVE_CHOOSER)
@@ -142,7 +142,7 @@ class ChooserSessionTest {
                    assertThat(state).isEqualTo(ChooserSession.STATE_CLOSED)
                }

                override fun onBoundsChanged(size: Rect) {
                override fun onBoundsChanged(bounds: Rect) {
                    invocationCounter.incrementAndGet()
                }
            }
@@ -178,7 +178,7 @@ class ChooserSessionTest {
                    assertThat(state).isEqualTo(ChooserSession.STATE_CLOSED)
                }

                override fun onBoundsChanged(size: Rect) {
                override fun onBoundsChanged(bounds: Rect) {
                    invocationCounter.incrementAndGet()
                }
            }
@@ -224,12 +224,12 @@ class ChooserSessionTest {
        session.removeStateListener(firstListener)
        controllerCallback.onBoundsChanged(secondSize)

        var sizeCapture = argumentCaptor<Rect>()
        verify(firstListener) { 1 * { onBoundsChanged(sizeCapture.capture()) } }
        assertThat(sizeCapture.firstValue).isEqualTo(firstSize)
        sizeCapture = argumentCaptor<Rect>()
        verify(secondListener) { 1 * { onBoundsChanged(sizeCapture.capture()) } }
        assertThat(sizeCapture.firstValue).isEqualTo(secondSize)
        var boundsCapture = argumentCaptor<Rect>()
        verify(firstListener) { 1 * { onBoundsChanged(boundsCapture.capture()) } }
        assertThat(boundsCapture.firstValue).isEqualTo(firstSize)
        boundsCapture = argumentCaptor<Rect>()
        verify(secondListener) { 1 * { onBoundsChanged(boundsCapture.capture()) } }
        assertThat(boundsCapture.firstValue).isEqualTo(secondSize)
    }

    @EnableFlags(Flags.FLAG_INTERACTIVE_CHOOSER)