Merge "Show hidden API warning once per process"
diff --git a/api/current.txt b/api/current.txt
index f6b1cbd..a9bf7d1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -56464,6 +56464,7 @@
     method public boolean enqueue();
     method public T get();
     method public boolean isEnqueued();
+    method public static void reachabilityFence(java.lang.Object);
   }
 
   public class ReferenceQueue<T> {
@@ -69486,7 +69487,6 @@
 
   public class ExemptionMechanism {
     ctor protected ExemptionMechanism(javax.crypto.ExemptionMechanismSpi, java.security.Provider, java.lang.String);
-    method protected void finalize();
     method public final byte[] genExemptionBlob() throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException;
     method public final int genExemptionBlob(byte[]) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException;
     method public final int genExemptionBlob(byte[], int) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException;
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 3784d4d..d7f725d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -761,17 +761,18 @@
 
     /*
      * Enable debugging only for apps forked from zygote.
+     * Set suspend=y to pause during VM init and use android ADB transport.
      */
     if (zygote) {
-      // Set the JDWP provider and required arguments. By default let the runtime choose how JDWP is
-      // implemented. When this is not set the runtime defaults to not allowing JDWP.
       addOption("-XjdwpOptions:suspend=n,server=y");
-      parseRuntimeOption("dalvik.vm.jdwp-provider",
-                         jdwpProviderBuf,
-                         "-XjdwpProvider:",
-                         "default");
     }
 
+    // Set the JDWP provider. By default let the runtime choose.
+    parseRuntimeOption("dalvik.vm.jdwp-provider",
+                       jdwpProviderBuf,
+                       "-XjdwpProvider:",
+                       "default");
+
     parseRuntimeOption("dalvik.vm.lockprof.threshold",
                        lockProfThresholdBuf,
                        "-Xlockprofthreshold:");
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 63dba43..fa86c75 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -141,32 +141,45 @@
   errno = saved_errno;
 }
 
-// Configures the SIGCHLD handler for the zygote process. This is configured
-// very late, because earlier in the runtime we may fork() and exec()
-// other processes, and we want to waitpid() for those rather than
+// Configures the SIGCHLD/SIGHUP handlers for the zygote process. This is
+// configured very late, because earlier in the runtime we may fork() and
+// exec() other processes, and we want to waitpid() for those rather than
 // have them be harvested immediately.
 //
+// Ignore SIGHUP because all processes forked by the zygote are in the same
+// process group as the zygote and we don't want to be notified if we become
+// an orphaned group and have one or more stopped processes. This is not a
+// theoretical concern :
+// - we can become an orphaned group if one of our direct descendants forks
+//   and is subsequently killed before its children.
+// - crash_dump routinely STOPs the process it's tracing.
+//
+// See issues b/71965619 and b/25567761 for further details.
+//
 // This ends up being called repeatedly before each fork(), but there's
 // no real harm in that.
-static void SetSigChldHandler() {
-  struct sigaction sa;
-  memset(&sa, 0, sizeof(sa));
-  sa.sa_handler = SigChldHandler;
+static void SetSignalHandlers() {
+  struct sigaction sig_chld = {};
+  sig_chld.sa_handler = SigChldHandler;
 
-  int err = sigaction(SIGCHLD, &sa, NULL);
-  if (err < 0) {
+  if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) {
     ALOGW("Error setting SIGCHLD handler: %s", strerror(errno));
   }
+
+  struct sigaction sig_hup = {};
+  sig_hup.sa_handler = SIG_IGN;
+  if (sigaction(SIGHUP, &sig_hup, NULL) < 0) {
+    ALOGW("Error setting SIGHUP handler: %s", strerror(errno));
+  }
 }
 
 // Sets the SIGCHLD handler back to default behavior in zygote children.
-static void UnsetSigChldHandler() {
+static void UnsetChldSignalHandler() {
   struct sigaction sa;
   memset(&sa, 0, sizeof(sa));
   sa.sa_handler = SIG_DFL;
 
-  int err = sigaction(SIGCHLD, &sa, NULL);
-  if (err < 0) {
+  if (sigaction(SIGCHLD, &sa, NULL) < 0) {
     ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno));
   }
 }
@@ -505,7 +518,7 @@
                                      bool is_system_server, jintArray fdsToClose,
                                      jintArray fdsToIgnore,
                                      jstring instructionSet, jstring dataDir) {
-  SetSigChldHandler();
+  SetSignalHandlers();
 
   sigset_t sigchld;
   sigemptyset(&sigchld);
@@ -682,7 +695,8 @@
     delete se_info;
     delete se_name;
 
-    UnsetSigChldHandler();
+    // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers).
+    UnsetChldSignalHandler();
 
     env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
                               is_system_server, instructionSet);
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
index 99bcd6c..a6c5373 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
@@ -36,10 +36,8 @@
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 
 $(built_dex_intermediate): $(mainDexList)
-endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml
index e3068920..7cd01e54 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml
@@ -7,6 +7,8 @@
     <uses-sdk
         android:minSdkVersion="9"
         android:targetSdkVersion="19" />
+    <!-- Required for com.android.framework.multidexlegacytestservices.test2 -->
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
 
     <application
         android:label="MultiDexLegacyTestServices">
@@ -124,6 +126,6 @@
                 <action android:name="com.android.framework.multidexlegacytestservices.action.Service19" />
             </intent-filter>
         </service>
-        </application>
+    </application>
 
 </manifest>
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
index 7b83999..cb0a591 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java
@@ -60,35 +60,40 @@
             // of the result file will be too big.
             RandomAccessFile raf = new RandomAccessFile(resultFile, "rw");
             raf.seek(raf.length());
-            Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath());
-            raf.writeInt(0x42434445);
+            if (raf.length() == 0) {
+                Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath());
+                raf.writeInt(0x42434445);
+            } else {
+                Log.w(TAG, "Service was restarted appending 0x42434445 twice at " + raf.length()
+                        + " in " + resultFile.getPath());
+                raf.writeInt(0x42434445);
+                raf.writeInt(0x42434445);
+            }
             raf.close();
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        MultiDex.install(applicationContext);
-        Log.i(TAG, "Multi dex installation done.");
+            MultiDex.install(applicationContext);
+            Log.i(TAG, "Multi dex installation done.");
 
-        int value = getValue();
-        Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath());
-        try {
+            int value = getValue();
+            Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath());
             // Append the check value in result file, keeping the constant values already written.
-            RandomAccessFile raf = new RandomAccessFile(resultFile, "rw");
+            raf = new RandomAccessFile(resultFile, "rw");
             raf.seek(raf.length());
             Log.i(TAG, "Writing result at " + raf.length() + " in " + resultFile.getPath());
             raf.writeInt(value);
             raf.close();
         } catch (IOException e) {
-            e.printStackTrace();
-        }
-        try {
-            // Writing end of processing flags, the existence of the file is the criteria
-            RandomAccessFile raf = new RandomAccessFile(new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw");
-            Log.i(TAG, "creating complete file " + resultFile.getPath());
-            raf.writeInt(0x32333435);
-            raf.close();
-        } catch (IOException e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
+        } finally {
+            try {
+                // Writing end of processing flags, the existence of the file is the criteria
+                RandomAccessFile raf = new RandomAccessFile(
+                        new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw");
+                Log.i(TAG, "creating complete file " + resultFile.getPath());
+                raf.writeInt(0x32333435);
+                raf.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
         }
     }
 
@@ -119,9 +124,10 @@
             intermediate = ReflectIntermediateClass.get(45, 80, 20 /* 5 seems enough on a nakasi,
                 using 20 to get some margin */);
         } catch (Exception e) {
-            e.printStackTrace();
+            throw new AssertionError(e);
         }
-        int value = new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() +
+        int value =
+                new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() +
                 new com.android.framework.multidexlegacytestservices.manymethods.Big002().get2() +
                 new com.android.framework.multidexlegacytestservices.manymethods.Big003().get3() +
                 new com.android.framework.multidexlegacytestservices.manymethods.Big004().get4() +
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk
new file mode 100644
index 0000000..f3d98a8
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := MultiDexLegacyTestServicesTests2
+
+LOCAL_JAVA_LIBRARIES := android-support-multidex
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SDK_VERSION := 9
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml
new file mode 100644
index 0000000..0ab2959
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.framework.multidexlegacytestservices.test2"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="9" />
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.framework.multidexlegacytestservices" />
+
+    <application
+        android:label="multidexlegacytestservices.test2" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java
new file mode 100644
index 0000000..900f203
--- /dev/null
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.framework.multidexlegacytestservices.test2;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.concurrent.TimeoutException;
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Run the tests with: <code>adb shell am instrument -w
+ * com.android.framework.multidexlegacytestservices.test2/android.support.test.runner.AndroidJUnitRunner
+ * </code>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ServicesTests {
+    private static final String TAG = "ServicesTests";
+
+    static {
+        Log.i(TAG, "Initializing");
+    }
+
+    private class ExtensionFilter implements FileFilter {
+        private final String ext;
+
+        public ExtensionFilter(String ext) {
+            this.ext = ext;
+        }
+
+        @Override
+        public boolean accept(File file) {
+            return file.getName().endsWith(ext);
+        }
+    }
+
+    private class ExtractedZipFilter extends ExtensionFilter {
+        public  ExtractedZipFilter() {
+            super(".zip");
+        }
+
+        @Override
+        public boolean accept(File file) {
+            return super.accept(file) && !file.getName().startsWith("tmp-");
+        }
+    }
+
+    private static final int ENDHDR = 22;
+
+    private static final String SERVICE_BASE_ACTION =
+            "com.android.framework.multidexlegacytestservices.action.Service";
+    private static final int MIN_SERVICE = 1;
+    private static final int MAX_SERVICE = 19;
+    private static final String COMPLETION_SUCCESS = "Success";
+
+    private File targetFilesDir;
+
+    @Before
+    public void setup() throws Exception {
+        Log.i(TAG, "setup");
+        killServices();
+
+        File applicationDataDir =
+                new File(InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir);
+        clearDirContent(applicationDataDir);
+        targetFilesDir = InstrumentationRegistry.getTargetContext().getFilesDir();
+
+        Log.i(TAG, "setup done");
+    }
+
+    @Test
+    public void testStressConcurentLaunch() throws Exception {
+        startServices();
+        waitServicesCompletion();
+        String completionStatus = getServicesCompletionStatus();
+        if (completionStatus != COMPLETION_SUCCESS) {
+            Assert.fail(completionStatus);
+        }
+    }
+
+    @Test
+    public void testRecoverFromZipCorruption() throws Exception {
+        int serviceId = 1;
+        // Ensure extraction.
+        initServicesWorkFiles();
+        startService(serviceId);
+        waitServicesCompletion(serviceId);
+
+        // Corruption of the extracted zips.
+        tamperAllExtractedZips();
+
+        killServices();
+        checkRecover();
+    }
+
+    @Test
+    public void testRecoverFromDexCorruption() throws Exception {
+        int serviceId = 1;
+        // Ensure extraction.
+        initServicesWorkFiles();
+        startService(serviceId);
+        waitServicesCompletion(serviceId);
+
+        // Corruption of the odex files.
+        tamperAllOdex();
+
+        killServices();
+        checkRecover();
+    }
+
+    @Test
+    public void testRecoverFromZipCorruptionStressTest() throws Exception {
+        Thread startServices =
+                new Thread() {
+            @Override
+            public void run() {
+                startServices();
+            }
+        };
+
+        startServices.start();
+
+        // Start services lasts more than 80s, lets cause a few corruptions during this interval.
+        for (int i = 0; i < 80; i++) {
+            Thread.sleep(1000);
+            tamperAllExtractedZips();
+        }
+        startServices.join();
+        try {
+            waitServicesCompletion();
+        } catch (TimeoutException e) {
+            // Can happen.
+        }
+
+        killServices();
+        checkRecover();
+    }
+
+    @Test
+    public void testRecoverFromDexCorruptionStressTest() throws Exception {
+        Thread startServices =
+                new Thread() {
+            @Override
+            public void run() {
+                startServices();
+            }
+        };
+
+        startServices.start();
+
+        // Start services lasts more than 80s, lets cause a few corruptions during this interval.
+        for (int i = 0; i < 80; i++) {
+            Thread.sleep(1000);
+            tamperAllOdex();
+        }
+        startServices.join();
+        try {
+            waitServicesCompletion();
+        } catch (TimeoutException e) {
+            // Will probably happen most of the time considering what we're doing...
+        }
+
+        killServices();
+        checkRecover();
+    }
+
+    private static void clearDirContent(File dir) {
+        for (File subElement : dir.listFiles()) {
+            if (subElement.isDirectory()) {
+                clearDirContent(subElement);
+            }
+            if (!subElement.delete()) {
+                throw new AssertionError("Failed to clear '" + subElement.getAbsolutePath() + "'");
+            }
+        }
+    }
+
+    private void startServices() {
+        Log.i(TAG, "start services");
+        initServicesWorkFiles();
+        for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
+            startService(i);
+            try {
+                Thread.sleep((i - 1) * (1 << (i / 5)));
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    private void startService(int serviceId) {
+        Log.i(TAG, "start service " + serviceId);
+        InstrumentationRegistry.getContext().startService(new Intent(SERVICE_BASE_ACTION + serviceId));
+    }
+
+    private void initServicesWorkFiles() {
+        for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
+            File resultFile = new File(targetFilesDir, "Service" + i);
+            resultFile.delete();
+            Assert.assertFalse(
+                    "Failed to delete result file '" + resultFile.getAbsolutePath() + "'.",
+                    resultFile.exists());
+            File completeFile = new File(targetFilesDir, "Service" + i + ".complete");
+            completeFile.delete();
+            Assert.assertFalse(
+                    "Failed to delete completion file '" + completeFile.getAbsolutePath() + "'.",
+                    completeFile.exists());
+        }
+    }
+
+    private void waitServicesCompletion() throws TimeoutException {
+        Log.i(TAG, "start sleeping");
+        int attempt = 0;
+        int maxAttempt = 50; // 10 is enough for a nexus S
+        do {
+            try {
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+            }
+            attempt++;
+            if (attempt >= maxAttempt) {
+                throw new TimeoutException();
+            }
+        } while (!areAllServicesCompleted());
+    }
+
+    private void waitServicesCompletion(int serviceId) throws TimeoutException {
+        Log.i(TAG, "start sleeping");
+        int attempt = 0;
+        int maxAttempt = 50; // 10 is enough for a nexus S
+        do {
+            try {
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+            }
+            attempt++;
+            if (attempt >= maxAttempt) {
+                throw new TimeoutException();
+            }
+        } while (isServiceRunning(serviceId));
+    }
+
+    private String getServicesCompletionStatus() {
+        String status = COMPLETION_SUCCESS;
+        for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
+            File resultFile = new File(targetFilesDir, "Service" + i);
+            if (!resultFile.isFile()) {
+                status = "Service" + i + " never completed.";
+                break;
+            }
+            if (resultFile.length() != 8) {
+                status = "Service" + i + " was restarted.";
+                break;
+            }
+        }
+        Log.i(TAG, "Services completion status: " + status);
+        return status;
+    }
+
+    private String getServiceCompletionStatus(int serviceId) {
+        String status = COMPLETION_SUCCESS;
+        File resultFile = new File(targetFilesDir, "Service" + serviceId);
+        if (!resultFile.isFile()) {
+            status = "Service" + serviceId + " never completed.";
+        } else if (resultFile.length() != 8) {
+            status = "Service" + serviceId + " was restarted.";
+        }
+        Log.i(TAG, "Service " + serviceId + " completion status: " + status);
+        return status;
+    }
+
+    private boolean areAllServicesCompleted() {
+        for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
+            if (isServiceRunning(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean isServiceRunning(int i) {
+        File completeFile = new File(targetFilesDir, "Service" + i + ".complete");
+        return !completeFile.exists();
+    }
+
+    private File getSecondaryFolder() {
+        File dir =
+                new File(
+                        new File(
+                                InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir,
+                                "code_cache"),
+                        "secondary-dexes");
+        Assert.assertTrue(dir.getAbsolutePath(), dir.isDirectory());
+        return dir;
+    }
+
+    private void tamperAllExtractedZips() throws IOException {
+        // First attempt was to just overwrite zip entries but keep central directory, this was no
+        // trouble for Dalvik that was just ignoring those zip and using the odex files.
+        Log.i(TAG, "Tamper extracted zip files by overwriting all content by '\\0's.");
+        byte[] zeros = new byte[4 * 1024];
+        // Do not tamper tmp zip during their extraction.
+        for (File zip : getSecondaryFolder().listFiles(new ExtractedZipFilter())) {
+            long fileLength = zip.length();
+            Assert.assertTrue(fileLength > ENDHDR);
+            zip.setWritable(true);
+            RandomAccessFile raf = new RandomAccessFile(zip, "rw");
+            try {
+                int index = 0;
+                while (index < fileLength) {
+                    int length = (int) Math.min(zeros.length, fileLength - index);
+                    raf.write(zeros, 0, length);
+                    index += length;
+                }
+            } finally {
+                raf.close();
+            }
+        }
+    }
+
+    private void tamperAllOdex() throws IOException {
+        Log.i(TAG, "Tamper odex files by overwriting some content by '\\0's.");
+        byte[] zeros = new byte[4 * 1024];
+        // I think max size would be 40 (u1[8] + 8 u4) but it's a test so lets take big margins.
+        int savedSizeForOdexHeader = 80;
+        for (File odex : getSecondaryFolder().listFiles(new ExtensionFilter(".dex"))) {
+            long fileLength = odex.length();
+            Assert.assertTrue(fileLength > zeros.length + savedSizeForOdexHeader);
+            odex.setWritable(true);
+            RandomAccessFile raf = new RandomAccessFile(odex, "rw");
+            try {
+                raf.seek(savedSizeForOdexHeader);
+                raf.write(zeros, 0, zeros.length);
+            } finally {
+                raf.close();
+            }
+        }
+    }
+
+    private void checkRecover() throws TimeoutException {
+        Log.i(TAG, "Check recover capability");
+        int serviceId = 1;
+        // Start one service and check it was able to run correctly even if a previous run failed.
+        initServicesWorkFiles();
+        startService(serviceId);
+        waitServicesCompletion(serviceId);
+        String completionStatus = getServiceCompletionStatus(serviceId);
+        if (completionStatus != COMPLETION_SUCCESS) {
+            Assert.fail(completionStatus);
+        }
+    }
+
+    private void killServices() {
+        ((ActivityManager)
+                InstrumentationRegistry.getContext().getSystemService(Context.ACTIVITY_SERVICE))
+        .killBackgroundProcesses("com.android.framework.multidexlegacytestservices");
+    }
+}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index ef3a183..25f5133 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -27,6 +27,7 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Description of the response of a setup data call connection request.
@@ -220,17 +221,8 @@
 
     @Override
     public int hashCode() {
-        return mStatus * 31
-                + mSuggestedRetryTime * 37
-                + mCid * 41
-                + mActive * 43
-                + mType.hashCode() * 47
-                + mIfname.hashCode() * 53
-                + mAddresses.hashCode() * 59
-                + mDnses.hashCode() * 61
-                + mGateways.hashCode() * 67
-                + mPcscfs.hashCode() * 71
-                + mMtu * 73;
+        return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses,
+                mDnses, mGateways, mPcscfs, mMtu);
     }
 
     @Override