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

Commit 8d2f6976 authored by Anton Potapov's avatar Anton Potapov
Browse files

Add Low memory state TileService rebinding delay

The problem is that TileService might be unbound by the system when it
kills the app to release memory in Low Memory state. This change
increases service rebind delay and increases it even further for
Low Memory state.

Test: manually check TileService binding when the providing app is
killed
Test: atest TileLifecycleManagerTest TileServicesTest
Fixes: 302657931

Change-Id: I2f3601b1936e41a676a8ac947d4e816554f6a089
parent ce28f538
Loading
Loading
Loading
Loading
+39 −17
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.systemui.qs.external;

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

import android.app.ActivityManager;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -35,6 +36,7 @@ import android.os.UserHandle;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;

@@ -81,7 +83,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements

    // Bind retry control.
    private static final int MAX_BIND_RETRIES = 5;
    private static final int DEFAULT_BIND_RETRY_DELAY = 1000;
    private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS;
    private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS;

    // Shared prefs that hold tile lifecycle info.
    private static final String TILES = "tiles_prefs";
@@ -94,6 +97,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
    private final IBinder mToken = new Binder();
    private final PackageManagerAdapter mPackageManagerAdapter;
    private final BroadcastDispatcher mBroadcastDispatcher;
    private final ActivityManager mActivityManager;

    private Set<Integer> mQueuedMessages = new ArraySet<>();
    @Nullable
@@ -102,7 +106,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements
    private IBinder mClickBinder;

    private int mBindTryCount;
    private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
    private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
    private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false);
    private AtomicBoolean mBound = new AtomicBoolean(false);
    private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false);
    private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false);
@@ -115,7 +120,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
    @AssistedInject
    TileLifecycleManager(@Main Handler handler, Context context, IQSService service,
            PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher,
            @Assisted Intent intent, @Assisted UserHandle user,
            @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager,
            @Background DelayableExecutor executor) {
        mContext = context;
        mHandler = handler;
@@ -126,6 +131,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
        mExecutor = executor;
        mPackageManagerAdapter = packageManagerAdapter;
        mBroadcastDispatcher = broadcastDispatcher;
        mActivityManager = activityManager;
        if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
    }

@@ -152,10 +158,6 @@ public class TileLifecycleManager extends BroadcastReceiver implements
        }
    }

    public void setBindRetryDelay(int delayMs) {
        mBindRetryDelay = delayMs;
    }

    public boolean isActiveTile() {
        try {
            ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
@@ -250,19 +252,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements

    private boolean bindServices() {
        String packageName = mIntent.getComponent().getPackageName();
        int flags = Context.BIND_AUTO_CREATE
                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                | Context.BIND_WAIVE_PRIORITY;
        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, flags, 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,
                flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                mUser);
    }

@@ -352,8 +350,32 @@ public class TileLifecycleManager extends BroadcastReceiver implements
        if (!mBound.get()) return;
        if (DEBUG) Log.d(TAG, "handleDeath");
        if (checkComponentState()) {
            mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay);
            if (isDeathRebindScheduled.compareAndSet(false, true)) {
                mExecutor.executeDelayed(() -> {
                    setBindService(true);
                    isDeathRebindScheduled.set(false);
                }, getRebindDelay());
            }
        }
    }

    /**
     * @return the delay to automatically rebind after a service died. It provides a longer delay if
     * the device is a low memory state because the service is likely to get killed again by the
     * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds.
     */
    private long getRebindDelay() {
        final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
        mActivityManager.getMemoryInfo(info);

        final long delay;
        if (info.lowMemory) {
            delay = LOW_MEMORY_BIND_RETRY_DELAY;
        } else {
            delay = mBindRetryDelay;
        }
        Log.i(TAG, "Rebinding with a delay=" + delay);
        return delay;
    }

    private boolean checkComponentState() {
+4 −5
Original line number Diff line number Diff line
@@ -75,13 +75,12 @@ public class TileServiceManager {
    private boolean mStarted = false;

    TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
            BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
            CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
            UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
            CustomTileAddedRepository customTileAddedRepository) {
        this(tileServices, handler, userTracker, customTileAddedRepository,
                new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
                        new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
                tileLifecycleManagerFactory.create(
                        new Intent(TileService.ACTION_QS_TILE).setComponent(component),
                        userTracker.getUserHandle(), executor));
                        userTracker.getUserHandle()));
    }

    @VisibleForTesting
+11 −8
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ public class TileServices extends IQSService.Stub {
    private final UserTracker mUserTracker;
    private final StatusBarIconController mStatusBarIconController;
    private final PanelInteractor mPanelInteractor;
    private final TileLifecycleManager.Factory mTileLifecycleManagerFactory;
    private final CustomTileAddedRepository mCustomTileAddedRepository;
    private final DelayableExecutor mBackgroundExecutor;

@@ -96,6 +97,7 @@ public class TileServices extends IQSService.Stub {
            CommandQueue commandQueue,
            StatusBarIconController statusBarIconController,
            PanelInteractor panelInteractor,
            TileLifecycleManager.Factory tileLifecycleManagerFactory,
            CustomTileAddedRepository customTileAddedRepository,
            @Background DelayableExecutor backgroundExecutor) {
        mHost = host;
@@ -109,6 +111,7 @@ public class TileServices extends IQSService.Stub {
        mStatusBarIconController = statusBarIconController;
        mCommandQueue.addCallback(mRequestListeningCallback);
        mPanelInteractor = panelInteractor;
        mTileLifecycleManagerFactory = tileLifecycleManagerFactory;
        mCustomTileAddedRepository = customTileAddedRepository;
        mBackgroundExecutor = backgroundExecutor;
    }
@@ -137,8 +140,8 @@ public class TileServices extends IQSService.Stub {

    protected TileServiceManager onCreateTileService(ComponentName component,
            BroadcastDispatcher broadcastDispatcher) {
        return new TileServiceManager(this, mHandlerProvider.get(), component,
                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
        return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker,
                mTileLifecycleManagerFactory, mCustomTileAddedRepository);
    }

    public void freeService(CustomTileInterface tile, TileServiceManager service) {
+34 −4
Original line number Diff line number Diff line
@@ -29,12 +29,14 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -73,16 +75,18 @@ import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLifecycleManagerTest extends SysuiTestCase {
    private static final int TEST_FAIL_TIMEOUT = 5000;

    private final PackageManagerAdapter mMockPackageManagerAdapter =
            mock(PackageManagerAdapter.class);
    private final BroadcastDispatcher mMockBroadcastDispatcher =
            mock(BroadcastDispatcher.class);
    private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class);
    private final ActivityManager mActivityManager = mock(ActivityManager.class);

    private ComponentName mTileServiceComponentName;
    private Intent mTileServiceIntent;
    private UserHandle mUser;
    private FakeSystemClock mClock;
    private FakeExecutor mExecutor;
    private HandlerThread mThread;
    private Handler mHandler;
@@ -112,13 +116,15 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
        mThread = new HandlerThread("TestThread");
        mThread.start();
        mHandler = Handler.createAsync(mThread.getLooper());
        mExecutor = new FakeExecutor(new FakeSystemClock());
        mClock = new FakeSystemClock();
        mExecutor = new FakeExecutor(mClock);
        mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
                mock(IQSService.class),
                mMockPackageManagerAdapter,
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser,
                mActivityManager,
                mExecutor);
    }

@@ -294,11 +300,32 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
        mStateManager.onStartListening();
        mStateManager.executeSetBindService(true);
        mExecutor.runAllReady();
        mStateManager.setBindRetryDelay(0);
        mExecutor.runAllReady();
        mStateManager.onServiceDisconnected(mTileServiceComponentName);
        mClock.advanceTime(5000);

        // Two calls: one for the first bind, one for the restart.
        verifyBind(2);
        verify(mMockTileService, times(2)).onStartListening();
    }

    @Test
    public void testKillProcessLowMemory() throws Exception {
        doAnswer(invocation -> {
            ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0);
            memoryInfo.lowMemory = true;
            return null;
        }).when(mActivityManager).getMemoryInfo(any());
        mStateManager.onStartListening();
        mStateManager.executeSetBindService(true);
        mExecutor.runAllReady();
        mStateManager.onServiceDisconnected(mTileServiceComponentName);

        // Longer delay than a regular one
        mClock.advanceTime(5000);
        verifyBind(1);
        verify(mMockTileService, times(1)).onStartListening();

        mClock.advanceTime(20000);
        // Two calls: one for the first bind, one for the restart.
        verifyBind(2);
        verify(mMockTileService, times(2)).onStartListening();
@@ -319,6 +346,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser,
                mActivityManager,
                mExecutor);

        manager.executeSetBindService(true);
@@ -340,6 +368,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser,
                mActivityManager,
                mExecutor);

        manager.executeSetBindService(true);
@@ -361,6 +390,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
                mMockBroadcastDispatcher,
                mTileServiceIntent,
                mUser,
                mActivityManager,
                mExecutor);

        manager.executeSetBindService(true);
+1 −1
Original line number Diff line number Diff line
@@ -304,7 +304,7 @@ public class TileServicesTest extends SysuiTestCase {
                CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
            super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
                    commandQueue, statusBarIconController, panelInteractor,
                    customTileAddedRepository, executor);
                    mTileLifecycleManagerFactory, customTileAddedRepository, executor);
        }

        @Override