diff --git a/core/api/current.txt b/core/api/current.txt index 5dd1b3938f40b..42a979b8b90c7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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"; diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index 7b6ff975e8df3..d957029f42ed6 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -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); @@ -410,7 +434,7 @@ public class TileService extends Service { } @Override - public void onUnlockComplete() throws RemoteException{ + public void onUnlockComplete() throws RemoteException { mHandler.sendEmptyMessage(H.MSG_UNLOCK_COMPLETE); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index d393680124875..385e72017bae6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -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. *
@@ -124,7 +127,9 @@ public class TileLifecycleManager extends BroadcastReceiver implements /** Injectable factory for TileLifecycleManager. */ @AssistedFactory public interface Factory { - /** */ + /** + * + */ TileLifecycleManager create(Intent intent, UserHandle userHandle); } @@ -161,7 +166,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements * Determines whether the associated TileService is a Boolean Tile. * * @return true if {@link TileService#META_DATA_TOGGLEABLE_TILE} is set to {@code true} for this - * tile + * tile * @see TileService#META_DATA_TOGGLEABLE_TILE */ public boolean isToggleableTile() { @@ -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; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 04b50d8d98c1c..2e6b0cf7b0a81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -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 { - mThread.quit(); + 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;