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