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
diff --git a/core/api/current.txt b/core/api/current.txt
index 5dd1b39..42a979b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40383,7 +40383,7 @@
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 7b6ff97..d957029 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -24,6 +24,9 @@
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 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 @@
* 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 @@
/**
* 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 @@
}
@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 d393680..385e720 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 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 @@
/** Injectable factory for TileLifecycleManager. */
@AssistedFactory
public interface Factory {
- /** */
+ /**
+ *
+ */
TileLifecycleManager create(Intent intent, UserHandle userHandle);
}
@@ -161,7 +166,7 @@
* 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 @@
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 @@
}
}
+ 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 @@
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 04b50d8..2e6b0cf 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.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.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -77,11 +85,16 @@
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 @@
@After
public void tearDown() throws Exception {
- mThread.quit();
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
+ if (mThread != null) {
+ mThread.quit();
+ }
+
mStateManager.handleDestroy();
}
@@ -290,6 +309,50 @@
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;