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     /** @return The pid of the started service. */
startService(int code)161     private int startService(int code) {
162         return waitForServiceStarted(
163                 () -> mContext.startService(mServiceIntent.putExtra(EXTRA_START_CODE, code)));
164     }
165 
166     /** @return The pid of the started service. */
waitForServiceStarted(Runnable serviceTrigger)167     private int waitForServiceStarted(Runnable serviceTrigger) {
168         final CompletableFuture<Integer> pidResult = new CompletableFuture<>();
169         mContext.registerReceiver(new BroadcastReceiver() {
170             @Override
171             public void onReceive(Context context, Intent intent) {
172                 pidResult.complete(intent.getIntExtra(EXTRA_PID, NOT_STARTED));
173                 mContext.unregisterReceiver(this);
174             }
175         }, new IntentFilter(ACTION_SERVICE_STARTED), Context.RECEIVER_EXPORTED_UNAUDITED);
176 
177         serviceTrigger.run();
178         try {
179             return pidResult.get(TIMEOUT_SEC, TimeUnit.SECONDS);
180         } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
181         }
182         return NOT_STARTED;
183     }
184 
185     /** @return The pid of the bound service. */
bindService(int flags)186     private int bindService(int flags) {
187         mCurrentConnection = new TestConnection();
188         assertThat(mContext.bindService(mServiceIntent, mCurrentConnection, flags), is(true));
189         return mCurrentConnection.takePid();
190     }
191 
192     private static class TestConnection implements ServiceConnection {
193         private CompletableFuture<Integer> mServicePid = new CompletableFuture<>();
194 
195         /**
196          * @return The pid of the connected service. It is only valid once after
197          *         {@link #onServiceConnected} is called.
198          */
takePid()199         int takePid() {
200             try {
201                 return mServicePid.get(TIMEOUT_SEC, TimeUnit.SECONDS);
202             } catch (ExecutionException | InterruptedException | TimeoutException ignored) {
203             } finally {
204                 mServicePid = new CompletableFuture<>();
205             }
206             return NOT_STARTED;
207         }
208 
209         @Override
onServiceConnected(ComponentName name, IBinder service)210         public void onServiceConnected(ComponentName name, IBinder service) {
211             final Parcel data = Parcel.obtain();
212             final Parcel reply = Parcel.obtain();
213             data.writeInterfaceToken(RemoteService.DESCRIPTOR);
214             try {
215                 service.transact(RemoteService.TRANSACTION_GET_PID, data, reply, 0 /* flags */);
216                 reply.readException();
217                 mServicePid.complete(reply.readInt());
218             } catch (RemoteException e) {
219                 mServicePid.complete(NOT_STARTED);
220             } finally {
221                 data.recycle();
222                 reply.recycle();
223             }
224         }
225 
226         @Override
onServiceDisconnected(ComponentName name)227         public void onServiceDisconnected(ComponentName name) {
228         }
229     }
230 
231     public static class RemoteService extends Service {
232         static final String DESCRIPTOR = RemoteService.class.getName();
233         static final int TRANSACTION_GET_PID = Binder.FIRST_CALL_TRANSACTION;
234 
235         @Override
onStartCommand(Intent intent, int flags, int startId)236         public int onStartCommand(Intent intent, int flags, int startId) {
237             new Handler().post(() -> {
238                 final Intent responseIntent = new Intent(ACTION_SERVICE_STARTED);
239                 responseIntent.putExtra(EXTRA_PID, Process.myPid());
240                 sendBroadcast(responseIntent);
241             });
242             if (intent != null && intent.hasExtra(EXTRA_START_CODE)) {
243                 return intent.getIntExtra(EXTRA_START_CODE, Service.START_NOT_STICKY);
244             }
245             return super.onStartCommand(intent, flags, startId);
246         }
247 
248         @Override
onBind(Intent intent)249         public IBinder onBind(Intent intent) {
250             return new Binder() {
251                 @Override
252                 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
253                         throws RemoteException {
254                     if (code == TRANSACTION_GET_PID) {
255                         data.enforceInterface(DESCRIPTOR);
256                         reply.writeNoException();
257                         reply.writeInt(Process.myPid());
258                         return true;
259                     }
260                     return super.onTransact(code, data, reply, flags);
261                 }
262             };
263         }
264     }
265 }
266