1 /* 2 * Copyright (C) 2018 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.jobscheduler.cts.shareduidtests; 18 19 import android.annotation.TargetApi; 20 import android.app.job.JobInfo; 21 import android.app.job.JobWorkItem; 22 import android.content.ContentProviderClient; 23 import android.content.Intent; 24 import android.jobscheduler.MockJobService.TestWorkItem; 25 import android.net.Uri; 26 27 import com.android.compatibility.common.util.SystemUtil; 28 29 import java.util.ArrayList; 30 31 /** 32 * Schedules jobs with the {@link android.app.job.JobScheduler} by enqueue work in to 33 * them and processing it. 34 */ 35 @TargetApi(26) 36 public class EnqueueJobWorkTest extends ConstraintTest { 37 private static final String TAG = "ClipDataJobTest"; 38 39 /** Unique identifier for the job scheduled by this suite of tests. */ 40 public static final int ENQUEUE_WORK_JOB_ID = EnqueueJobWorkTest.class.hashCode(); 41 42 private JobInfo.Builder mBuilder; 43 private ContentProviderClient mProvider; 44 45 @Override setUp()46 public void setUp() throws Exception { 47 super.setUp(); 48 49 mBuilder = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent); 50 mProvider = getContext().getContentResolver().acquireContentProviderClient(mFirstUri); 51 } 52 53 @Override tearDown()54 public void tearDown() throws Exception { 55 super.tearDown(); 56 mProvider.close(); 57 mJobScheduler.cancel(ENQUEUE_WORK_JOB_ID); 58 } 59 intentEquals(Intent i1, Intent i2)60 private boolean intentEquals(Intent i1, Intent i2) { 61 if (i1 == i2) { 62 return true; 63 } 64 if (i1 == null || i2 == null) { 65 return false; 66 } 67 return i1.filterEquals(i2); 68 } 69 compareWork(TestWorkItem[] expected, ArrayList<JobWorkItem> received)70 private void compareWork(TestWorkItem[] expected, ArrayList<JobWorkItem> received) { 71 if (received == null) { 72 fail("Didn't receive any expected work."); 73 } 74 ArrayList<TestWorkItem> expectedArray = new ArrayList<>(); 75 for (int i = 0; i < expected.length; i++) { 76 expectedArray.add(expected[i]); 77 } 78 for (int i = 0; i < received.size(); i++) { 79 JobWorkItem work = received.get(i); 80 if (i < expected.length && expected[i].subitems != null) { 81 TestWorkItem[] sub = expected[i].subitems; 82 for (int j = 0; j < sub.length; j++) { 83 expectedArray.add(sub[j]); 84 } 85 } 86 if (i >= expectedArray.size()) { 87 fail("Received more than " + expected.length + " work items, first extra is " 88 + work); 89 } 90 if (!intentEquals(work.getIntent(), expectedArray.get(i).intent)) { 91 fail("Received work #" + i + " " + work.getIntent() + " but expected " + expected[i]); 92 } 93 if (work.getDeliveryCount() != expectedArray.get(i).deliveryCount) { 94 fail("Received work #" + i + " " + work.getIntent() + " delivery count is " 95 + work.getDeliveryCount() + " but expected " 96 + expectedArray.get(i).deliveryCount); 97 } 98 } 99 if (received.size() < expected.length) { 100 fail("Received only " + received.size() + " work items, but expected " 101 + expected.length); 102 } 103 } 104 105 /** 106 * Test basic enqueueing of work. 107 */ testEnqueueOneWork()108 public void testEnqueueOneWork() throws Exception { 109 Intent work1 = new Intent("work1"); 110 TestWorkItem[] work = new TestWorkItem[] { new TestWorkItem(work1) }; 111 kTestEnvironment.setExpectedExecutions(1); 112 kTestEnvironment.setExpectedWork(work); 113 mJobScheduler.enqueue(mBuilder.setOverrideDeadline(0).build(), new JobWorkItem(work1)); 114 kTestEnvironment.readyToWork(); 115 assertTrue("Job with work enqueued did not fire.", 116 kTestEnvironment.awaitExecution()); 117 compareWork(work, kTestEnvironment.getLastReceivedWork()); 118 if (kTestEnvironment.getLastErrorMessage() != null) { 119 fail(kTestEnvironment.getLastErrorMessage()); 120 } 121 } 122 123 /** 124 * Test basic enqueueing batches of work. 125 */ testEnqueueMultipleWork()126 public void testEnqueueMultipleWork() throws Exception { 127 Intent work1 = new Intent("work1"); 128 Intent work2 = new Intent("work2"); 129 Intent work3 = new Intent("work3"); 130 Intent work4 = new Intent("work4"); 131 Intent work5 = new Intent("work5"); 132 Intent work6 = new Intent("work6"); 133 Intent work7 = new Intent("work7"); 134 Intent work8 = new Intent("work8"); 135 TestWorkItem[] work = new TestWorkItem[] { 136 new TestWorkItem(work1), new TestWorkItem(work2), new TestWorkItem(work3), 137 new TestWorkItem(work4), new TestWorkItem(work5), new TestWorkItem(work6), 138 new TestWorkItem(work7), new TestWorkItem(work8) }; 139 kTestEnvironment.setExpectedExecutions(1); 140 kTestEnvironment.setExpectedWork(work); 141 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 142 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 143 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 144 mJobScheduler.enqueue(ji, new JobWorkItem(work3)); 145 mJobScheduler.enqueue(ji, new JobWorkItem(work4)); 146 mJobScheduler.enqueue(ji, new JobWorkItem(work5)); 147 mJobScheduler.enqueue(ji, new JobWorkItem(work6)); 148 mJobScheduler.enqueue(ji, new JobWorkItem(work7)); 149 mJobScheduler.enqueue(ji, new JobWorkItem(work8)); 150 kTestEnvironment.readyToWork(); 151 assertTrue("Job with work enqueued did not fire.", 152 kTestEnvironment.awaitExecution()); 153 compareWork(work, kTestEnvironment.getLastReceivedWork()); 154 } 155 156 /** 157 * Test basic enqueueing batches of work, with new work coming in while processing existing 158 * work. 159 */ testEnqueueMultipleSubWork()160 public void testEnqueueMultipleSubWork() throws Exception { 161 Intent work1 = new Intent("work1"); 162 Intent work2 = new Intent("work2"); 163 Intent work3 = new Intent("work3"); 164 Intent work4 = new Intent("work4"); 165 Intent work5 = new Intent("work5"); 166 Intent work6 = new Intent("work6"); 167 Intent work7 = new Intent("work7"); 168 Intent work8 = new Intent("work8"); 169 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 170 TestWorkItem[] work = new TestWorkItem[]{ 171 new TestWorkItem(work1), new TestWorkItem(work2), new TestWorkItem(work3), 172 new TestWorkItem(work4, ji, new TestWorkItem[] { 173 new TestWorkItem(work5), new TestWorkItem(work6), 174 new TestWorkItem(work7), new TestWorkItem(work8)}) 175 }; 176 kTestEnvironment.setExpectedExecutions(1); 177 kTestEnvironment.setExpectedWork(work); 178 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 179 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 180 mJobScheduler.enqueue(ji, new JobWorkItem(work3)); 181 mJobScheduler.enqueue(ji, new JobWorkItem(work4)); 182 kTestEnvironment.readyToWork(); 183 assertTrue("Job with work enqueued did not fire.", 184 kTestEnvironment.awaitExecution()); 185 compareWork(work, kTestEnvironment.getLastReceivedWork()); 186 } 187 188 /** 189 * Test that continuing to enqueue work after changing the job's constraints 190 * properly retains any already-enqueued work. 191 */ testEnqueuedWorkNewConstraints()192 public void testEnqueuedWorkNewConstraints() throws Exception { 193 Intent work1 = new Intent("work1"); 194 Intent work2 = new Intent("work2"); 195 TestWorkItem[] work = new TestWorkItem[] { 196 new TestWorkItem(work1), 197 new TestWorkItem(work2) 198 }; 199 200 kTestEnvironment.setExpectedExecutions(1); 201 kTestEnvironment.setExpectedWork(work); 202 203 // enqueue work under one set of constraints 204 JobInfo ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent) 205 .setMinimumLatency(5000L) 206 .build(); 207 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 208 209 // now enqueue more work and also change the job's constraints 210 ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent) 211 .setOverrideDeadline(0) 212 .build(); 213 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 214 215 kTestEnvironment.readyToWork(); 216 assertTrue("Job with work enqueued did not start", 217 kTestEnvironment.awaitExecution()); 218 compareWork(work, kTestEnvironment.getLastReceivedWork()); 219 } 220 221 /** 222 * Test basic enqueueing batches of work that will be executed in parallel. 223 */ testEnqueueParallel2Work()224 public void testEnqueueParallel2Work() throws Exception { 225 Intent work1 = new Intent("work1"); 226 Intent work2 = new Intent("work2"); 227 TestWorkItem[] work = new TestWorkItem[] { 228 new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK), 229 new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK) }; 230 kTestEnvironment.setExpectedExecutions(1); 231 kTestEnvironment.setExpectedWork(work); 232 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 233 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 234 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 235 kTestEnvironment.readyToWork(); 236 assertTrue("Job with work enqueued did not fire.", 237 kTestEnvironment.awaitExecution()); 238 compareWork(work, kTestEnvironment.getLastReceivedWork()); 239 } 240 241 /** 242 * Test basic enqueueing batches of work that will be executed in parallel and completed 243 * in reverse order. 244 */ testEnqueueParallel2ReverseWork()245 public void testEnqueueParallel2ReverseWork() throws Exception { 246 Intent work1 = new Intent("work1"); 247 Intent work2 = new Intent("work2"); 248 TestWorkItem[] work = new TestWorkItem[] { 249 new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK), 250 new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP) }; 251 kTestEnvironment.setExpectedExecutions(1); 252 kTestEnvironment.setExpectedWork(work); 253 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 254 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 255 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 256 kTestEnvironment.readyToWork(); 257 assertTrue("Job with work enqueued did not fire.", 258 kTestEnvironment.awaitExecution()); 259 compareWork(work, kTestEnvironment.getLastReceivedWork()); 260 } 261 262 /** 263 * Test basic enqueueing batches of work that will be executed in parallel. 264 */ testEnqueueMultipleParallelWork()265 public void testEnqueueMultipleParallelWork() throws Exception { 266 Intent work1 = new Intent("work1"); 267 Intent work2 = new Intent("work2"); 268 Intent work3 = new Intent("work3"); 269 Intent work4 = new Intent("work4"); 270 Intent work5 = new Intent("work5"); 271 Intent work6 = new Intent("work6"); 272 Intent work7 = new Intent("work7"); 273 Intent work8 = new Intent("work8"); 274 TestWorkItem[] work = new TestWorkItem[] { 275 new TestWorkItem(work1, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK), 276 new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP), 277 new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK 278 | TestWorkItem.FLAG_COMPLETE_NEXT), 279 new TestWorkItem(work4, TestWorkItem.FLAG_COMPLETE_NEXT), 280 new TestWorkItem(work5, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP), 281 new TestWorkItem(work6, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP), 282 new TestWorkItem(work7, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_TOP 283 | TestWorkItem.FLAG_COMPLETE_NEXT), 284 new TestWorkItem(work8) }; 285 kTestEnvironment.setExpectedExecutions(1); 286 kTestEnvironment.setExpectedWork(work); 287 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 288 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 289 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 290 mJobScheduler.enqueue(ji, new JobWorkItem(work3)); 291 mJobScheduler.enqueue(ji, new JobWorkItem(work4)); 292 mJobScheduler.enqueue(ji, new JobWorkItem(work5)); 293 mJobScheduler.enqueue(ji, new JobWorkItem(work6)); 294 mJobScheduler.enqueue(ji, new JobWorkItem(work7)); 295 mJobScheduler.enqueue(ji, new JobWorkItem(work8)); 296 kTestEnvironment.readyToWork(); 297 assertTrue("Job with work enqueued did not fire.", 298 kTestEnvironment.awaitExecution()); 299 compareWork(work, kTestEnvironment.getLastReceivedWork()); 300 } 301 302 /** 303 * Test job getting stopped while processing work and that work being redelivered. 304 */ testEnqueueMultipleRedeliver()305 public void testEnqueueMultipleRedeliver() throws Exception { 306 Intent work1 = new Intent("work1"); 307 Intent work2 = new Intent("work2"); 308 Intent work3 = new Intent("work3"); 309 Intent work4 = new Intent("work4"); 310 Intent work5 = new Intent("work5"); 311 Intent work6 = new Intent("work6"); 312 TestWorkItem[] initialWork = new TestWorkItem[] { 313 new TestWorkItem(work1), new TestWorkItem(work2), new TestWorkItem(work3), 314 new TestWorkItem(work4, TestWorkItem.FLAG_WAIT_FOR_STOP, 1) }; 315 kTestEnvironment.setExpectedExecutions(1); 316 kTestEnvironment.setExpectedWaitForStop(); 317 kTestEnvironment.setExpectedWork(initialWork); 318 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 319 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 320 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 321 mJobScheduler.enqueue(ji, new JobWorkItem(work3)); 322 mJobScheduler.enqueue(ji, new JobWorkItem(work4)); 323 kTestEnvironment.readyToWork(); 324 325 // Now wait for the job to get to the point where it is processing the last 326 // work and waiting for it to be stopped. 327 assertTrue("Job with work enqueued did not wait to stop.", 328 kTestEnvironment.awaitWaitingForStop()); 329 330 // Cause the job to timeout (stop) immediately, and wait for its execution to finish. 331 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler timeout " 332 + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID); 333 assertTrue("Job with work enqueued did not finish.", 334 kTestEnvironment.awaitExecution()); 335 compareWork(initialWork, kTestEnvironment.getLastReceivedWork()); 336 337 // Now we are going to add some more work, restart the job, and see if it correctly 338 // redelivers the last work and delivers the new work. 339 TestWorkItem[] finalWork = new TestWorkItem[] { 340 new TestWorkItem(work4, 0, 2), new TestWorkItem(work5), new TestWorkItem(work6) }; 341 kTestEnvironment.setExpectedExecutions(1); 342 kTestEnvironment.setExpectedWork(finalWork); 343 mJobScheduler.enqueue(ji, new JobWorkItem(work5)); 344 mJobScheduler.enqueue(ji, new JobWorkItem(work6)); 345 kTestEnvironment.readyToWork(); 346 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run " 347 + " --user " + getCurrentUser() + " " 348 + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID); 349 350 assertTrue("Restarted with work enqueued did not execute.", 351 kTestEnvironment.awaitExecution()); 352 compareWork(finalWork, kTestEnvironment.getLastReceivedWork()); 353 } 354 355 /** 356 * Test job getting stopped while processing work in parallel and that work being redelivered. 357 */ testEnqueueMultipleParallelRedeliver()358 public void testEnqueueMultipleParallelRedeliver() throws Exception { 359 Intent work1 = new Intent("work1"); 360 Intent work2 = new Intent("work2"); 361 Intent work3 = new Intent("work3"); 362 Intent work4 = new Intent("work4"); 363 Intent work5 = new Intent("work5"); 364 Intent work6 = new Intent("work6"); 365 TestWorkItem[] initialWork = new TestWorkItem[] { 366 new TestWorkItem(work1), 367 new TestWorkItem(work2, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK), 368 new TestWorkItem(work3, TestWorkItem.FLAG_DELAY_COMPLETE_PUSH_BACK), 369 new TestWorkItem(work4, TestWorkItem.FLAG_WAIT_FOR_STOP, 1) }; 370 kTestEnvironment.setExpectedExecutions(1); 371 kTestEnvironment.setExpectedWaitForStop(); 372 kTestEnvironment.setExpectedWork(initialWork); 373 JobInfo ji = mBuilder.setOverrideDeadline(0).build(); 374 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 375 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 376 mJobScheduler.enqueue(ji, new JobWorkItem(work3)); 377 mJobScheduler.enqueue(ji, new JobWorkItem(work4)); 378 kTestEnvironment.readyToWork(); 379 380 // Now wait for the job to get to the point where it is processing the last 381 // work and waiting for it to be stopped. 382 assertTrue("Job with work enqueued did not wait to stop.", 383 kTestEnvironment.awaitWaitingForStop()); 384 385 // Cause the job to timeout (stop) immediately, and wait for its execution to finish. 386 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler timeout " 387 + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID); 388 assertTrue("Job with work enqueued did not finish.", 389 kTestEnvironment.awaitExecution()); 390 compareWork(initialWork, kTestEnvironment.getLastReceivedWork()); 391 392 // Now we are going to add some more work, restart the job, and see if it correctly 393 // redelivers the last work and delivers the new work. 394 TestWorkItem[] finalWork = new TestWorkItem[] { 395 new TestWorkItem(work2, 0, 2), new TestWorkItem(work3, 0, 2), 396 new TestWorkItem(work4, 0, 2), new TestWorkItem(work5), new TestWorkItem(work6) }; 397 kTestEnvironment.setExpectedExecutions(1); 398 kTestEnvironment.setExpectedWork(finalWork); 399 mJobScheduler.enqueue(ji, new JobWorkItem(work5)); 400 mJobScheduler.enqueue(ji, new JobWorkItem(work6)); 401 kTestEnvironment.readyToWork(); 402 SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler run " 403 + " --user " + getCurrentUser() + " " 404 + kJobServiceComponent.getPackageName() + " " + ENQUEUE_WORK_JOB_ID); 405 406 assertTrue("Restarted with work enqueued did not execute.", 407 kTestEnvironment.awaitExecution()); 408 compareWork(finalWork, kTestEnvironment.getLastReceivedWork()); 409 } 410 411 /** 412 * Test basic enqueueing batches of work. 413 */ testEnqueueMultipleUriGrantWork()414 public void testEnqueueMultipleUriGrantWork() throws Exception { 415 // Start out with storage low, so job is enqueued but not executed yet. 416 setStorageState(true); 417 418 // We need to get a permission grant so that we can grant it to ourself. 419 mProvider.call("grant", MY_PACKAGE, mFirstUriBundle); 420 mProvider.call("grant", MY_PACKAGE, mSecondUriBundle); 421 assertHasUriPermission(mFirstUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 422 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 423 assertHasUriPermission(mSecondUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 424 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 425 426 Intent work1 = new Intent("work1"); 427 work1.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 428 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 429 work1.setData(mFirstUri); 430 work1.setClipData(mSecondClipData); 431 432 Intent work2 = new Intent("work2"); 433 work2.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION 434 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 435 work2.setData(mFirstUri); 436 437 TestWorkItem[] work = new TestWorkItem[] { 438 new TestWorkItem(work1, new Uri[] { mFirstUri, mSecondUri}, new Uri[0]), 439 new TestWorkItem(work2, new Uri[] { mFirstUri }, new Uri[] { mSecondUri}) }; 440 kTestEnvironment.setExpectedExecutions(1); 441 kTestEnvironment.setExpectedWork(work); 442 JobInfo ji = mBuilder.setOverrideDeadline(0).setRequiresStorageNotLow(true).build(); 443 mJobScheduler.enqueue(ji, new JobWorkItem(work1)); 444 mJobScheduler.enqueue(ji, new JobWorkItem(work2)); 445 446 // Remove the explicit grant, we should still have a grant due to the job. 447 mProvider.call("revoke", MY_PACKAGE, mFirstUriBundle); 448 mProvider.call("revoke", MY_PACKAGE, mSecondUriBundle); 449 assertHasUriPermission(mFirstUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 450 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 451 assertHasUriPermission(mSecondUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 452 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 453 454 kTestEnvironment.readyToWork(); 455 456 // Now allow the job to run. 457 setStorageState(false); 458 459 assertTrue("Job with work enqueued did not fire.", 460 kTestEnvironment.awaitExecution()); 461 compareWork(work, kTestEnvironment.getLastReceivedWork()); 462 463 // And wait for everything to be cleaned up. 464 waitPermissionRevoke(mFirstUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, 5000); 465 waitPermissionRevoke(mSecondUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, 5000); 466 } 467 getCurrentUser()468 private static int getCurrentUser() { 469 return android.os.Process.myUserHandle().getIdentifier(); 470 } 471 } 472