Loading core/java/android/service/controls/ControlsProviderService.java +25 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading Loading @@ -285,6 +286,7 @@ public abstract class ControlsProviderService extends Service { private IControlsSubscriber mCs; private boolean mEnforceStateless; private Context mContext; private SubscriptionAdapter mSubscription; SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) { mEnforceStateless = enforceStateless; Loading @@ -300,11 +302,14 @@ public abstract class ControlsProviderService extends Service { public void onSubscribe(Subscription subscription) { try { mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription)); SubscriptionAdapter subscriptionAdapter = new SubscriptionAdapter(subscription); mCs.onSubscribe(mToken, subscriptionAdapter); mSubscription = subscriptionAdapter; } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onNext(@NonNull Control control) { Preconditions.checkNotNull(control); try { Loading @@ -318,20 +323,36 @@ public abstract class ControlsProviderService extends Service { } mCs.onNext(mToken, control); } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onError(Throwable t) { try { mCs.onError(mToken, t.toString()); mSubscription = null; } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onComplete() { try { mCs.onComplete(mToken); mSubscription = null; } catch (RemoteException ex) { handleRemoteException(ex); } } private void handleRemoteException(RemoteException ex) { if (ex instanceof DeadObjectException) { // System UI crashed or is restarting. There is no need to rethrow this SubscriptionAdapter subscriptionAdapter = mSubscription; if (subscriptionAdapter != null) { subscriptionAdapter.cancel(); } } else { ex.rethrowAsRuntimeException(); } } Loading core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java +45 −11 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -36,6 +37,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Bundle; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.service.controls.actions.CommandAction; Loading @@ -53,6 +55,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; Loading Loading @@ -307,6 +310,18 @@ public class ControlProviderServiceTest { intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL))); } @Test public void testOnNextDoesntRethrowDeadObjectException() throws RemoteException { doAnswer(invocation -> { throw new DeadObjectException(); }).when(mSubscriber).onNext(ArgumentMatchers.any(), ArgumentMatchers.any()); Control control = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build(); sendControlGetControl(control); assertTrue(mControlsProviderService.mSubscription.mIsCancelled); } /** * Sends the control through the publisher in {@code mControlsProviderService}, returning * the control obtained by the subscriber Loading Loading @@ -359,6 +374,7 @@ public class ControlProviderServiceTest { } private List<Control> mControls; private FakeSubscription mSubscription; public void setControls(List<Control> controls) { mControls = controls; Loading Loading @@ -398,17 +414,35 @@ public class ControlProviderServiceTest { } private Subscription createSubscription(Subscriber s, List<Control> controls) { return new Subscription() { FakeSubscription subscription = new FakeSubscription(s, controls); mSubscription = subscription; return subscription; } } private static final class FakeSubscription implements Subscription { private final Subscriber mSubscriber; private final List<Control> mControls; private boolean mIsCancelled = false; FakeSubscription(Subscriber s, List<Control> controls) { mSubscriber = s; mControls = controls; } public void request(long n) { int i = 0; for (Control c : mControls) { if (i++ < n) s.onNext(c); if (i++ < n) mSubscriber.onNext(c); else break; } s.onComplete(); mSubscriber.onComplete(); } public void cancel() {} }; public void cancel() { mIsCancelled = true; } } } Loading
core/java/android/service/controls/ControlsProviderService.java +25 −4 Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; Loading Loading @@ -285,6 +286,7 @@ public abstract class ControlsProviderService extends Service { private IControlsSubscriber mCs; private boolean mEnforceStateless; private Context mContext; private SubscriptionAdapter mSubscription; SubscriberProxy(boolean enforceStateless, IBinder token, IControlsSubscriber cs) { mEnforceStateless = enforceStateless; Loading @@ -300,11 +302,14 @@ public abstract class ControlsProviderService extends Service { public void onSubscribe(Subscription subscription) { try { mCs.onSubscribe(mToken, new SubscriptionAdapter(subscription)); SubscriptionAdapter subscriptionAdapter = new SubscriptionAdapter(subscription); mCs.onSubscribe(mToken, subscriptionAdapter); mSubscription = subscriptionAdapter; } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onNext(@NonNull Control control) { Preconditions.checkNotNull(control); try { Loading @@ -318,20 +323,36 @@ public abstract class ControlsProviderService extends Service { } mCs.onNext(mToken, control); } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onError(Throwable t) { try { mCs.onError(mToken, t.toString()); mSubscription = null; } catch (RemoteException ex) { ex.rethrowAsRuntimeException(); handleRemoteException(ex); } } public void onComplete() { try { mCs.onComplete(mToken); mSubscription = null; } catch (RemoteException ex) { handleRemoteException(ex); } } private void handleRemoteException(RemoteException ex) { if (ex instanceof DeadObjectException) { // System UI crashed or is restarting. There is no need to rethrow this SubscriptionAdapter subscriptionAdapter = mSubscription; if (subscriptionAdapter != null) { subscriptionAdapter.cancel(); } } else { ex.rethrowAsRuntimeException(); } } Loading
core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java +45 −11 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -36,6 +37,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Bundle; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.service.controls.actions.CommandAction; Loading @@ -53,6 +55,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; Loading Loading @@ -307,6 +310,18 @@ public class ControlProviderServiceTest { intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL))); } @Test public void testOnNextDoesntRethrowDeadObjectException() throws RemoteException { doAnswer(invocation -> { throw new DeadObjectException(); }).when(mSubscriber).onNext(ArgumentMatchers.any(), ArgumentMatchers.any()); Control control = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build(); sendControlGetControl(control); assertTrue(mControlsProviderService.mSubscription.mIsCancelled); } /** * Sends the control through the publisher in {@code mControlsProviderService}, returning * the control obtained by the subscriber Loading Loading @@ -359,6 +374,7 @@ public class ControlProviderServiceTest { } private List<Control> mControls; private FakeSubscription mSubscription; public void setControls(List<Control> controls) { mControls = controls; Loading Loading @@ -398,17 +414,35 @@ public class ControlProviderServiceTest { } private Subscription createSubscription(Subscriber s, List<Control> controls) { return new Subscription() { FakeSubscription subscription = new FakeSubscription(s, controls); mSubscription = subscription; return subscription; } } private static final class FakeSubscription implements Subscription { private final Subscriber mSubscriber; private final List<Control> mControls; private boolean mIsCancelled = false; FakeSubscription(Subscriber s, List<Control> controls) { mSubscriber = s; mControls = controls; } public void request(long n) { int i = 0; for (Control c : mControls) { if (i++ < n) s.onNext(c); if (i++ < n) mSubscriber.onNext(c); else break; } s.onComplete(); mSubscriber.onComplete(); } public void cancel() {} }; public void cancel() { mIsCancelled = true; } } }