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