/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.admin.IDeviceAdminService; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.UserHandle; import android.test.AndroidTestCase; import android.util.Pair; import org.mockito.ArgumentMatchers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; public class PersistentConnectionTest extends AndroidTestCase { private static class MyConnection extends PersistentConnection { public long uptimeMillis = 12345; public ArrayList> scheduledRunnables = new ArrayList<>(); public MyConnection(String tag, Context context, Handler handler, int userId, ComponentName componentName, long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { super(tag, context, handler, userId, componentName, rebindBackoffSeconds, rebindBackoffIncrease, rebindMaxBackoffSeconds); } @Override protected IDeviceAdminService asInterface(IBinder binder) { return (IDeviceAdminService) binder; } @Override long injectUptimeMillis() { return uptimeMillis; } @Override void injectPostAtTime(Runnable r, long uptimeMillis) { scheduledRunnables.add(Pair.create(r, uptimeMillis)); } @Override void injectRemoveCallbacks(Runnable r) { for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { if (scheduledRunnables.get(i).first.equals(r)) { scheduledRunnables.remove(i); } } } void elapse(long milliSeconds) { uptimeMillis += milliSeconds; // Fire the scheduled runnables. // Note we collect first and then run all, because sometimes a scheduled runnable // calls removeCallbacks. final ArrayList list = new ArrayList<>(); for (int i = scheduledRunnables.size() - 1; i >= 0; i--) { if (scheduledRunnables.get(i).second <= uptimeMillis) { list.add(scheduledRunnables.get(i).first); scheduledRunnables.remove(i); } } Collections.reverse(list); for (Runnable r : list) { r.run(); } } } public void testAll() { final Context context = mock(Context.class); final int userId = 11; final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); final Handler handler = new Handler(Looper.getMainLooper()); final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, /* rebindBackoffSeconds= */ 5, /* rebindBackoffIncrease= */ 1.5, /* rebindMaxBackoffSeconds= */ 11); assertFalse(conn.isBound()); assertFalse(conn.isConnected()); assertFalse(conn.isRebindScheduled()); assertEquals(5000, conn.getNextBackoffMsForTest()); assertNull(conn.getServiceBinder()); when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), any(Handler.class), any(UserHandle.class))) .thenReturn(true); // Call bind. conn.bind(); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertFalse(conn.isRebindScheduled()); assertNull(conn.getServiceBinder()); assertEquals(5000, conn.getNextBackoffMsForTest()); verify(context).bindServiceAsUser( ArgumentMatchers.argThat(intent -> cn.equals(intent.getComponent())), eq(conn.getServiceConnectionForTest()), eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE), eq(handler), eq(UserHandle.of(userId))); // AM responds... conn.getServiceConnectionForTest().onServiceConnected(cn, new IDeviceAdminService.Stub() {}); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertTrue(conn.isConnected()); assertNotNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(5000, conn.getNextBackoffMsForTest()); // Now connected. // Call unbind... conn.unbind(); assertFalse(conn.isBound()); assertFalse(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); // Caller bind again... conn.bind(); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertFalse(conn.isRebindScheduled()); assertNull(conn.getServiceBinder()); assertEquals(5000, conn.getNextBackoffMsForTest()); // Now connected again. // The service got killed... conn.getServiceConnectionForTest().onServiceDisconnected(cn); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(5000, conn.getNextBackoffMsForTest()); // Connected again... conn.getServiceConnectionForTest().onServiceConnected(cn, new IDeviceAdminService.Stub() {}); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertTrue(conn.isConnected()); assertNotNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(5000, conn.getNextBackoffMsForTest()); // Then the binding is "died"... conn.getServiceConnectionForTest().onBindingDied(cn); assertFalse(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertTrue(conn.isRebindScheduled()); assertEquals(7500, conn.getNextBackoffMsForTest()); assertEquals( Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), conn.uptimeMillis + 5000)), conn.scheduledRunnables); // 5000 ms later... conn.elapse(5000); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(7500, conn.getNextBackoffMsForTest()); // Connected. conn.getServiceConnectionForTest().onServiceConnected(cn, new IDeviceAdminService.Stub() {}); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertTrue(conn.isConnected()); assertNotNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(7500, conn.getNextBackoffMsForTest()); // Then the binding is "died"... conn.getServiceConnectionForTest().onBindingDied(cn); assertFalse(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertTrue(conn.isRebindScheduled()); assertEquals(11000, conn.getNextBackoffMsForTest()); assertEquals( Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), conn.uptimeMillis + 7500)), conn.scheduledRunnables); // Later... conn.elapse(7500); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); assertEquals(11000, conn.getNextBackoffMsForTest()); // Then the binding is "died"... conn.getServiceConnectionForTest().onBindingDied(cn); assertFalse(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertTrue(conn.isRebindScheduled()); assertEquals(11000, conn.getNextBackoffMsForTest()); assertEquals( Arrays.asList(Pair.create(conn.getBindForBackoffRunnableForTest(), conn.uptimeMillis + 11000)), conn.scheduledRunnables); // Call unbind... conn.unbind(); assertFalse(conn.isBound()); assertFalse(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertNull(conn.getServiceBinder()); assertFalse(conn.isRebindScheduled()); // Call bind again... And now the backoff is reset to 5000. conn.bind(); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isConnected()); assertFalse(conn.isRebindScheduled()); assertNull(conn.getServiceBinder()); assertEquals(5000, conn.getNextBackoffMsForTest()); } public void testReconnectFiresAfterUnbind() { final Context context = mock(Context.class); final int userId = 11; final ComponentName cn = ComponentName.unflattenFromString("a.b.c/def"); final Handler handler = new Handler(Looper.getMainLooper()); final MyConnection conn = new MyConnection("tag", context, handler, userId, cn, /* rebindBackoffSeconds= */ 5, /* rebindBackoffIncrease= */ 1.5, /* rebindMaxBackoffSeconds= */ 11); when(context.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(), any(Handler.class), any(UserHandle.class))) .thenReturn(true); // Bind. conn.bind(); assertTrue(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertFalse(conn.isRebindScheduled()); conn.elapse(1000); // Service crashes. conn.getServiceConnectionForTest().onBindingDied(cn); assertFalse(conn.isBound()); assertTrue(conn.shouldBeBoundForTest()); assertTrue(conn.isRebindScheduled()); assertEquals(7500, conn.getNextBackoffMsForTest()); // Call unbind. conn.unbind(); assertFalse(conn.isBound()); assertFalse(conn.shouldBeBoundForTest()); // Now, at this point, it's possible that the scheduled runnable had already been fired // before during the unbind() call, and waiting on mLock. // To simulate it, we just call the runnable here. conn.getBindForBackoffRunnableForTest().run(); // Should still not be bound. assertFalse(conn.isBound()); assertFalse(conn.shouldBeBoundForTest()); } }