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

Commit 35768346 authored by Rasheed Lewis's avatar Rasheed Lewis
Browse files

CompatChanges for TileService#startActivityAndCollapse

For versions U+, the old startActivityAndCollapse(Intent) should not be
allowed to be called, and the binding flag
Context#BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS should not be bound.

Bug: 241766793
Test: atest TileLifecycleManagerTest
Change-Id: I95a823902b15b2a8c8309bf3cf1ff4ef8d4a26b5
parent 404b7181
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -40383,7 +40383,7 @@ package android.service.quicksettings {
    method public void onTileRemoved();
    method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
    method public final void showDialog(android.app.Dialog);
    method public final void startActivityAndCollapse(android.content.Intent);
    method @Deprecated public final void startActivityAndCollapse(android.content.Intent);
    method public final void startActivityAndCollapse(@NonNull android.app.PendingIntent);
    method public final void unlockAndRun(Runnable);
    field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+26 −2
Original line number Diff line number Diff line
@@ -24,6 +24,9 @@ import android.app.Dialog;
import android.app.PendingIntent;
import android.app.Service;
import android.app.StatusBarManager;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -166,6 +169,17 @@ public class TileService extends Service {
     */
    public static final String EXTRA_STATE = "state";

    /**
     * The method {@link TileService#startActivityAndCollapse(Intent)} will verify that only
     * apps targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher will
     * not be allowed to use it.
     *
     * @hide
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
    public static final long START_ACTIVITY_NEEDS_PENDING_INTENT = 241766793L;

    private final H mHandler = new H(Looper.getMainLooper());

    private boolean mListening = false;
@@ -251,7 +265,6 @@ public class TileService extends Service {
     * This will collapse the Quick Settings panel and show the dialog.
     *
     * @param dialog Dialog to show.
     *
     * @see #isLocked()
     */
    public final void showDialog(Dialog dialog) {
@@ -330,8 +343,19 @@ public class TileService extends Service {

    /**
     * Start an activity while collapsing the panel.
     *
     * @deprecated for versions {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and up,
     * use {@link TileService#startActivityAndCollapse(PendingIntent)} instead.
     * @throws UnsupportedOperationException if called in versions
     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and up
     */
    @Deprecated
    public final void startActivityAndCollapse(Intent intent) {
        if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT)) {
            throw new UnsupportedOperationException(
                    "startActivityAndCollapse: Starting activity from TileService using an Intent"
                            + " is not allowed.");
        }
        startActivity(intent);
        try {
            mService.onStartActivity(mTileToken);
+35 −14
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@
 */
package com.android.systemui.qs.external;

import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;

import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -41,15 +44,15 @@ import androidx.annotation.WorkerThread;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;

import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;

/**
 * Manages the lifecycle of a TileService.
 * <p>
@@ -124,7 +127,9 @@ public class TileLifecycleManager extends BroadcastReceiver implements
    /** Injectable factory for TileLifecycleManager. */
    @AssistedFactory
    public interface Factory {
        /** */
        /**
         *
         */
        TileLifecycleManager create(Intent intent, UserHandle userHandle);
    }

@@ -207,12 +212,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
            if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
            mBindTryCount++;
            try {
                mIsBound = mContext.bindServiceAsUser(mIntent, this,
                        Context.BIND_AUTO_CREATE
                                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                                | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
                                | Context.BIND_WAIVE_PRIORITY,
                        mUser);
                mIsBound = bindServices();
                if (!mIsBound) {
                    mContext.unbindService(this);
                }
@@ -237,6 +237,24 @@ public class TileLifecycleManager extends BroadcastReceiver implements
        }
    }

    private boolean bindServices() {
        String packageName = mIntent.getComponent().getPackageName();
        if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName,
                mUser)) {
            return mContext.bindServiceAsUser(mIntent, this,
                    Context.BIND_AUTO_CREATE
                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                            | Context.BIND_WAIVE_PRIORITY,
                    mUser);
        }
        return mContext.bindServiceAsUser(mIntent, this,
                Context.BIND_AUTO_CREATE
                        | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
                        | Context.BIND_WAIVE_PRIORITY,
                mUser);
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (DEBUG) Log.d(TAG, "onServiceConnected " + name);
@@ -418,8 +436,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements
            mPackageManagerAdapter.getPackageInfoAsUser(packageName, 0, mUser.getIdentifier());
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            if (DEBUG) Log.d(TAG, "Package not available: " + packageName, e);
            else Log.d(TAG, "Package not available: " + packageName);
            if (DEBUG) {
                Log.d(TAG, "Package not available: " + packageName, e);
            } else {
                Log.d(TAG, "Package not available: " + packageName);
            }
        }
        return false;
    }
+64 −1
Original line number Diff line number Diff line
@@ -15,11 +15,17 @@
 */
package com.android.systemui.qs.external;

import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
@@ -29,6 +35,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -59,6 +66,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.MockitoSession;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -77,11 +85,16 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
    private Handler mHandler;
    private TileLifecycleManager mStateManager;
    private TestContextWrapper mWrappedContext;
    private MockitoSession mMockitoSession;

    @Before
    public void setUp() throws Exception {
        setPackageEnabled(true);
        mTileServiceComponentName = new ComponentName(mContext, "FakeTileService.class");
        mMockitoSession = mockitoSession()
                .initMocks(this)
                .mockStatic(CompatChanges.class)
                .startMocking();

        // Stub.asInterface will just return itself.
        when(mMockTileService.queryLocalInterface(anyString())).thenReturn(mMockTileService);
@@ -106,7 +119,13 @@ public class TileLifecycleManagerTest extends SysuiTestCase {

    @After
    public void tearDown() throws Exception {
        if (mMockitoSession != null) {
            mMockitoSession.finishMocking();
        }
        if (mThread != null) {
            mThread.quit();
        }

        mStateManager.handleDestroy();
    }

@@ -290,6 +309,50 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
        verify(falseContext).unbindService(captor.getValue());
    }

    @Test
    public void testVersionUDoesNotBindsAllowBackgroundActivity() {
        mockChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, true);
        Context falseContext = mock(Context.class);
        TileLifecycleManager manager = new TileLifecycleManager(mHandler, falseContext,
                mock(IQSService.class),
                mMockPackageManagerAdapter,
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser);

        manager.setBindService(true);
        int flags = Context.BIND_AUTO_CREATE
                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                | Context.BIND_WAIVE_PRIORITY;

        verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any());
    }

    @Test
    public void testVersionLessThanUBindsAllowBackgroundActivity() {
        mockChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, false);
        Context falseContext = mock(Context.class);
        TileLifecycleManager manager = new TileLifecycleManager(mHandler, falseContext,
                mock(IQSService.class),
                mMockPackageManagerAdapter,
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser);

        manager.setBindService(true);
        int flags = Context.BIND_AUTO_CREATE
                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
                | Context.BIND_WAIVE_PRIORITY;

        verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any());
    }

    private void mockChangeEnabled(long changeId, boolean enabled) {
        doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                any(UserHandle.class)));
    }

    private static class TestContextWrapper extends ContextWrapper {
        private IntentFilter mLastIntentFilter;
        private int mLastFlag;