diff options
21 files changed, 706 insertions, 322 deletions
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 06f7916be4b6..70af021fe0c3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -688,6 +688,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Cycles do not exist because they are illegal and screened for during installation. * * May be null if no splits are installed, or if no dependencies exist between them. + * + * NOTE: Any change to the way split dependencies are stored must update the logic that + * creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts). + * * @hide */ public SparseArray<int[]> splitDependencies; diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index 7dec4d724f15..3544ea1e03e3 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -156,4 +156,27 @@ public class HidlSupport { // Should not reach here. throw new UnsupportedOperationException(); } + + /** + * Test that two interfaces are equal. This is the Java equivalent to C++ + * interfacesEqual function. + * This essentially calls .equals on the internal binder objects (via Binder()). + * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder} + * object, and they are compared in {@link HwRemoteBinder#equals}. + * - If both interfaces are stubs, asBinder() returns the object itself. By default, + * auto-generated IFoo.Stub does not override equals(), but an implementation can + * optionally override it, and {@code interfacesEqual} will use it here. + */ + public static boolean interfacesEqual(IHwInterface lft, Object rgt) { + if (lft == rgt) { + return true; + } + if (lft == null || rgt == null) { + return false; + } + if (!(rgt instanceof IHwInterface)) { + return false; + } + return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder()); + } } diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java index 2f89ce6270be..a07e42c720c5 100644 --- a/core/java/android/os/HwRemoteBinder.java +++ b/core/java/android/os/HwRemoteBinder.java @@ -63,4 +63,9 @@ public class HwRemoteBinder implements IHwBinder { } private long mNativeContext; + + @Override + public final native boolean equals(Object other); + @Override + public final native int hashCode(); } diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java index 9b3a2d689693..00333dad1a17 100644 --- a/core/java/android/os/TokenWatcher.java +++ b/core/java/android/os/TokenWatcher.java @@ -16,17 +16,23 @@ package android.os; +import android.util.Log; + import java.io.PrintWriter; import java.util.ArrayList; -import java.util.WeakHashMap; import java.util.Set; -import android.util.Log; +import java.util.WeakHashMap; /** - * Helper class that helps you use IBinder objects as reference counted - * tokens. IBinders make good tokens because we find out when they are - * removed + * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added + * to the collection by calling {@link #acquire}, and removed by calling {@link + * #release}. IBinders are also implicitly removed when they become weakly + * reachable. Each IBinder may be added at most once. * + * The {@link #acquired} method is invoked by posting to the specified handler + * whenever the size of the watched collection becomes nonzero. The {@link + * #released} method is invoked on the specified handler whenever the size of + * the watched collection becomes zero. */ public abstract class TokenWatcher { @@ -59,15 +65,23 @@ public abstract class TokenWatcher * Record that this token has been acquired. When acquire is called, and * the current count is 0, the acquired method is called on the given * handler. - * - * @param token An IBinder object. If this token has already been acquired, - * no action is taken. + * + * Note that the same {@code token} can only be acquired once. If this + * {@code token} has already been acquired, no action is taken. The first + * subsequent call to {@link #release} will release this {@code token} + * immediately. + * + * @param token An IBinder object. * @param tag A string used by the {@link #dump} method for debugging, * to see who has references. */ public void acquire(IBinder token, String tag) { synchronized (mTokens) { + if (mTokens.containsKey(token)) { + return; + } + // explicitly checked to avoid bogus sendNotification calls because // of the WeakHashMap and the GC int oldSize = mTokens.size(); diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 8d9630fe5654..e5ad1f47d37d 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -804,7 +804,7 @@ public class StateMachine { /** State that processed the message */ State msgProcessedState = null; - if (mIsConstructionCompleted) { + if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) { /** Normal path */ msgProcessedState = processMsg(msg); } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp index 737ec47e8ffb..bb916d2431c5 100644 --- a/core/jni/android_os_HwBlob.cpp +++ b/core/jni/android_os_HwBlob.cpp @@ -26,6 +26,7 @@ #include <android_runtime/AndroidRuntime.h> #include <hidl/Status.h> #include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include "core_jni_helpers.h" @@ -349,6 +350,13 @@ static void JHwBlob_native_copyTo ## Suffix ## Array( \ static_cast<const uint8_t *>(blob->data()) + offset)); \ } +DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte) +DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short) +DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int) +DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long) +DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float) +DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double) + static void JHwBlob_native_copyToBoolArray( JNIEnv *env, jobject thiz, @@ -386,13 +394,6 @@ static void JHwBlob_native_copyToBoolArray( dst = nullptr; } -DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte) -DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short) -DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int) -DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long) -DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float) -DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double) - #define DEFINE_BLOB_PUTTER(Suffix,Type) \ static void JHwBlob_native_put ## Suffix( \ JNIEnv *env, jobject thiz, jlong offset, Type x) { \ @@ -458,23 +459,17 @@ static void JHwBlob_native_putString( #define DEFINE_BLOB_ARRAY_PUTTER(Suffix,Type,NewType) \ static void JHwBlob_native_put ## Suffix ## Array( \ JNIEnv *env, jobject thiz, jlong offset, Type ## Array array) { \ + Scoped ## NewType ## ArrayRO autoArray(env, array); \ \ if (array == nullptr) { \ - jniThrowException(env, "java/lang/NullPointerException", nullptr); \ + /* NullpointerException already pending */ \ return; \ } \ \ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \ \ - jsize len = env->GetArrayLength(array); \ - \ - Type *src = \ - env->Get ## NewType ## ArrayElements(array, nullptr /* isCopy */); \ - \ - status_t err = blob->write(offset, src, len * sizeof(Type)); \ - \ - env->Release ## NewType ## ArrayElements(array, src, 0 /* mode */); \ - src = nullptr; \ + status_t err = blob->write( \ + offset, autoArray.get(), autoArray.size() * sizeof(Type)); \ \ if (err != OK) { \ signalExceptionForError(env, err); \ @@ -490,35 +485,28 @@ DEFINE_BLOB_ARRAY_PUTTER(Double,jdouble,Double) static void JHwBlob_native_putBoolArray( JNIEnv *env, jobject thiz, jlong offset, jbooleanArray array) { + ScopedBooleanArrayRO autoArray(env, array); if (array == nullptr) { - jniThrowException(env, "java/lang/NullPointerException", nullptr); + /* NullpointerException already pending */ return; } sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); - jsize len = env->GetArrayLength(array); - - if ((offset + len * sizeof(bool)) > blob->size()) { + if ((offset + autoArray.size() * sizeof(bool)) > blob->size()) { signalExceptionForError(env, -ERANGE); return; } - const jboolean *src = - env->GetBooleanArrayElements(array, nullptr /* isCopy */); + const jboolean *src = autoArray.get(); bool *dst = reinterpret_cast<bool *>( static_cast<uint8_t *>(blob->data()) + offset); - for (jsize i = 0; i < len; ++i) { + for (size_t i = 0; i < autoArray.size(); ++i) { dst[i] = src[i]; } - - env->ReleaseBooleanArrayElements( - array, const_cast<jboolean *>(src), 0 /* mode */); - - src = nullptr; } static void JHwBlob_native_putBlob( diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp index cf59a56a13dc..ca5e1e45dcdc 100644 --- a/core/jni/android_os_HwRemoteBinder.cpp +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -22,9 +22,13 @@ #include "android_os_HwParcel.h" -#include <nativehelper/JNIHelp.h> +#include <android/hidl/base/1.0/IBase.h> +#include <android/hidl/base/1.0/BpHwBase.h> +#include <android/hidl/base/1.0/BnHwBase.h> #include <android_runtime/AndroidRuntime.h> #include <hidl/Status.h> +#include <hidl/HidlTransportSupport.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedLocalRef.h> @@ -413,6 +417,44 @@ static jboolean JHwRemoteBinder_unlinkToDeath(JNIEnv* env, jobject thiz, return res; } +static sp<hidl::base::V1_0::IBase> toIBase(JNIEnv* env, jclass hwRemoteBinderClazz, jobject jbinder) +{ + if (jbinder == nullptr) { + return nullptr; + } + if (!env->IsInstanceOf(jbinder, hwRemoteBinderClazz)) { + return nullptr; + } + sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, jbinder); + sp<hardware::IBinder> cbinder = context->getBinder(); + return hardware::fromBinder<hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase, + hidl::base::V1_0::BnHwBase>(cbinder); +} + +// equals iff other is also a non-null android.os.HwRemoteBinder object +// and getBinder() returns the same object. +// In particular, if other is an android.os.HwBinder object (for stubs) then +// it returns false. +static jboolean JHwRemoteBinder_equals(JNIEnv* env, jobject thiz, jobject other) +{ + if (env->IsSameObject(thiz, other)) { + return true; + } + if (other == NULL) { + return false; + } + + ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH)); + + return hardware::interfacesEqual(toIBase(env, clazz.get(), thiz), toIBase(env, clazz.get(), other)); +} + +static jint JHwRemoteBinder_hashCode(JNIEnv* env, jobject thiz) { + jlong longHash = reinterpret_cast<jlong>( + JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder().get()); + return static_cast<jint>(longHash ^ (longHash >> 32)); // See Long.hashCode() +} + static JNINativeMethod gMethods[] = { { "native_init", "()J", (void *)JHwRemoteBinder_native_init }, @@ -430,6 +472,11 @@ static JNINativeMethod gMethods[] = { {"unlinkToDeath", "(Landroid/os/IHwBinder$DeathRecipient;)Z", (void*)JHwRemoteBinder_unlinkToDeath}, + + {"equals", "(Ljava/lang/Object;)Z", + (void*)JHwRemoteBinder_equals}, + + {"hashCode", "()I", (void*)JHwRemoteBinder_hashCode}, }; namespace android { diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 9f8d2880d8bf..560c38486bb9 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -162,6 +162,7 @@ static std::atomic<uint32_t> gCollectedAtRefs(0); // Garbage collect if we've allocated at least GC_INTERVAL refs since the last time. // TODO: Consider removing this completely. We should no longer be generating GlobalRefs // that are reclaimed as a result of GC action. +__attribute__((no_sanitize("unsigned-integer-overflow"))) static void gcIfManyNewRefs(JNIEnv* env) { uint32_t totalRefs = gNumLocalRefsCreated.load(std::memory_order_relaxed) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 40fb6e83e94d..e291ea6cc856 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2402,7 +2402,13 @@ <bool name="config_networkSamplingWakesDevice">true</bool> - <string-array translatable="false" name="config_cdma_home_system" /> + <!-- Home (non-roaming) values for CDMA roaming indicator. + Carriers can override this table by resource overlay. If not, + the default values come from 3GPP2 C.R1001 table + 8.1-1. Enhanced Roaming Indicator Number Assignments --> + <string-array translatable="false" name="config_cdma_home_system"> + <item>1</item> + </string-array> <!--From SmsMessage--> <!--Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java index eb2a5165dc4f..76aa93f7e8be 100644 --- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java @@ -24,6 +24,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.test.TestLooper; import android.test.suitebuilder.annotation.Suppress; import com.android.internal.util.State; @@ -343,6 +344,100 @@ public class StateMachineTest extends TestCase { } /** + * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}. + */ + class StateMachineQuitNowAfterStartTest extends StateMachine { + Collection<LogRec> mLogRecs; + + StateMachineQuitNowAfterStartTest(String name, Looper looper) { + super(name, looper); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + @Override + public void onQuitting() { + tlog("onQuitting"); + addLogRec(ON_QUITTING); + mLogRecs = mThisSm.copyLogRecs(); + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + class S1 extends State { + @Override + public void enter() { + tlog("S1.enter"); + addLogRec(ENTER); + } + @Override + public void exit() { + tlog("S1.exit"); + addLogRec(EXIT); + } + @Override + public boolean processMessage(Message message) { + switch(message.what) { + // Sleep and assume the other messages will be queued up. + case TEST_CMD_1: { + tlog("TEST_CMD_1"); + sleep(500); + break; + } + default: { + tlog("default what=" + message.what); + break; + } + } + return HANDLED; + } + } + + private StateMachineQuitNowAfterStartTest mThisSm; + private S1 mS1 = new S1(); + } + + /** + * When quitNow() is called immediately after start(), the QUIT_CMD gets processed + * before the INIT_CMD. This test ensures that the StateMachine can gracefully handle + * this sequencing of messages (QUIT before INIT). + */ + @SmallTest + public void testStateMachineQuitNowAfterStart() throws Exception { + if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); + + TestLooper testLooper = new TestLooper(); + StateMachineQuitNowAfterStartTest smQuitNowAfterStartTest = + new StateMachineQuitNowAfterStartTest( + "smQuitNowAfterStartTest", testLooper.getLooper()); + smQuitNowAfterStartTest.start(); + smQuitNowAfterStartTest.quitNow(); + if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart E"); + + testLooper.dispatchAll(); + dumpLogRecs(smQuitNowAfterStartTest.mLogRecs); + assertEquals(2, smQuitNowAfterStartTest.mLogRecs.size()); + + LogRec lr; + Iterator<LogRec> itr = smQuitNowAfterStartTest.mLogRecs.iterator(); + lr = itr.next(); + assertEquals(EXIT, lr.getInfo()); + assertEquals(smQuitNowAfterStartTest.mS1, lr.getState()); + + lr = itr.next(); + assertEquals(ON_QUITTING, lr.getInfo()); + + if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart X"); + } + + /** * Test enter/exit can use transitionTo */ class StateMachineEnterExitTransitionToTest extends StateMachine { diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp index 5abfc8ee917e..5d243da2097c 100644 --- a/libs/androidfw/ZipUtils.cpp +++ b/libs/androidfw/ZipUtils.cpp @@ -20,10 +20,11 @@ #define LOG_TAG "ziputil" +#include "android-base/file.h" #include <androidfw/ZipUtils.h> -#include <androidfw/ZipFileRO.h> #include <utils/Log.h> #include <utils/Compat.h> +#include <ziparchive/zip_archive.h> #include <stdlib.h> #include <string.h> @@ -33,211 +34,121 @@ using namespace android; -static inline unsigned long get4LE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); -} - - -static const unsigned long kReadBufSize = 32768; - -/* - * Utility function that expands zip/gzip "deflate" compressed data - * into a buffer. - * - * (This is a clone of the previous function, but it takes a FILE* instead - * of an fd. We could pass fileno(fd) to the above, but we can run into - * trouble when "fp" has a different notion of what fd's file position is.) - * - * "fp" is an open file positioned at the start of the "deflate" data - * "buf" must hold at least "uncompressedLen" bytes. - */ -/*static*/ template<typename T> bool inflateToBuffer(T& reader, void* buf, - long uncompressedLen, long compressedLen) -{ - bool result = false; - - z_stream zstream; - int zerr; - unsigned long compRemaining; - - assert(uncompressedLen >= 0); - assert(compressedLen >= 0); - - compRemaining = compressedLen; - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = (Bytef*) buf; - zstream.avail_out = uncompressedLen; - zstream.data_type = Z_UNKNOWN; - - /* - * Use the undocumented "negative window bits" feature to tell zlib - * that there's no zlib header waiting for it. - */ - zerr = inflateInit2(&zstream, -MAX_WBITS); - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; +// TODO: This can go away once the only remaining usage in aapt goes away. +class FileReader : public zip_archive::Reader { + public: + FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) { } - /* - * Loop while we have data. - */ - do { - unsigned long getSize; - - /* read as much as we can */ - if (zstream.avail_in == 0) { - getSize = (compRemaining > kReadBufSize) ? - kReadBufSize : compRemaining; - ALOGV("+++ reading %ld bytes (%ld left)\n", - getSize, compRemaining); - - unsigned char* nextBuffer = NULL; - const unsigned long nextSize = reader.read(&nextBuffer, getSize); - - if (nextSize < getSize || nextBuffer == NULL) { - ALOGD("inflate read failed (%ld vs %ld)\n", nextSize, getSize); - goto z_bail; + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + // Data is usually requested sequentially, so this helps avoid pointless + // fseeks every time we perform a read. There's an impedence mismatch + // here because the original API was designed around pread and pwrite. + if (offset != mCurrentOffset) { + if (fseek(mFp, offset, SEEK_SET) != 0) { + return false; } - compRemaining -= nextSize; - - zstream.next_in = nextBuffer; - zstream.avail_in = nextSize; + mCurrentOffset = offset; } - /* uncompress the data */ - zerr = inflate(&zstream, Z_NO_FLUSH); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGD("zlib inflate call failed (zerr=%d)\n", zerr); - goto z_bail; + size_t read = fread(buf, 1, len, mFp); + if (read != len) { + return false; } - /* output buffer holds all, so no need to write the output */ - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - if ((long) zstream.total_out != uncompressedLen) { - ALOGW("Size mismatch on inflated file (%ld vs %ld)\n", - zstream.total_out, uncompressedLen); - goto z_bail; + mCurrentOffset += read; + return true; } - // success! - result = true; - -z_bail: - inflateEnd(&zstream); /* free up any allocated structures */ - -bail: - return result; -} - -class FileReader { -public: - explicit FileReader(FILE* fp) : - mFp(fp), mReadBuf(new unsigned char[kReadBufSize]) - { - } + private: + FILE* mFp; + mutable uint32_t mCurrentOffset; +}; - ~FileReader() { - delete[] mReadBuf; - } +class FdReader : public zip_archive::Reader { + public: + explicit FdReader(int fd) : mFd(fd) { + } - long read(unsigned char** nextBuffer, long readSize) const { - *nextBuffer = mReadBuf; - return fread(mReadBuf, 1, readSize, mFp); - } + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + return android::base::ReadFullyAtOffset(mFd, buf, len, static_cast<off_t>(offset)); + } - FILE* mFp; - unsigned char* mReadBuf; + private: + const int mFd; }; -class FdReader { -public: - explicit FdReader(int fd) : - mFd(fd), mReadBuf(new unsigned char[kReadBufSize]) - { - } +class BufferReader : public zip_archive::Reader { + public: + BufferReader(const void* input, size_t inputSize) : Reader(), + mInput(reinterpret_cast<const uint8_t*>(input)), + mInputSize(inputSize) { + } - ~FdReader() { - delete[] mReadBuf; - } + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + if (offset + len > mInputSize) { + return false; + } - long read(unsigned char** nextBuffer, long readSize) const { - *nextBuffer = mReadBuf; - return TEMP_FAILURE_RETRY(::read(mFd, mReadBuf, readSize)); - } + memcpy(buf, mInput + offset, len); + return true; + } - int mFd; - unsigned char* mReadBuf; + private: + const uint8_t* mInput; + const size_t mInputSize; }; -class BufferReader { -public: - BufferReader(void* input, size_t inputSize) : - mInput(reinterpret_cast<unsigned char*>(input)), - mInputSize(inputSize), - mBufferReturned(false) - { +class BufferWriter : public zip_archive::Writer { + public: + BufferWriter(void* output, size_t outputSize) : Writer(), + mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) { } - long read(unsigned char** nextBuffer, long /*readSize*/) { - if (!mBufferReturned) { - mBufferReturned = true; - *nextBuffer = mInput; - return mInputSize; + bool Append(uint8_t* buf, size_t bufSize) override { + if (mBytesWritten + bufSize > mOutputSize) { + return false; } - *nextBuffer = NULL; - return 0; + memcpy(mOutput + mBytesWritten, buf, bufSize); + mBytesWritten += bufSize; + return true; } - unsigned char* mInput; - const size_t mInputSize; - bool mBufferReturned; + private: + uint8_t* const mOutput; + const size_t mOutputSize; + size_t mBytesWritten; }; /*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf, long uncompressedLen, long compressedLen) { FileReader reader(fp); - return ::inflateToBuffer<FileReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } /*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf, long uncompressedLen, long compressedLen) { FdReader reader(fd); - return ::inflateToBuffer<FdReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } -/*static*/ bool ZipUtils::inflateToBuffer(void* in, void* buf, +/*static*/ bool ZipUtils::inflateToBuffer(const void* in, void* buf, long uncompressedLen, long compressedLen) { BufferReader reader(in, compressedLen); - return ::inflateToBuffer<BufferReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } - +static inline unsigned long get4LE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); +} /* * Look at the contents of a gzip archive. We want to know where the @@ -275,7 +186,7 @@ public: /* quick sanity checks */ if (method == EOF || flags == EOF) return false; - if (method != ZipFileRO::kCompressDeflated) + if (method != kCompressDeflated) return false; /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */ diff --git a/libs/androidfw/include/androidfw/ZipUtils.h b/libs/androidfw/include/androidfw/ZipUtils.h index 55575d774522..4d35e992cc89 100644 --- a/libs/androidfw/include/androidfw/ZipUtils.h +++ b/libs/androidfw/include/androidfw/ZipUtils.h @@ -40,7 +40,7 @@ public: long compressedLen); static bool inflateToBuffer(int fd, void* buf, long uncompressedLen, long compressedLen); - static bool inflateToBuffer(void *in, void* buf, long uncompressedLen, + static bool inflateToBuffer(const void *in, void* buf, long uncompressedLen, long compressedLen); /* diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index da6e26e17122..73ac05738f0f 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -53,7 +53,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub { private final static boolean DEBUG_DEXOPT = true; // The synthetic library dependencies denoting "no checks." - private final static String[] NO_LIBRARIES = new String[] { "&" }; + private final static String[] NO_LIBRARIES = + new String[] { PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK }; // The amount of "available" (free - low threshold) space necessary at the start of an OTA to // not bulk-delete unused apps' odex files. @@ -325,7 +326,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { mPackageManagerService.getDexManager().dexoptSecondaryDex( new DexoptOptions(pkg.packageName, compilationReason, DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | - DexoptOptions.DEXOPT_BOOT_COMPLETE)); + DexoptOptions.DEXOPT_BOOT_COMPLETE)); return commands; } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index dfa828df5236..ef015e7e418b 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -40,6 +40,7 @@ import com.android.server.pm.dex.PackageDexUsage; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -158,16 +159,37 @@ public class PackageDexOptimizer { // Get the class loader context dependencies. // For each code path in the package, this array contains the class loader context that // needs to be passed to dexopt in order to ensure correct optimizations. + boolean[] pathsWithCode = new boolean[paths.size()]; + pathsWithCode[0] = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0; + for (int i = 1; i < paths.size(); i++) { + pathsWithCode[i] = (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) != 0; + } String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts( - pkg.applicationInfo, sharedLibraries); + pkg.applicationInfo, sharedLibraries, pathsWithCode); + + // Sanity check that we do not call dexopt with inconsistent data. + if (paths.size() != classLoaderContexts.length) { + String[] splitCodePaths = pkg.applicationInfo.getSplitCodePaths(); + throw new IllegalStateException("Inconsistent information " + + "between PackageParser.Package and its ApplicationInfo. " + + "pkg.getAllCodePaths=" + paths + + " pkg.applicationInfo.getBaseCodePath=" + pkg.applicationInfo.getBaseCodePath() + + " pkg.applicationInfo.getSplitCodePaths=" + + (splitCodePaths == null ? "null" : Arrays.toString(splitCodePaths))); + } int result = DEX_OPT_SKIPPED; for (int i = 0; i < paths.size(); i++) { // Skip paths that have no code. - if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) || - (i != 0 && (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) == 0)) { + if (!pathsWithCode[i]) { continue; } + if (classLoaderContexts[i] == null) { + throw new IllegalStateException("Inconsistent information in the " + + "package structure. A split is marked to contain code " + + "but has no dependency listed. Index=" + i + " path=" + paths.get(i)); + } + // Append shared libraries with split dependencies for this split. String path = paths.get(i); if (options.getSplitName() != null) { @@ -187,7 +209,7 @@ public class PackageDexOptimizer { // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct // flags. - final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete()); + final int dexoptFlags = getDexFlags(pkg, compilerFilter, options); for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, @@ -327,8 +349,7 @@ public class PackageDexOptimizer { dexUseInfo.isUsedByOtherApps()); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. // Secondary dex files are currently not compiled at boot. - int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true) - | DEXOPT_SECONDARY_DEX; + int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX; // Check the app storage and add the appropriate flags. if (info.deviceProtectedDataDir != null && FileUtils.contains(info.deviceProtectedDataDir, path)) { @@ -345,18 +366,13 @@ public class PackageDexOptimizer { + " dexoptFlags=" + printDexoptFlags(dexoptFlags) + " target-filter=" + compilerFilter); - String classLoaderContext; - if (dexUseInfo.isUnknownClassLoaderContext() || - dexUseInfo.isUnsupportedClassLoaderContext() || - dexUseInfo.isVariableClassLoaderContext()) { - // If we have an unknown (not yet set), unsupported (custom class loaders), or a - // variable class loader chain, compile without a context and mark the oat file with - // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation. - // TODO(calin): We should just extract in this case. - classLoaderContext = SKIP_SHARED_LIBRARY_CHECK; - } else { - classLoaderContext = dexUseInfo.getClassLoaderContext(); - } + // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context + // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files + // in isolation (and avoid to extract/verify the main apk if it's in the class path). + // Note this trades correctness for performance since the resulting slow down is + // unacceptable in some cases until b/64530081 is fixed. + String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK; + try { for (String isa : dexUseInfo.getLoaderIsas()) { // Reuse the same dexopt path as for the primary apks. We don't need all the @@ -416,7 +432,7 @@ public class PackageDexOptimizer { } if (useInfo.isUsedByOtherApps(path)) { - pw.println("used be other apps: " + useInfo.getLoadingPackages(path)); + pw.println("used by other apps: " + useInfo.getLoadingPackages(path)); } Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap(); @@ -429,19 +445,10 @@ public class PackageDexOptimizer { PackageDexUsage.DexUseInfo dexUseInfo = e.getValue(); pw.println(dex); pw.increaseIndent(); - for (String isa : dexUseInfo.getLoaderIsas()) { - String status = null; - try { - status = DexFile.getDexFileStatus(path, isa); - } catch (IOException ioe) { - status = "[Exception]: " + ioe.getMessage(); - } - pw.println(isa + ": " + status); - } - + // TODO(calin): get the status of the oat file (needs installd call) pw.println("class loader context: " + dexUseInfo.getClassLoaderContext()); if (dexUseInfo.isUsedByOtherApps()) { - pw.println("used be other apps: " + dexUseInfo.getLoadingPackages()); + pw.println("used by other apps: " + dexUseInfo.getLoadingPackages()); } pw.decreaseIndent(); } @@ -465,8 +472,9 @@ public class PackageDexOptimizer { } if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) { - // If the dex files is used by other apps, we cannot use profile-guided compilation. - return getNonProfileGuidedCompilerFilter(targetCompilerFilter); + // If the dex files is used by other apps, apply the shared filter. + return PackageManagerServiceCompilerMapping.getCompilerFilterForReason( + PackageManagerService.REASON_SHARED); } return targetCompilerFilter; @@ -477,11 +485,11 @@ public class PackageDexOptimizer { * filter. */ private int getDexFlags(PackageParser.Package pkg, String compilerFilter, - boolean bootComplete) { - return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete); + DexoptOptions options) { + return getDexFlags(pkg.applicationInfo, compilerFilter, options); } - private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) { + private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) { int flags = info.flags; boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; // Profile guide compiled oat files should not be public. @@ -492,7 +500,8 @@ public class PackageDexOptimizer { (isPublic ? DEXOPT_PUBLIC : 0) | (debuggable ? DEXOPT_DEBUGGABLE : 0) | profileFlag - | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0); + | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0) + | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0); return adjustDexoptFlags(dexFlags); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a8ea4eaee4fd..242a76dbb63f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -562,8 +562,9 @@ public class PackageManagerService extends IPackageManager.Stub public static final int REASON_BACKGROUND_DEXOPT = 3; public static final int REASON_AB_OTA = 4; public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5; + public static final int REASON_SHARED = 6; - public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE; + public static final int REASON_LAST = REASON_SHARED; /** All dangerous permission names in the same order as the events in MetricsEvent */ private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList( @@ -18263,36 +18264,6 @@ public class PackageManagerService extends IPackageManager.Stub Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } } - - // dexopt can take some time to complete, so, for instant apps, we skip this - // step during installation. Instead, we'll take extra time the first time the - // instant app starts. It's preferred to do it this way to provide continuous - // progress to the user instead of mysteriously blocking somewhere in the - // middle of running an instant app. The default behaviour can be overridden - // via gservices. - if (!instantApp || Global.getInt( - mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); - // Do not run PackageDexOptimizer through the local performDexOpt - // method because `pkg` may not be in `mPackages` yet. - // - // Also, don't fail application installs if the dexopt step fails. - DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName, - REASON_INSTALL, - DexoptOptions.DEXOPT_BOOT_COMPLETE); - mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, - null /* instructionSets */, - getOrCreateCompilerPackageStats(pkg), - mDexManager.getPackageUseInfoOrDefault(pkg.packageName), - dexoptOptions); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - // Notify BackgroundDexOptService that the package has been changed. - // If this is an update of a package which used to fail to compile, - // BDOS will remove it from its blacklist. - // TODO: Layering violation - BackgroundDexOptService.notifyPackageChanged(pkg.packageName); } if (!args.doRename(res.returnCode, pkg, oldCodePath)) { @@ -18300,6 +18271,50 @@ public class PackageManagerService extends IPackageManager.Stub return; } + // Verify if we need to dexopt the app. + // + // NOTE: it is *important* to call dexopt after doRename which will sync the + // package data from PackageParser.Package and its corresponding ApplicationInfo. + // + // We only need to dexopt if the package meets ALL of the following conditions: + // 1) it is not forward locked. + // 2) it is not on on an external ASEC container. + // 3) it is not an instant app or if it is then dexopt is enabled via gservices. + // + // Note that we do not dexopt instant apps by default. dexopt can take some time to + // complete, so we skip this step during installation. Instead, we'll take extra time + // the first time the instant app starts. It's preferred to do it this way to provide + // continuous progress to the useur instead of mysteriously blocking somewhere in the + // middle of running an instant app. The default behaviour can be overridden + // via gservices. + final boolean performDexopt = !forwardLocked + && !pkg.applicationInfo.isExternalAsec() + && (!instantApp || Global.getInt(mContext.getContentResolver(), + Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0); + + if (performDexopt) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Do not run PackageDexOptimizer through the local performDexOpt + // method because `pkg` may not be in `mPackages` yet. + // + // Also, don't fail application installs if the dexopt step fails. + DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName, + REASON_INSTALL, + DexoptOptions.DEXOPT_BOOT_COMPLETE); + mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, + null /* instructionSets */, + getOrCreateCompilerPackageStats(pkg), + mDexManager.getPackageUseInfoOrDefault(pkg.packageName), + dexoptOptions); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // Notify BackgroundDexOptService that the package has been changed. + // If this is an update of a package which used to fail to compile, + // BackgroundDexOptService will remove it from its blacklist. + // TODO: Layering violation + BackgroundDexOptService.notifyPackageChanged(pkg.packageName); + startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags, diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index 0a9480b4bbe8..781216c3c43f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -26,14 +26,19 @@ import dalvik.system.DexFile; public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. static final String REASON_STRINGS[] = { - "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive" + "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive", "shared" }; + static final int REASON_SHARED_INDEX = 6; + // Static block to ensure the strings array is of the right length. static { if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) { throw new IllegalStateException("REASON_STRINGS not correct"); } + if (!"shared".equals(REASON_STRINGS[REASON_SHARED_INDEX])) { + throw new IllegalStateException("REASON_STRINGS not correct because of shared index"); + } } private static String getSystemPropertyName(int reason) { @@ -54,6 +59,8 @@ public class PackageManagerServiceCompilerMapping { // vendor. if ("pm.dexopt.inactive".equals(sysPropName)) { sysPropValue = "verify"; + } else if ("pm.dexopt.shared".equals(sysPropName)) { + sysPropValue = "speed"; } else { sysPropValue = SystemProperties.get(sysPropName); } @@ -61,11 +68,18 @@ public class PackageManagerServiceCompilerMapping { !DexFile.isValidCompilerFilter(sysPropValue)) { throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid " + "(reason " + REASON_STRINGS[reason] + ")"); + } else if (!isFilterAllowedForReason(reason, sysPropValue)) { + throw new IllegalStateException("Value \"" + sysPropValue +"\" not allowed " + + "(reason " + REASON_STRINGS[reason] + ")"); } return sysPropValue; } + private static boolean isFilterAllowedForReason(int reason, String filter) { + return reason != REASON_SHARED_INDEX || !DexFile.isProfileGuidedCompilerFilter(filter); + } + // Check that the properties are set and valid. // Note: this is done in a separate method so this class can be statically initialized. static void checkProperties() { diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java index bc8bf5e1fc4c..c23b03153e58 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java +++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java @@ -20,6 +20,8 @@ import android.content.pm.ApplicationInfo; import android.util.Slog; import android.util.SparseArray; +import com.android.server.pm.PackageDexOptimizer; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -33,7 +35,9 @@ public final class DexoptUtils { /** * Creates the class loader context dependencies for each of the application code paths. * The returned array contains the class loader contexts that needs to be passed to dexopt in - * order to ensure correct optimizations. + * order to ensure correct optimizations. "Code" paths with no actual code, as specified by + * {@param pathsWithCode}, are ignored and will have null as their context in the returned array + * (configuration splits are an example of paths without code). * * A class loader context describes how the class loader chain should be built by dex2oat * in order to ensure that classes are resolved during compilation as they would be resolved @@ -58,7 +62,8 @@ public final class DexoptUtils { * {@link android.app.LoadedApk#makePaths( * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}. */ - public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) { + public static String[] getClassLoaderContexts(ApplicationInfo info, + String[] sharedLibraries, boolean[] pathsWithCode) { // The base class loader context contains only the shared library. String sharedLibrariesClassPath = encodeClasspath(sharedLibraries); String baseApkContextClassLoader = encodeClassLoader( @@ -84,7 +89,7 @@ public final class DexoptUtils { // Index 0 is the class loaded context for the base apk. // Index `i` is the class loader context encoding for split `i`. String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length]; - classLoaderContexts[0] = baseApkContextClassLoader; + classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null; if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) { // If the app didn't request for the splits to be loaded in isolation or if it does not @@ -92,7 +97,15 @@ public final class DexoptUtils { // apk class loader (in the order of their definition). String classpath = sharedLibrariesAndBaseClassPath; for (int i = 1; i < classLoaderContexts.length; i++) { - classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader"); + classLoaderContexts[i] = pathsWithCode[i] + ? encodeClassLoader(classpath, "dalvik.system.PathClassLoader") : null; + // Note that the splits with no code are not removed from the classpath computation. + // i.e. split_n might get the split_n-1 in its classpath dependency even + // if split_n-1 has no code. + // The splits with no code do not matter for the runtime which ignores + // apks without code when doing the classpath checks. As such we could actually + // filter them but we don't do it in order to keep consistency with how the apps + // are loaded. classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]); } } else { @@ -114,9 +127,17 @@ public final class DexoptUtils { String splitDependencyOnBase = encodeClassLoader( sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader"); SparseArray<int[]> splitDependencies = info.splitDependencies; + + // Note that not all splits have dependencies (e.g. configuration splits) + // The splits without dependencies will have classLoaderContexts[config_split_index] + // set to null after this step. for (int i = 1; i < splitDependencies.size(); i++) { - getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache, - splitDependencies, classLoaderContexts, splitDependencyOnBase); + int splitIndex = splitDependencies.keyAt(i); + if (pathsWithCode[splitIndex]) { + // Compute the class loader context only for the splits with code. + getParentDependencies(splitIndex, splitClassLoaderEncodingCache, + splitDependencies, classLoaderContexts, splitDependencyOnBase); + } } // At this point classLoaderContexts contains only the parent dependencies. @@ -124,8 +145,17 @@ public final class DexoptUtils { // come first in the context. for (int i = 1; i < classLoaderContexts.length; i++) { String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader"); - classLoaderContexts[i] = encodeClassLoaderChain( - splitClassLoader, classLoaderContexts[i]); + if (pathsWithCode[i]) { + // If classLoaderContexts[i] is null it means that the split does not have + // any dependency. In this case its context equals its declared class loader. + classLoaderContexts[i] = classLoaderContexts[i] == null + ? splitClassLoader + : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i]); + } else { + // This is a split without code, it has no dependency and it is not compiled. + // Its context will be null. + classLoaderContexts[i] = null; + } } } @@ -208,10 +238,15 @@ public final class DexoptUtils { /** * Encodes a single class loader dependency starting from {@param path} and * {@param classLoaderName}. + * When classpath is {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns + * the same. This special property is used only during OTA. * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. */ - private static String encodeClassLoader(String classpath, String classLoaderName) { + /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) { + if (classpath.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { + return classpath; + } String classLoaderDexoptEncoding = classLoaderName; if ("dalvik.system.PathClassLoader".equals(classLoaderName)) { classLoaderDexoptEncoding = "PCL"; @@ -223,10 +258,17 @@ public final class DexoptUtils { /** * Links to dependencies together in a format accepted by dexopt. + * For the special case when either of cl1 or cl2 equals + * {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns the same. This + * property is used only during OTA. * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split * dependencies {@see encodeClassLoader} separated by ';'. */ - private static String encodeClassLoaderChain(String cl1, String cl2) { + /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) { + if (cl1.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK) || + cl2.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { + return PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; + } if (cl1.isEmpty()) return cl2; if (cl2.isEmpty()) return cl1; return cl1 + ";" + cl2; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index ff7bd723d23d..34dc1adf3430 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -16,10 +16,14 @@ package com.android.server.pm.dex; +import com.android.server.pm.PackageDexOptimizer; + +import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.content.pm.ApplicationInfo; import android.support.test.filters.SmallTest; @@ -46,24 +50,46 @@ public class DexoptUtilsTest { private static final String DELEGATE_LAST_CLASS_LOADER_NAME = DelegateLastClassLoader.class.getName(); - private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits, + private static class TestData { + ApplicationInfo info; + boolean[] pathsWithCode; + } + + private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, boolean addSplitDependencies) { ApplicationInfo ai = new ApplicationInfo(); String codeDir = "/data/app/mock.android.com"; ai.setBaseCodePath(codeDir + "/base.dex"); ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; + boolean[] pathsWithCode; + if (!addSplits) { + pathsWithCode = new boolean[] {true}; + } else { + pathsWithCode = new boolean[9]; + Arrays.fill(pathsWithCode, true); + pathsWithCode[7] = false; // config split - if (addSplits) { ai.setSplitCodePaths(new String[]{ codeDir + "/base-1.dex", codeDir + "/base-2.dex", codeDir + "/base-3.dex", codeDir + "/base-4.dex", codeDir + "/base-5.dex", - codeDir + "/base-6.dex"}); - + codeDir + "/base-6.dex", + codeDir + "/config-split-7.dex", + codeDir + "/feature-no-deps.dex"}); + + String[] splitClassLoaderNames = new String[]{ + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + null, // A null class loader name should default to PathClassLoader. + null, // The config split gets a null class loader. + null}; // The feature split with no dependency and no specified class loader. if (addSplitDependencies) { - ai.splitDependencies = new SparseArray<>(6 + 1); + ai.splitDependencies = new SparseArray<>(splitClassLoaderNames.length + 1); ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 @@ -71,18 +97,24 @@ public class DexoptUtilsTest { ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 + // Do not add the config split to the dependency list. + // Do not add the feature split with no dependency to the dependency list. } } - return ai; + TestData data = new TestData(); + data.info = ai; + data.pathsWithCode = pathsWithCode; + return data; } @Test public void testSplitChain() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); @@ -91,15 +123,18 @@ public class DexoptUtilsTest { assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void testSplitChainNoSplitDependencies() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]); assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]); @@ -111,15 +146,21 @@ public class DexoptUtilsTest { assertEquals( "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency } @Test public void testSplitChainNoIsolationNoSharedLibrary() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); - ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + data.info.privateFlags = data.info.privateFlags + & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[]", contexts[0]); assertEquals("PCL[base.dex]", contexts[1]); assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); @@ -129,14 +170,20 @@ public class DexoptUtilsTest { assertEquals( "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency } + @Test public void testSplitChainNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, true, true); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[]", contexts[0]); assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]); assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]); @@ -144,16 +191,19 @@ public class DexoptUtilsTest { assertEquals("PCL[];PCL[base.dex]", contexts[4]); assertEquals("PCL[];PCL[base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void testSplitChainWithNullPrimaryClassLoader() { // A null classLoaderName should mean PathClassLoader. - ApplicationInfo ai = createMockApplicationInfo(null, true, true); + TestData data = createMockApplicationInfo(null, true, true); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); @@ -161,13 +211,16 @@ public class DexoptUtilsTest { assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void tesNoSplits() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); @@ -175,9 +228,10 @@ public class DexoptUtilsTest { @Test public void tesNoSplitsNullClassLoaderName() { - ApplicationInfo ai = createMockApplicationInfo(null, false, false); + TestData data = createMockApplicationInfo(null, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); @@ -185,10 +239,11 @@ public class DexoptUtilsTest { @Test public void tesNoSplitDelegateLast() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); @@ -196,8 +251,9 @@ public class DexoptUtilsTest { @Test public void tesNoSplitsNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[]", contexts[0]); @@ -205,15 +261,55 @@ public class DexoptUtilsTest { @Test public void tesNoSplitDelegateLastNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, false, false); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[]", contexts[0]); } @Test + public void testContextWithNoCode() { + TestData data = createMockApplicationInfo(null, true, false); + Arrays.fill(data.pathsWithCode, false); + + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals(null, contexts[1]); + assertEquals(null, contexts[2]); + assertEquals(null, contexts[3]); + assertEquals(null, contexts[4]); + assertEquals(null, contexts[5]); + assertEquals(null, contexts[6]); + assertEquals(null, contexts[7]); + } + + @Test + public void testContextBaseNoCode() { + TestData data = createMockApplicationInfo(null, true, true); + data.pathsWithCode[0] = false; + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); + } + + @Test public void testProcessContextForDexLoad() { List<String> classLoaders = Arrays.asList( DELEGATE_LAST_CLASS_LOADER_NAME, @@ -226,8 +322,8 @@ public class DexoptUtilsTest { String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); assertNotNull(context); assertEquals(2, context.length); - assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); - assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); + assertEquals("PCL[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); + assertEquals("PCL[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); } @Test @@ -276,4 +372,49 @@ public class DexoptUtilsTest { } assertTrue(gotException); } + + @Test + public void testEncodeClassLoader() { + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.PathClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.DexClassLoader")); + assertEquals("DLC[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.DelegateLastClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", null)); + assertEquals("abc[xyz]", DexoptUtils.encodeClassLoader("xyz", "abc")); + + try { + DexoptUtils.encodeClassLoader(null, "abc"); + fail(); // Exception should be caught. + } catch (NullPointerException expected) {} + } + + @Test + public void testEncodeClassLoaderChain() { + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain( + SKIP_SHARED_LIBRARY_CHECK, "PCL[a]")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", + SKIP_SHARED_LIBRARY_CHECK)); + assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]", + "DLC[b]")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", + SKIP_SHARED_LIBRARY_CHECK)); + + try { + DexoptUtils.encodeClassLoaderChain("a", null); + fail(); // exception is expected + } catch (NullPointerException expected) {} + + try { + DexoptUtils.encodeClassLoaderChain(null, "b"); + fail(); // exception is expected + } catch (NullPointerException expected) {} + } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 221986395fd3..946d237f5ba6 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -902,7 +902,7 @@ public class UsbDeviceManager { updateCurrentAccessory(); } if (mBootCompleted) { - if (!mConnected) { + if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) { // restore defaults when USB is disconnected setEnabledFunctions(null, !mAdbEnabled, false); } diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java index e64e815248e4..1b50861707ed 100644 --- a/telephony/java/android/telephony/Telephony.java +++ b/telephony/java/android/telephony/Telephony.java @@ -3250,4 +3250,69 @@ public final class Telephony { */ public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation"; } + + /** + * Contains carrier identification information. + * @hide + */ + public static final class CarrierIdentification implements BaseColumns { + /** + * Numeric operator ID (as String). {@code MCC + MNC} + * <P>Type: TEXT </P> + */ + public static final String MCCMNC = "mccmnc"; + + /** + * Group id level 1 (as String). + * <P>Type: TEXT </P> + */ + public static final String GID1 = "gid1"; + + /** + * Group id level 2 (as String). + * <P>Type: TEXT </P> + */ + public static final String GID2 = "gid2"; + + /** + * Public Land Mobile Network name. + * <P>Type: TEXT </P> + */ + public static final String PLMN = "plmn"; + + /** + * Prefix xpattern of IMSI (International Mobile Subscriber Identity). + * <P>Type: TEXT </P> + */ + public static final String IMSI_PREFIX_XPATTERN = "imsi_prefix_xpattern"; + + /** + * Service Provider Name. + * <P>Type: TEXT </P> + */ + public static final String SPN = "spn"; + + /** + * Prefer APN name. + * <P>Type: TEXT </P> + */ + public static final String APN = "apn"; + + /** + * User facing carrier name. + * <P>Type: TEXT </P> + */ + public static final String NAME = "carrier_name"; + + /** + * A unique carrier id + * <P>Type: INTEGER </P> + */ + public static final String CID = "carrier_id"; + + /** + * The {@code content://} URI for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://carrier_identification"); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 450f485317ef..73f59379ff59 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2133,13 +2133,16 @@ public class TelephonyManager { * @hide */ public String getSimOperatorNumeric() { - int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + int subId = mSubId; if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultSmsSubscriptionId(); + subId = SubscriptionManager.getDefaultDataSubscriptionId(); if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultVoiceSubscriptionId(); + subId = SubscriptionManager.getDefaultSmsSubscriptionId(); if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultSubscriptionId(); + subId = SubscriptionManager.getDefaultVoiceSubscriptionId(); + if (!SubscriptionManager.isUsableSubIdValue(subId)) { + subId = SubscriptionManager.getDefaultSubscriptionId(); + } } } } |