• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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