Loading core/api/current.txt +1 −1 Original line number Diff line number Diff line Loading @@ -40439,7 +40439,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"; core/java/android/service/quicksettings/TileService.java +26 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +35 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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> Loading Loading @@ -124,7 +127,9 @@ public class TileLifecycleManager extends BroadcastReceiver implements /** Injectable factory for TileLifecycleManager. */ @AssistedFactory public interface Factory { /** */ /** * */ TileLifecycleManager create(Intent intent, UserHandle userHandle); } Loading Loading @@ -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); } Loading @@ -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); Loading Loading @@ -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; } Loading packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +64 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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; Loading Loading
core/api/current.txt +1 −1 Original line number Diff line number Diff line Loading @@ -40439,7 +40439,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";
core/java/android/service/quicksettings/TileService.java +26 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading
packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +35 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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> Loading Loading @@ -124,7 +127,9 @@ public class TileLifecycleManager extends BroadcastReceiver implements /** Injectable factory for TileLifecycleManager. */ @AssistedFactory public interface Factory { /** */ /** * */ TileLifecycleManager create(Intent intent, UserHandle userHandle); } Loading Loading @@ -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); } Loading @@ -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); Loading Loading @@ -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; } Loading
packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +64 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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) Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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; Loading