• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.activity;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static org.hamcrest.MatcherAssert.assertThat;
22 import static org.hamcrest.Matchers.notNullValue;
23 import static org.hamcrest.core.Is.is;
24 import static org.hamcrest.core.IsNot.not;
25 
26 import android.app.ActivityManager;
27 import android.app.ActivityManager.RunningServiceInfo;
28 import android.app.Service;
29 import android.content.BroadcastReceiver;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.content.ServiceConnection;
35 import android.os.Binder;
36 import android.os.Handler;
37 import android.os.IBinder;
38 import android.os.Parcel;
39 import android.os.Process;
40 import android.os.RemoteException;
41 
42 import androidx.test.filters.MediumTest;
43 
44 import junit.framework.TestCase;
45 
46 import org.junit.Test;
47 
48 import java.util.concurrent.CompletableFuture;
49 import java.util.concurrent.ExecutionException;
50 import java.util.concurrent.TimeUnit;
51 import java.util.concurrent.TimeoutException;
52 import java.util.function.Supplier;
53 
54 /**
55  * Test for verifying the behavior of {@link Service}.
56  * <p>
57  * Tests related to internal behavior are usually placed here, e.g. the restart delay may be
58  * different depending on the current amount of restarting services.
59  * <p>
60  * Build/Install/Run:
61  *  atest FrameworksCoreTests:ServiceTest
62  */
63 @MediumTest
64 public class ServiceTest extends TestCase {
65     private static final String ACTION_SERVICE_STARTED = RemoteService.class.getName() + "_STARTED";
66     private static final String EXTRA_START_CODE = "start_code";
67     private static final String EXTRA_PID = "pid";
68 
69     private static final long TIMEOUT_SEC = 5;
70     private static final int NOT_STARTED = -1;
71 
72     private final Context mContext = getInstrumentation().getContext();
73     private final Intent mServiceIntent = new Intent(mContext, RemoteService.class);
74     private TestConnection mCurrentConnection;
75 
76     @Override
tearDown()77     public void tearDown() {
78         mContext.stopService(mServiceIntent);
79         if (mCurrentConnection != null) {
80             mContext.unbindService(mCurrentConnection);
81             mCurrentConnection = null;
82         }
83     }
84 
85     @Test
testRestart_stickyStartedService_restarted()86     public void testRestart_stickyStartedService_restarted() {
87         testRestartStartedService(Service.START_STICKY, true /* shouldRestart */);
88     }
89 
90     @Test
testRestart_redeliveryStartedService_restarted()91     public void testRestart_redeliveryStartedService_restarted() {
92         testRestartStartedService(Service.START_FLAG_REDELIVERY, true /* shouldRestart */);
93     }
94 
95     @Test
testRestart_notStickyStartedService_notRestarted()96     public void testRestart_notStickyStartedService_notRestarted() {
97         testRestartStartedService(Service.START_NOT_STICKY, false /* shouldRestart */);
98     }
99 
testRestartStartedService(int startFlag, boolean shouldRestart)100     private void testRestartStartedService(int startFlag, boolean shouldRestart) {
101         final int servicePid = startService(startFlag);
102         assertThat(servicePid, not(NOT_STARTED));
103 
104         final int restartedServicePid = waitForServiceStarted(
105                 () -> Process.killProcess(servicePid));
106         assertThat(restartedServicePid, shouldRestart ? not(NOT_STARTED) : is(NOT_STARTED));
107     }
108 
109     @Test
testRestart_boundService_restarted()110     public void testRestart_boundService_restarted() {
111         final int servicePid = bindService(Context.BIND_AUTO_CREATE);
112         assertThat(servicePid, not(NOT_STARTED));
113 
114         Process.killProcess(servicePid);
115         // The service should be restarted and the connection will receive onServiceConnected again.
116         assertThat(mCurrentConnection.takePid(), not(NOT_STARTED));
117     }
118 
119     @Test
testRestart_boundNotStickyStartedService_restarted()120     public void testRestart_boundNotStickyStartedService_restarted() {
121         final ActivityManager am = mContext.getSystemService(ActivityManager.class);
122         final Supplier<RunningServiceInfo> serviceInfoGetter = () -> {
123             for (RunningServiceInfo rs : am.getRunningServices(Integer.MAX_VALUE)) {
124                 if (mServiceIntent.getComponent().equals(rs.service)) {
125                     return rs;
126                 }
127             }
128             return null;
129         };
130 
131         final int servicePid = bindService(Context.BIND_AUTO_CREATE);
132         assertThat(servicePid, not(NOT_STARTED));
133         assertThat(startService(Service.START_NOT_STICKY), is(servicePid));
134 
135         RunningServiceInfo info = serviceInfoGetter.get();
136         assertThat(info, notNullValue());
137         assertThat(info.started, is(true));
138 
139         Process.killProcess(servicePid);
140         // The service will be restarted for connection but the started state should be gone.
141         final int restartedServicePid = mCurrentConnection.takePid();
142         assertThat(restartedServicePid, not(NOT_STARTED));
143 
144         info = serviceInfoGetter.get();
145         assertThat(info, notNullValue());
146         assertThat(info.started, is(false));
147         assertThat(info.clientCount, is(1));
148     }
149 
150     @Test
testRestart_notStickyStartedNoAutoCreateBoundService_notRestarted()151     public void testRestart_notStickyStartedNoAutoCreateBoundService_notRestarted() {
152         final int servicePid = startService(Service.START_NOT_STICKY);
153         assertThat(servicePid, not(NOT_STARTED));
154         assertThat(bindService(0 /* flags */), is(servicePid));
155 
156         Process.killProcess(servicePid);
157         assertThat(mCurrentConnection.takePid(), is(NOT_STARTED));
158     }
159 
160     @Test
testRestart_stickyStartedService_unbindHappenedAfterRestart_restarted()161     public void testRestart_stickyStartedService_unbindHappenedAfterRestart_restarted() {
162         final int servicePid = startService(Service.START_STICKY);
163         assertThat(servicePid, not(NOT_STARTED));
164         assertThat(bindService(0 /* flags */), is(servicePid));
165 
166         final int restartedServicePid = waitForServiceStarted(
167                 () -> {
168                     Process.killProcess(servicePid);
169                     mContext.unbindService(mCurrentConnection);
170                     mCurrentConnection = null;
171                 });
172         assertThat(restartedServicePid, not(NOT_STARTED));
173     }
174 
175     /** @return The pid of the started service. */
startService(int code)176     private int startService(int code) {
177         return waitForServiceStarted(
178                 () -> mContext.startService(mServiceIntent.putExtra(EXTRA_START_CODE, code)));
179     }
180 
181     /** @return The pid of the started service. */
waitForServiceStarted(Runnable serviceTrigger)182     private int waitForServiceStarted(Runnable serviceTrigger) {
183         final CompletableFuture<Integer> pidResult = new CompletableFuture<>();
184         mContext.registerReceiver(new BroadcastReceiver() {
185             @Override
186             public void onReceive(Context context, Intent intent) {
187                 pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
188                 mContext.unregisterReceiver(this);
189             }
190         }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
191 
192         serviceTrigger.run();
193         try {
194             return pidResult.get(TIMEOUT_SEC, TimeUnit.SECONDS);
195         } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
196         }
197         return NOT_STARTED;
198     }
199 
200     /** @return The pid of the bound service. */
bindService(int flags)201     private int bindService(int flags) {
202         mCurrentConnection = new TestConnection();
203         assertThat(mContext.bindService(mServiceIntent, mCurrentConnection, flags), is(true));
204         return mCurrentConnection.takePid();
205     }
206 
207     private static class TestConnection implements ServiceConnection {
208         private CompletableFuture<Integer> mServicePid = new CompletableFuture<>();
209 
210         /**
211          * @return The pid of the connected service. It is only valid once after
212          *         {@link #onServiceConnected} is called.
213          */
takePid()214         int takePid() {
215             try {
216                 return mServicePid.get(TIMEOUT_SEC, TimeUnit.SECONDS);
217             } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
218             } finally {
219                 mServicePid = new CompletableFuture<>();
220             }
221             return NOT_STARTED;
222         }
223 
224         @Override
onServiceConnected(ComponentName name, IBinder service)225         public void onServiceConnected(ComponentName name, IBinder service) {
226             final Parcel data = Parcel.obtain();
227             final Parcel reply = Parcel.obtain();
228             data.writeInterfaceToken(RemoteService.DESCRIPTOR);
229             try {
230                 service.transact(RemoteService.TRANSACTION_GET_PID, data, reply, 0 /* flags */);
231                 reply.readException();
232                 mServicePid.complete(reply.readInt());
233             } catch (RemoteException e) {
234                 mServicePid.complete(NOT_STARTED);
235             } finally {
236                 data.recycle();
237                 reply.recycle();
238             }
239         }
240 
241         @Override
onServiceDisconnected(ComponentName name)242         public void onServiceDisconnected(ComponentName name) {
243         }
244     }
245 
246     public static class RemoteService extends Service {
247         static final String DESCRIPTOR = RemoteService.class.getName();
248         static final int TRANSACTION_GET_PID = Binder.FIRST_CALL_TRANSACTION;
249 
250         @Override
onStartCommand(Intent intent, int flags, int startId)251         public int onStartCommand(Intent intent, int flags, int startId) {
252             new Handler().post(() -> {
253                 final Intent responseIntent = new Intent(ACTION_SERVICE_STARTED);
254                 responseIntent.putExtra(EXTRA_PID, Process.myPid());
255                 sendBroadcast(responseIntent);
256             });
257             if (intent != null && intent.hasExtra(EXTRA_START_CODE)) {
258                 return intent.getIntExtra(EXTRA_START_CODE, Service.START_NOT_STICKY);
259             }
260             return super.onStartCommand(intent, flags, startId);
261         }
262 
263         @Override
onBind(Intent intent)264         public IBinder onBind(Intent intent) {
265             return new Binder() {
266                 @Override
267                 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
268                         throws RemoteException {
269                     if (code == TRANSACTION_GET_PID) {
270                         data.enforceInterface(DESCRIPTOR);
271                         reply.writeNoException();
272                         reply.writeInt(Process.myPid());
273                         return true;
274                     }
275                     return super.onTransact(code, data, reply, flags);
276                 }
277             };
278         }
279     }
280 }
281