• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.os.bugreports.tests;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.Manifest;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.os.BugreportManager;
31 import android.os.BugreportManager.BugreportCallback;
32 import android.os.BugreportParams;
33 import android.os.FileUtils;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.ParcelFileDescriptor;
37 import android.os.Process;
38 import android.os.StrictMode;
39 import android.text.TextUtils;
40 import android.util.Log;
41 
42 import androidx.annotation.NonNull;
43 import androidx.test.InstrumentationRegistry;
44 import androidx.test.filters.LargeTest;
45 import androidx.test.uiautomator.By;
46 import androidx.test.uiautomator.BySelector;
47 import androidx.test.uiautomator.UiDevice;
48 import androidx.test.uiautomator.UiObject2;
49 import androidx.test.uiautomator.Until;
50 
51 import org.junit.After;
52 import org.junit.Before;
53 import org.junit.Rule;
54 import org.junit.Test;
55 import org.junit.rules.ExternalResource;
56 import org.junit.rules.TestName;
57 import org.junit.runner.RunWith;
58 import org.junit.runners.JUnit4;
59 
60 import java.io.File;
61 import java.io.IOException;
62 import java.util.concurrent.CountDownLatch;
63 import java.util.concurrent.Executor;
64 import java.util.concurrent.TimeUnit;
65 
66 /**
67  * Tests for BugreportManager API.
68  */
69 @RunWith(JUnit4.class)
70 public class BugreportManagerTest {
71     @Rule public TestName name = new TestName();
72     @Rule public ExtendedStrictModeVmPolicy mTemporaryVmPolicy = new ExtendedStrictModeVmPolicy();
73 
74     private static final String TAG = "BugreportManagerTest";
75     private static final long BUGREPORT_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10);
76     private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
77     private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
78 
79 
80     // A small timeout used when waiting for the result of a BugreportCallback to be received.
81     // This value must be at least 1000ms since there is an intentional delay in
82     // BugreportManagerServiceImpl in the error case.
83     private static final long CALLBACK_RESULT_TIMEOUT_MS = 1500;
84 
85     // Sent by Shell when its bugreport finishes (contains final bugreport/screenshot file name
86     // associated with the bugreport).
87     private static final String INTENT_BUGREPORT_FINISHED =
88             "com.android.internal.intent.action.BUGREPORT_FINISHED";
89     private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
90     private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
91 
92     private Handler mHandler;
93     private Executor mExecutor;
94     private BugreportManager mBrm;
95     private File mBugreportFile;
96     private File mScreenshotFile;
97     private ParcelFileDescriptor mBugreportFd;
98     private ParcelFileDescriptor mScreenshotFd;
99 
100     @Before
setup()101     public void setup() throws Exception {
102         mHandler = createHandler();
103         mExecutor = (runnable) -> {
104             if (mHandler != null) {
105                 mHandler.post(() -> {
106                     runnable.run();
107                 });
108             }
109         };
110 
111         mBrm = getBugreportManager();
112         mBugreportFile = createTempFile("bugreport_" + name.getMethodName(), ".zip");
113         mScreenshotFile = createTempFile("screenshot_" + name.getMethodName(), ".png");
114         mBugreportFd = parcelFd(mBugreportFile);
115         mScreenshotFd = parcelFd(mScreenshotFile);
116 
117         getPermissions();
118     }
119 
120     @After
teardown()121     public void teardown() throws Exception {
122         dropPermissions();
123         FileUtils.closeQuietly(mBugreportFd);
124         FileUtils.closeQuietly(mScreenshotFd);
125     }
126 
127 
128     @Test
normalFlow_wifi()129     public void normalFlow_wifi() throws Exception {
130         BugreportCallbackImpl callback = new BugreportCallbackImpl();
131         // wifi bugreport does not take screenshot
132         mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, wifi(),
133                 mExecutor, callback);
134         shareConsentDialog(ConsentReply.ALLOW);
135         waitTillDoneOrTimeout(callback);
136 
137         assertThat(callback.isDone()).isTrue();
138         // Wifi bugreports should not receive any progress.
139         assertThat(callback.hasReceivedProgress()).isFalse();
140         assertThat(mBugreportFile.length()).isGreaterThan(0L);
141         assertThat(callback.hasEarlyReportFinished()).isTrue();
142         assertFdsAreClosed(mBugreportFd);
143     }
144 
145     @LargeTest
146     @Test
normalFlow_interactive()147     public void normalFlow_interactive() throws Exception {
148         BugreportCallbackImpl callback = new BugreportCallbackImpl();
149         // interactive bugreport does not take screenshot
150         mBrm.startBugreport(mBugreportFd, null /*screenshotFd = null*/, interactive(),
151                 mExecutor, callback);
152         shareConsentDialog(ConsentReply.ALLOW);
153         waitTillDoneOrTimeout(callback);
154 
155         assertThat(callback.isDone()).isTrue();
156         // Interactive bugreports show progress updates.
157         assertThat(callback.hasReceivedProgress()).isTrue();
158         assertThat(mBugreportFile.length()).isGreaterThan(0L);
159         assertThat(callback.hasEarlyReportFinished()).isTrue();
160         assertFdsAreClosed(mBugreportFd);
161     }
162 
163     @LargeTest
164     @Test
normalFlow_full()165     public void normalFlow_full() throws Exception {
166         BugreportCallbackImpl callback = new BugreportCallbackImpl();
167         mBrm.startBugreport(mBugreportFd, mScreenshotFd, full(), mExecutor, callback);
168         shareConsentDialog(ConsentReply.ALLOW);
169         waitTillDoneOrTimeout(callback);
170 
171         assertThat(callback.isDone()).isTrue();
172         // bugreport and screenshot files shouldn't be empty when user consents.
173         assertThat(mBugreportFile.length()).isGreaterThan(0L);
174         assertThat(mScreenshotFile.length()).isGreaterThan(0L);
175         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
176     }
177 
178     @Test
simultaneousBugreportsNotAllowed()179     public void simultaneousBugreportsNotAllowed() throws Exception {
180         // Start bugreport #1
181         BugreportCallbackImpl callback = new BugreportCallbackImpl();
182         mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
183         // TODO(b/162389762) Make sure the wait time is reasonable
184         shareConsentDialog(ConsentReply.ALLOW);
185 
186         // Before #1 is done, try to start #2.
187         assertThat(callback.isDone()).isFalse();
188         BugreportCallbackImpl callback2 = new BugreportCallbackImpl();
189         File bugreportFile2 = createTempFile("bugreport_2_" + name.getMethodName(), ".zip");
190         File screenshotFile2 = createTempFile("screenshot_2_" + name.getMethodName(), ".png");
191         ParcelFileDescriptor bugreportFd2 = parcelFd(bugreportFile2);
192         ParcelFileDescriptor screenshotFd2 = parcelFd(screenshotFile2);
193         mBrm.startBugreport(bugreportFd2, screenshotFd2, wifi(), mExecutor, callback2);
194         Thread.sleep(CALLBACK_RESULT_TIMEOUT_MS);
195 
196         // Verify #2 encounters an error.
197         assertThat(callback2.getErrorCode()).isEqualTo(
198                 BugreportCallback.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
199         assertFdsAreClosed(bugreportFd2, screenshotFd2);
200 
201         // Cancel #1 so we can move on to the next test.
202         mBrm.cancelBugreport();
203         waitTillDoneOrTimeout(callback);
204         assertThat(callback.isDone()).isTrue();
205         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
206     }
207 
208     @Test
cancelBugreport()209     public void cancelBugreport() throws Exception {
210         // Start a bugreport.
211         BugreportCallbackImpl callback = new BugreportCallbackImpl();
212         mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
213 
214         // Verify it's not finished yet.
215         assertThat(callback.isDone()).isFalse();
216 
217         // Try to cancel it, but first without DUMP permission.
218         dropPermissions();
219         try {
220             mBrm.cancelBugreport();
221             fail("Expected cancelBugreport to throw SecurityException without DUMP permission");
222         } catch (SecurityException expected) {
223         }
224         assertThat(callback.isDone()).isFalse();
225 
226         // Try again, with DUMP permission.
227         getPermissions();
228         mBrm.cancelBugreport();
229         waitTillDoneOrTimeout(callback);
230         assertThat(callback.isDone()).isTrue();
231         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
232     }
233 
234     @Test
cancelBugreport_noReportStarted()235     public void cancelBugreport_noReportStarted() throws Exception {
236         // Without the native DumpstateService running, we don't get a SecurityException.
237         mBrm.cancelBugreport();
238     }
239 
240     @LargeTest
241     @Test
cancelBugreport_fromDifferentUid()242     public void cancelBugreport_fromDifferentUid() throws Exception {
243         assertThat(Process.myUid()).isNotEqualTo(Process.SHELL_UID);
244 
245         // Start a bugreport through ActivityManager's shell command - this starts a BR from the
246         // shell UID rather than our own.
247         BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
248         InstrumentationRegistry.getContext()
249                 .registerReceiver(br, new IntentFilter(INTENT_BUGREPORT_FINISHED));
250         UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
251                 .executeShellCommand("am bug-report");
252 
253         // The command triggers the report through a broadcast, so wait until dumpstate actually
254         // starts up, which may take a bit.
255         waitTillDumpstateRunningOrTimeout();
256 
257         try {
258             mBrm.cancelBugreport();
259             fail("Expected cancelBugreport to throw SecurityException when report started by "
260                     + "different UID");
261         } catch (SecurityException expected) {
262         } finally {
263             // Do this in the finally block so that even if this test case fails, we don't break
264             // other test cases unexpectedly due to the still-running shell report.
265             try {
266                 // The shell's BR is still running and should complete successfully.
267                 br.waitForBugreportFinished();
268             } finally {
269                 // The latch may fail for a number of reasons but we still need to unregister the
270                 // BroadcastReceiver.
271                 InstrumentationRegistry.getContext().unregisterReceiver(br);
272             }
273         }
274     }
275 
276     @Test
insufficientPermissions_throwsException()277     public void insufficientPermissions_throwsException() throws Exception {
278         dropPermissions();
279 
280         BugreportCallbackImpl callback = new BugreportCallbackImpl();
281         try {
282             mBrm.startBugreport(mBugreportFd, mScreenshotFd, wifi(), mExecutor, callback);
283             fail("Expected startBugreport to throw SecurityException without DUMP permission");
284         } catch (SecurityException expected) {
285         }
286         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
287     }
288 
289     @Test
invalidBugreportMode_throwsException()290     public void invalidBugreportMode_throwsException() throws Exception {
291         BugreportCallbackImpl callback = new BugreportCallbackImpl();
292 
293         try {
294             mBrm.startBugreport(mBugreportFd, mScreenshotFd,
295                     new BugreportParams(25) /* unknown bugreport mode */, mExecutor, callback);
296             fail("Expected to throw IllegalArgumentException with unknown bugreport mode");
297         } catch (IllegalArgumentException expected) {
298         }
299         assertFdsAreClosed(mBugreportFd, mScreenshotFd);
300     }
301 
createHandler()302     private Handler createHandler() {
303         HandlerThread handlerThread = new HandlerThread("BugreportManagerTest");
304         handlerThread.start();
305         return new Handler(handlerThread.getLooper());
306     }
307 
308     /* Implementatiion of {@link BugreportCallback} that offers wrappers around execution result */
309     private static final class BugreportCallbackImpl extends BugreportCallback {
310         private int mErrorCode = -1;
311         private boolean mSuccess = false;
312         private boolean mReceivedProgress = false;
313         private boolean mEarlyReportFinished = false;
314         private final Object mLock = new Object();
315 
316         @Override
onProgress(float progress)317         public void onProgress(float progress) {
318             synchronized (mLock) {
319                 mReceivedProgress = true;
320             }
321         }
322 
323         @Override
onError(int errorCode)324         public void onError(int errorCode) {
325             synchronized (mLock) {
326                 mErrorCode = errorCode;
327                 Log.d(TAG, "bugreport errored.");
328             }
329         }
330 
331         @Override
onFinished()332         public void onFinished() {
333             synchronized (mLock) {
334                 Log.d(TAG, "bugreport finished.");
335                 mSuccess =  true;
336             }
337         }
338 
339         @Override
onEarlyReportFinished()340         public void onEarlyReportFinished() {
341             synchronized (mLock) {
342                 mEarlyReportFinished = true;
343             }
344         }
345 
346         /* Indicates completion; and ended up with a success or error. */
isDone()347         public boolean isDone() {
348             synchronized (mLock) {
349                 return (mErrorCode != -1) || mSuccess;
350             }
351         }
352 
getErrorCode()353         public int getErrorCode() {
354             synchronized (mLock) {
355                 return mErrorCode;
356             }
357         }
358 
isSuccess()359         public boolean isSuccess() {
360             synchronized (mLock) {
361                 return mSuccess;
362             }
363         }
364 
hasReceivedProgress()365         public boolean hasReceivedProgress() {
366             synchronized (mLock) {
367                 return mReceivedProgress;
368             }
369         }
370 
hasEarlyReportFinished()371         public boolean hasEarlyReportFinished() {
372             synchronized (mLock) {
373                 return mEarlyReportFinished;
374             }
375         }
376     }
377 
getBugreportManager()378     public static BugreportManager getBugreportManager() {
379         Context context = InstrumentationRegistry.getContext();
380         BugreportManager bm =
381                 (BugreportManager) context.getSystemService(Context.BUGREPORT_SERVICE);
382         if (bm == null) {
383             throw new AssertionError("Failed to get BugreportManager");
384         }
385         return bm;
386     }
387 
createTempFile(String prefix, String extension)388     private static File createTempFile(String prefix, String extension) throws Exception {
389         final File f = File.createTempFile(prefix, extension);
390         f.setReadable(true, true);
391         f.setWritable(true, true);
392 
393         f.deleteOnExit();
394         return f;
395     }
396 
parcelFd(File file)397     private static ParcelFileDescriptor parcelFd(File file) throws Exception {
398         return ParcelFileDescriptor.open(file,
399                 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
400     }
401 
dropPermissions()402     private static void dropPermissions() {
403         InstrumentationRegistry.getInstrumentation().getUiAutomation()
404                 .dropShellPermissionIdentity();
405     }
406 
getPermissions()407     private static void getPermissions() {
408         InstrumentationRegistry.getInstrumentation().getUiAutomation()
409                 .adoptShellPermissionIdentity(Manifest.permission.DUMP);
410     }
411 
isDumpstateRunning()412     private static boolean isDumpstateRunning() {
413         String[] output;
414         try {
415             output =
416                     UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
417                             .executeShellCommand("ps -A -o NAME | grep dumpstate")
418                             .trim()
419                             .split("\n");
420         } catch (IOException e) {
421             Log.w(TAG, "Failed to check if dumpstate is running", e);
422             return false;
423         }
424         for (String line : output) {
425             // Check for an exact match since there may be other things that contain "dumpstate" as
426             // a substring (e.g. the dumpstate HAL).
427             if (TextUtils.equals("dumpstate", line)) {
428                 return true;
429             }
430         }
431         return false;
432     }
433 
assertFdIsClosed(ParcelFileDescriptor pfd)434     private static void assertFdIsClosed(ParcelFileDescriptor pfd) {
435         try {
436             int fd = pfd.getFd();
437             fail("Expected ParcelFileDescriptor argument to be closed, but got: " + fd);
438         } catch (IllegalStateException expected) {
439         }
440     }
441 
assertFdsAreClosed(ParcelFileDescriptor... pfds)442     private static void assertFdsAreClosed(ParcelFileDescriptor... pfds) {
443         for (int i = 0; i <  pfds.length; i++) {
444             assertFdIsClosed(pfds[i]);
445         }
446     }
447 
now()448     private static long now() {
449         return System.currentTimeMillis();
450     }
451 
waitTillDumpstateRunningOrTimeout()452     private static void waitTillDumpstateRunningOrTimeout() throws Exception {
453         long startTimeMs = now();
454         while (!isDumpstateRunning()) {
455             Thread.sleep(500 /* .5s */);
456             if (now() - startTimeMs >= DUMPSTATE_STARTUP_TIMEOUT_MS) {
457                 break;
458             }
459             Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for dumpstate to start");
460         }
461     }
462 
waitTillDoneOrTimeout(BugreportCallbackImpl callback)463     private static void waitTillDoneOrTimeout(BugreportCallbackImpl callback) throws Exception {
464         long startTimeMs = now();
465         while (!callback.isDone()) {
466             Thread.sleep(1000 /* 1s */);
467             if (now() - startTimeMs >= BUGREPORT_TIMEOUT_MS) {
468                 break;
469             }
470             Log.d(TAG, "Waited " + (now() - startTimeMs) + "ms for bugreport to finish");
471         }
472     }
473 
474     /*
475      * Returns a {@link BugreportParams} for wifi only bugreport.
476      *
477      * <p>Wifi bugreports have minimal content and are fast to run. They also suppress progress
478      * updates.
479      */
wifi()480     private static BugreportParams wifi() {
481         return new BugreportParams(BugreportParams.BUGREPORT_MODE_WIFI);
482     }
483 
484     /*
485      * Returns a {@link BugreportParams} for interactive bugreport that offers progress updates.
486      *
487      * <p>This is the typical bugreport taken by users. This can take on the order of minutes to
488      * finish.
489      */
interactive()490     private static BugreportParams interactive() {
491         return new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE);
492     }
493 
494     /*
495      * Returns a {@link BugreportParams} for full bugreport that includes a screenshot.
496      *
497      * <p> This can take on the order of minutes to finish
498      */
full()499     private static BugreportParams full() {
500         return new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
501     }
502 
503     /* Allow/deny the consent dialog to sharing bugreport data or check existence only. */
504     private enum ConsentReply {
505         ALLOW,
506         DENY,
507         TIMEOUT
508     }
509 
510     /*
511      * Ensure the consent dialog is shown and take action according to <code>consentReply<code/>.
512      * It will fail if the dialog is not shown when <code>ignoreNotFound<code/> is false.
513      */
shareConsentDialog(@onNull ConsentReply consentReply)514     private void shareConsentDialog(@NonNull ConsentReply consentReply) throws Exception {
515         mTemporaryVmPolicy.permitIncorrectContextUse();
516         final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
517 
518         // Unlock before finding/clicking an object.
519         device.wakeUp();
520         device.executeShellCommand("wm dismiss-keyguard");
521 
522         final BySelector consentTitleObj = By.res("android", "alertTitle");
523         if (!device.wait(Until.hasObject(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS)) {
524             fail("The consent dialog is not found");
525         }
526         if (consentReply.equals(ConsentReply.TIMEOUT)) {
527             return;
528         }
529         final BySelector selector;
530         if (consentReply.equals(ConsentReply.ALLOW)) {
531             selector = By.res("android", "button1");
532             Log.d(TAG, "Allow the consent dialog");
533         } else { // ConsentReply.DENY
534             selector = By.res("android", "button2");
535             Log.d(TAG, "Deny the consent dialog");
536         }
537         final UiObject2 btnObj = device.findObject(selector);
538         assertNotNull("The button of consent dialog is not found", btnObj);
539         btnObj.click();
540 
541         Log.d(TAG, "Wait for the dialog to be dismissed");
542         assertTrue(device.wait(Until.gone(consentTitleObj), UIAUTOMATOR_TIMEOUT_MS));
543     }
544 
545     private class BugreportBroadcastReceiver extends BroadcastReceiver {
546         Intent mBugreportFinishedIntent = null;
547         final CountDownLatch mLatch;
548 
BugreportBroadcastReceiver()549         BugreportBroadcastReceiver() {
550             mLatch = new CountDownLatch(1);
551         }
552 
553         @Override
onReceive(Context context, Intent intent)554         public void onReceive(Context context, Intent intent) {
555             setBugreportFinishedIntent(intent);
556             mLatch.countDown();
557         }
558 
setBugreportFinishedIntent(Intent intent)559         private void setBugreportFinishedIntent(Intent intent) {
560             mBugreportFinishedIntent = intent;
561         }
562 
getBugreportFinishedIntent()563         public Intent getBugreportFinishedIntent() {
564             return mBugreportFinishedIntent;
565         }
566 
waitForBugreportFinished()567         public void waitForBugreportFinished() throws Exception {
568             if (!mLatch.await(BUGREPORT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
569                 throw new Exception("Failed to receive BUGREPORT_FINISHED in "
570                         + BUGREPORT_TIMEOUT_MS + " ms.");
571             }
572         }
573     }
574 
575     /**
576      * A rule to change strict mode vm policy temporarily till test method finished.
577      *
578      * To permit the non-visual context usage in tests while taking bugreports need user consent,
579      * or UiAutomator/BugreportManager.DumpstateListener would run into error.
580      * UiDevice#findObject creates UiObject2, its Gesture object and ViewConfiguration and
581      * UiObject2#click need to know bounds. Both of them access to WindowManager internally without
582      * visual context comes from InstrumentationRegistry and violate the policy.
583      * Also <code>DumpstateListener<code/> violate the policy when onScreenshotTaken is called.
584      *
585      * TODO(b/161201609) Remove this class once violations fixed.
586      */
587     static class ExtendedStrictModeVmPolicy extends ExternalResource {
588         private boolean mWasVmPolicyChanged = false;
589         private StrictMode.VmPolicy mOldVmPolicy;
590 
591         @Override
after()592         protected void after() {
593             restoreVmPolicyIfNeeded();
594         }
595 
permitIncorrectContextUse()596         public void permitIncorrectContextUse() {
597             // Allow to call multiple times without losing old policy.
598             if (mOldVmPolicy == null) {
599                 mOldVmPolicy = StrictMode.getVmPolicy();
600             }
601             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
602                     .detectAll()
603                     .permitIncorrectContextUse()
604                     .penaltyLog()
605                     .build());
606             mWasVmPolicyChanged = true;
607         }
608 
restoreVmPolicyIfNeeded()609         private void restoreVmPolicyIfNeeded() {
610             if (mWasVmPolicyChanged && mOldVmPolicy != null) {
611                 StrictMode.setVmPolicy(mOldVmPolicy);
612                 mOldVmPolicy = null;
613             }
614         }
615     }
616 }
617