diff options
166 files changed, 4182 insertions, 570 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6ac7132166f7..593432e9c0c7 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2341,8 +2341,10 @@ public class DevicePolicyManager { public static final int WIPE_RESET_PROTECTION_DATA = 0x0002; /** - * Ask the user data be wiped. Wiping the primary user will cause the device to reboot, erasing - * all user data while next booting up. + * Ask that all user data be wiped. If called as a secondary user, the user will be removed and + * other users will remain unaffected. Calling from the primary user will cause the device to + * reboot, erasing all device data - including all the secondary users and their data - while + * booting up. * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to * be able to call this method; if it has not, a security exception will be thrown. diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java index 953b051ae6da..f47ad1fb03e9 100644 --- a/core/java/android/content/pm/IntentFilterVerificationInfo.java +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -60,9 +60,9 @@ public final class IntentFilterVerificationInfo implements Parcelable { mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } - public IntentFilterVerificationInfo(String packageName, ArrayList<String> domains) { + public IntentFilterVerificationInfo(String packageName, ArraySet<String> domains) { mPackageName = packageName; - mDomains.addAll(domains); + mDomains = domains; mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } @@ -96,8 +96,8 @@ public final class IntentFilterVerificationInfo implements Parcelable { return mDomains; } - public void setDomains(ArrayList<String> list) { - mDomains = new ArraySet<>(list); + public void setDomains(ArraySet<String> list) { + mDomains = list; } public String getDomainsString() { diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 91fe3524e1ad..c180c7f5ef4d 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -714,7 +714,7 @@ public class Build { public static final int N = 24; /** - * N MR1: Still ¯\_(シ)_/¯. + * N MR1: Nougat++. */ public static final int N_MR1 = 25; } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java new file mode 100644 index 000000000000..5ff79f752c39 --- /dev/null +++ b/core/java/android/os/HwBinder.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 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 android.os; + +import libcore.util.NativeAllocationRegistry; + +/** @hide */ +public abstract class HwBinder implements IHwBinder { + private static final String TAG = "HwBinder"; + + private static final NativeAllocationRegistry sNativeRegistry; + + public HwBinder() { + native_setup(); + + sNativeRegistry.registerNativeAllocation( + this, + mNativeContext); + } + + public final native void transact( + int code, HwParcel request, HwParcel reply, int flags); + + public abstract void onTransact( + int code, HwParcel request, HwParcel reply, int flags); + + public native final void registerService(String serviceName); + public static native final IHwBinder getService(String serviceName); + + // Returns address of the "freeFunction". + private static native final long native_init(); + + private native final void native_setup(); + + static { + long freeFunction = native_init(); + + sNativeRegistry = new NativeAllocationRegistry( + HwBinder.class.getClassLoader(), + freeFunction, + 128 /* size */); + } + + private long mNativeContext; +} diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java new file mode 100644 index 000000000000..fe7cdccd3e09 --- /dev/null +++ b/core/java/android/os/HwParcel.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 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 android.os; + +import libcore.util.NativeAllocationRegistry; + +/** @hide */ +public class HwParcel { + private static final String TAG = "HwParcel"; + + public static final int STATUS_SUCCESS = 0; + public static final int STATUS_ERROR = -1; + + private static final NativeAllocationRegistry sNativeRegistry; + + private HwParcel(boolean allocate) { + native_setup(allocate); + + sNativeRegistry.registerNativeAllocation( + this, + mNativeContext); + } + + public HwParcel() { + native_setup(true /* allocate */); + + sNativeRegistry.registerNativeAllocation( + this, + mNativeContext); + } + + public native final void writeInterfaceToken(String interfaceName); + public native final void writeInt8(byte val); + public native final void writeInt16(short val); + public native final void writeInt32(int val); + public native final void writeInt64(long val); + public native final void writeFloat(float val); + public native final void writeDouble(double val); + public native final void writeString(String val); + + public native final void writeInt8Array(int size, byte[] val); + public native final void writeInt8Vector(byte[] val); + public native final void writeInt16Array(int size, short[] val); + public native final void writeInt16Vector(short[] val); + public native final void writeInt32Array(int size, int[] val); + public native final void writeInt32Vector(int[] val); + public native final void writeInt64Array(int size, long[] val); + public native final void writeInt64Vector(long[] val); + public native final void writeFloatArray(int size, float[] val); + public native final void writeFloatVector(float[] val); + public native final void writeDoubleArray(int size, double[] val); + public native final void writeDoubleVector(double[] val); + public native final void writeStringArray(int size, String[] val); + public native final void writeStringVector(String[] val); + + public native final void writeStrongBinder(IHwBinder binder); + + public native final void enforceInterface(String interfaceName); + public native final byte readInt8(); + public native final short readInt16(); + public native final int readInt32(); + public native final long readInt64(); + public native final float readFloat(); + public native final double readDouble(); + public native final String readString(); + + public native final byte[] readInt8Array(int size); + public native final byte[] readInt8Vector(); + public native final short[] readInt16Array(int size); + public native final short[] readInt16Vector(); + public native final int[] readInt32Array(int size); + public native final int[] readInt32Vector(); + public native final long[] readInt64Array(int size); + public native final long[] readInt64Vector(); + public native final float[] readFloatArray(int size); + public native final float[] readFloatVector(); + public native final double[] readDoubleArray(int size); + public native final double[] readDoubleVector(); + public native final String[] readStringArray(int size); + public native final String[] readStringVector(); + + public native final IHwBinder readStrongBinder(); + + public native final void writeStatus(int status); + public native final void verifySuccess(); + public native final void releaseTemporaryStorage(); + + public native final void send(); + + // Returns address of the "freeFunction". + private static native final long native_init(); + + private native final void native_setup(boolean allocate); + + static { + long freeFunction = native_init(); + + sNativeRegistry = new NativeAllocationRegistry( + HwParcel.class.getClassLoader(), + freeFunction, + 128 /* size */); + } + + private long mNativeContext; +} + diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java new file mode 100644 index 000000000000..83866b3cceb7 --- /dev/null +++ b/core/java/android/os/HwRemoteBinder.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 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 android.os; + +import libcore.util.NativeAllocationRegistry; + +/** @hide */ +public class HwRemoteBinder implements IHwBinder { + private static final String TAG = "HwRemoteBinder"; + + private static final NativeAllocationRegistry sNativeRegistry; + + public HwRemoteBinder() { + native_setup_empty(); + + sNativeRegistry.registerNativeAllocation( + this, + mNativeContext); + } + + public IHwInterface queryLocalInterface(String descriptor) { + return null; + } + + public native final void transact( + int code, HwParcel request, HwParcel reply, int flags); + + private static native final long native_init(); + + private native final void native_setup_empty(); + + static { + long freeFunction = native_init(); + + sNativeRegistry = new NativeAllocationRegistry( + HwRemoteBinder.class.getClassLoader(), + freeFunction, + 128 /* size */); + } + + private long mNativeContext; +} diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java new file mode 100644 index 000000000000..88af4fb13710 --- /dev/null +++ b/core/java/android/os/IHwBinder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 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 android.os; + +/** @hide */ +public interface IHwBinder { + // MUST match libhwbinder/IBinder.h definition !!! + public static final int FIRST_CALL_TRANSACTION = 1; + + public void transact( + int code, HwParcel request, HwParcel reply, int flags); + + public IHwInterface queryLocalInterface(String descriptor); +} diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java new file mode 100644 index 000000000000..7c5ac6f44a49 --- /dev/null +++ b/core/java/android/os/IHwInterface.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016 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 android.os; + +/** @hide */ +public interface IHwInterface { + public IHwBinder asBinder(); +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 1b37ed47c392..eb6b17e08f46 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1581,6 +1581,15 @@ public interface WindowManager extends ViewManager { public static final int ROTATION_ANIMATION_JUMPCUT = 2; /** + * Value for {@link #rotationAnimation} to specify seamless rotation mode. + * This works like JUMPCUT but will fall back to CROSSFADE if rotation + * can't be applied without pausing the screen. + * + * @hide + */ + public static final int ROTATION_ANIMATION_SEAMLESS = 3; + + /** * Define the exit and entry animations used on this window when the device is rotated. * This only has an affect if the incoming and outgoing topmost * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index f137821faf44..b0d45e1d1db9 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -750,6 +750,14 @@ public class StateMachine { /** The destination state when transitionTo has been invoked */ private State mDestState; + /** + * Indicates if a transition is in progress + * + * This will be true for all calls of State.exit and all calls of State.enter except for the + * last enter call for the current destination state. + */ + private boolean mTransitionInProgress = false; + /** The list of deferred messages */ private ArrayList<Message> mDeferredMessages = new ArrayList<Message>(); @@ -862,6 +870,8 @@ public class StateMachine { * invoke the exit methods then the enter methods. */ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState); + // flag is cleared in invokeEnterMethods before entering the target state + mTransitionInProgress = true; invokeExitMethods(commonStateInfo); int stateStackEnteringIndex = moveTempStateStackToStateStack(); invokeEnterMethods(stateStackEnteringIndex); @@ -1017,10 +1027,15 @@ public class StateMachine { */ private final void invokeEnterMethods(int stateStackEnteringIndex) { for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { + if (stateStackEnteringIndex == mStateStackTopIndex) { + // Last enter state for transition + mTransitionInProgress = false; + } if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName()); mStateStack[i].state.enter(); mStateStack[i].active = true; } + mTransitionInProgress = false; // ensure flag set to false if no methods called } /** @@ -1196,6 +1211,10 @@ public class StateMachine { /** @see StateMachine#transitionTo(IState) */ private final void transitionTo(IState destState) { + if (mTransitionInProgress) { + Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " + + mDestState + ", new target state=" + destState); + } mDestState = (State) destState; if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName()); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 2ea80ff77c61..2a9498cf605b 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -79,6 +79,9 @@ LOCAL_SRC_FILES:= \ android_text_AndroidBidi.cpp \ android_text_StaticLayout.cpp \ android_os_Debug.cpp \ + android_os_HwBinder.cpp \ + android_os_HwParcel.cpp \ + android_os_HwRemoteBinder.cpp \ android_os_MemoryFile.cpp \ android_os_MessageQueue.cpp \ android_os_Parcel.cpp \ @@ -177,7 +180,8 @@ LOCAL_SRC_FILES:= \ com_android_internal_os_PathClassLoaderFactory.cpp \ com_android_internal_os_Zygote.cpp \ com_android_internal_util_VirtualRefBasePtr.cpp \ - com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp + com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \ + hwbinder/EphemeralStorage.cpp \ LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ @@ -260,6 +264,7 @@ LOCAL_SHARED_LIBRARIES := \ libradio_metadata \ libnativeloader \ libmemunreachable \ + libhwbinder \ LOCAL_SHARED_LIBRARIES += \ libhwui \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index be53cf343dd7..dc2748555c68 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -155,6 +155,9 @@ extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); extern int register_android_nio_utils(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); +extern int register_android_os_HwBinder(JNIEnv *env); +extern int register_android_os_HwParcel(JNIEnv *env); +extern int register_android_os_HwRemoteBinder(JNIEnv *env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); extern int register_android_os_SELinux(JNIEnv* env); @@ -1286,6 +1289,9 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_SystemProperties), REG_JNI(register_android_os_Binder), REG_JNI(register_android_os_Parcel), + REG_JNI(register_android_os_HwBinder), + REG_JNI(register_android_os_HwParcel), + REG_JNI(register_android_os_HwRemoteBinder), REG_JNI(register_android_nio_utils), REG_JNI(register_android_graphics_Canvas), REG_JNI(register_android_graphics_Graphics), diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp new file mode 100644 index 000000000000..a45e1eef2e8f --- /dev/null +++ b/core/jni/android_os_HwBinder.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2016 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "android_os_HwBinder" +#include <android-base/logging.h> + +#include "android_os_HwBinder.h" + +#include "android_os_HwParcel.h" +#include "android_os_HwRemoteBinder.h" + +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include <hwbinder/IServiceManager.h> +#include <hwbinder/ProcessState.h> +#include <hwbinder/Status.h> +#include <nativehelper/ScopedLocalRef.h> + +#include "core_jni_helpers.h" + +using android::AndroidRuntime; + +#define PACKAGE_PATH "android/os" +#define CLASS_NAME "HwBinder" +#define CLASS_PATH PACKAGE_PATH "/" CLASS_NAME + +namespace android { + +static struct fields_t { + jfieldID contextID; + jmethodID onTransactID; + +} gFields; + +// static +void JHwBinder::InitClass(JNIEnv *env) { + ScopedLocalRef<jclass> clazz( + env, FindClassOrDie(env, CLASS_PATH)); + + gFields.contextID = + GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J"); + + gFields.onTransactID = + GetMethodIDOrDie( + env, + clazz.get(), + "onTransact", + "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V"); +} + +// static +sp<JHwBinder> JHwBinder::SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinder> &context) { + sp<JHwBinder> old = + (JHwBinder *)env->GetLongField(thiz, gFields.contextID); + + if (context != NULL) { + context->incStrong(NULL /* id */); + } + + if (old != NULL) { + old->decStrong(NULL /* id */); + } + + env->SetLongField(thiz, gFields.contextID, (long)context.get()); + + return old; +} + +// static +sp<JHwBinder> JHwBinder::GetNativeContext( + JNIEnv *env, jobject thiz) { + return (JHwBinder *)env->GetLongField(thiz, gFields.contextID); +} + +JHwBinder::JHwBinder(JNIEnv *env, jobject thiz) { + jclass clazz = env->GetObjectClass(thiz); + CHECK(clazz != NULL); + + mClass = (jclass)env->NewGlobalRef(clazz); + mObject = env->NewWeakGlobalRef(thiz); +} + +JHwBinder::~JHwBinder() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + + env->DeleteGlobalRef(mClass); + mClass = NULL; +} + +status_t JHwBinder::onTransact( + uint32_t code, + const hardware::Parcel &data, + hardware::Parcel *reply, + uint32_t flags, + TransactCallback callback) { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + ScopedLocalRef<jobject> requestObj(env, JHwParcel::NewObject(env)); + JHwParcel::GetNativeContext(env, requestObj.get())->setParcel( + const_cast<hardware::Parcel *>(&data), false /* assumeOwnership */); + + ScopedLocalRef<jobject> replyObj(env, JHwParcel::NewObject(env)); + + sp<JHwParcel> replyContext = + JHwParcel::GetNativeContext(env, replyObj.get()); + + replyContext->setParcel(reply, false /* assumeOwnership */); + replyContext->setTransactCallback(callback); + + env->CallVoidMethod( + mObject, + gFields.onTransactID, + code, + requestObj.get(), + replyObj.get(), + flags); + + status_t err = OK; + + if (!replyContext->wasSent()) { + // The implementation never finished the transaction. + err = UNKNOWN_ERROR; // XXX special error code instead? + + reply->setDataPosition(0 /* pos */); + } + + // Release all temporary storage now that scatter-gather data + // has been consolidated, either by calling the TransactCallback, + // if wasSent() == true or clearing the reply parcel (setDataOffset above). + replyContext->getStorage()->release(env); + + // We cannot permanently pass ownership of "data" and "reply" over to their + // Java object wrappers (we don't own them ourselves). + + JHwParcel::GetNativeContext(env, requestObj.get())->setParcel( + NULL /* parcel */, false /* assumeOwnership */); + + replyContext->setParcel( + NULL /* parcel */, false /* assumeOwnership */); + + return err; +} + +} // namespace android + +//////////////////////////////////////////////////////////////////////////////// + +using namespace android; + +static void releaseNativeContext(void *nativeContext) { + sp<JHwBinder> binder = (JHwBinder *)nativeContext; + + if (binder != NULL) { + binder->decStrong(NULL /* id */); + } +} + +static jlong JHwBinder_native_init(JNIEnv *env) { + JHwBinder::InitClass(env); + + return reinterpret_cast<jlong>(&releaseNativeContext); +} + +static void JHwBinder_native_setup(JNIEnv *env, jobject thiz) { + sp<JHwBinder> context = new JHwBinder(env, thiz); + + JHwBinder::SetNativeContext(env, thiz, context); +} + +static void JHwBinder_native_transact( + JNIEnv * /* env */, + jobject /* thiz */, + jint /* code */, + jobject /* requestObj */, + jobject /* replyObj */, + jint /* flags */) { + CHECK(!"Should not be here"); +} + +static void JHwBinder_native_registerService( + JNIEnv *env, jobject thiz, jstring serviceNameObj) { + if (serviceNameObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + const jchar *serviceName = env->GetStringCritical(serviceNameObj, NULL); + + if (serviceName == NULL) { + return; // XXX exception already pending? + } + + const hardware::hidl_version kVersion = hardware::make_hidl_version(1, 0); + + sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz); + + status_t err = hardware::defaultServiceManager()->addService( + String16(reinterpret_cast<const char16_t *>(serviceName)), + binder, + kVersion); + + env->ReleaseStringCritical(serviceNameObj, serviceName); + serviceName = NULL; + + if (err == OK) { + LOG(INFO) << "Starting thread pool."; + ::android::hardware::ProcessState::self()->startThreadPool(); + } + + signalExceptionForError(env, err); +} + +static jobject JHwBinder_native_getService( + JNIEnv *env, jclass /* clazzObj */, jstring serviceNameObj) { + if (serviceNameObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return NULL; + } + + const jchar *serviceName = env->GetStringCritical(serviceNameObj, NULL); + + if (serviceName == NULL) { + return NULL; // XXX exception already pending? + } + + const hardware::hidl_version kVersion = hardware::make_hidl_version(1, 0); + + LOG(INFO) << "looking for service '" + << String8(String16( + reinterpret_cast<const char16_t *>(serviceName))).string() + << "'"; + + sp<hardware::IBinder> service = + hardware::defaultServiceManager()->getService( + String16(reinterpret_cast<const char16_t *>(serviceName)), + kVersion); + + env->ReleaseStringCritical(serviceNameObj, serviceName); + serviceName = NULL; + + if (service == NULL) { + signalExceptionForError(env, NAME_NOT_FOUND); + return NULL; + } + + return JHwRemoteBinder::NewObject(env, service); +} + +static JNINativeMethod gMethods[] = { + { "native_init", "()J", (void *)JHwBinder_native_init }, + { "native_setup", "()V", (void *)JHwBinder_native_setup }, + + { "transact", + "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V", + (void *)JHwBinder_native_transact }, + + { "registerService", "(Ljava/lang/String;)V", + (void *)JHwBinder_native_registerService }, + + { "getService", "(Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;", + (void *)JHwBinder_native_getService }, +}; + +namespace android { + +int register_android_os_HwBinder(JNIEnv *env) { + return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods)); +} + +} // namespace android diff --git a/core/jni/android_os_HwBinder.h b/core/jni/android_os_HwBinder.h new file mode 100644 index 000000000000..2ebc38164da8 --- /dev/null +++ b/core/jni/android_os_HwBinder.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _ANDROID_OS_HW_BINDER_H +#define _ANDROID_OS_HW_BINDER_H + +#include <android-base/macros.h> +#include <hwbinder/Binder.h> +#include <jni.h> +#include <utils/RefBase.h> + +namespace android { + +struct JHwBinder : public hardware::BBinder { + static void InitClass(JNIEnv *env); + + static sp<JHwBinder> SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwBinder> &context); + + static sp<JHwBinder> GetNativeContext(JNIEnv *env, jobject thiz); + + JHwBinder(JNIEnv *env, jobject thiz); + +protected: + virtual ~JHwBinder(); + + virtual status_t onTransact( + uint32_t code, + const hardware::Parcel &data, + hardware::Parcel *reply, + uint32_t flags, + TransactCallback callback); + +private: + jclass mClass; + jobject mObject; + + DISALLOW_COPY_AND_ASSIGN(JHwBinder); +}; + +int register_android_os_HwBinder(JNIEnv *env); + +} // namespace android + +#endif // _ANDROID_OS_HW_BINDER_H + + diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp new file mode 100644 index 000000000000..0202303b10ea --- /dev/null +++ b/core/jni/android_os_HwParcel.cpp @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2016 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "android_os_HwParcel" +#include <android-base/logging.h> + +#include "android_os_HwParcel.h" + +#include "android_os_HwBinder.h" +#include "android_os_HwRemoteBinder.h" + +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include <hwbinder/Status.h> +#include <nativehelper/ScopedLocalRef.h> + +#include "core_jni_helpers.h" + +using android::AndroidRuntime; + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +#define PACKAGE_PATH "android/os" +#define CLASS_NAME "HwParcel" +#define CLASS_PATH PACKAGE_PATH "/" CLASS_NAME + +namespace android { + +static struct fields_t { + jfieldID contextID; + jmethodID constructID; + +} gFields; + +void signalExceptionForError(JNIEnv *env, status_t err) { + switch (err) { + case OK: + break; + + case NO_MEMORY: + { + jniThrowException(env, "java/lang/OutOfMemoryError", NULL); + break; + } + + case INVALID_OPERATION: + { + jniThrowException( + env, "java/lang/UnsupportedOperationException", NULL); + break; + } + + case BAD_VALUE: + { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + break; + } + + case BAD_INDEX: + { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL); + break; + } + + case BAD_TYPE: + { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + break; + } + + case NAME_NOT_FOUND: + { + jniThrowException(env, "java/util/NoSuchElementException", NULL); + break; + } + + case PERMISSION_DENIED: + { + jniThrowException(env, "java/lang/SecurityException", NULL); + break; + } + + case NO_INIT: + { + jniThrowException( + env, "java/lang/RuntimeException", "Not initialized"); + break; + } + + case ALREADY_EXISTS: + { + jniThrowException( + env, "java/lang/RuntimeException", "Item already exists"); + break; + } + + default: + { + jniThrowException( + env, "java/lang/RuntimeException", "Unknown error"); + + break; + } + } +} + +// static +void JHwParcel::InitClass(JNIEnv *env) { + ScopedLocalRef<jclass> clazz( + env, FindClassOrDie(env, CLASS_PATH)); + + gFields.contextID = + GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J"); + + gFields.constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "(Z)V"); +} + +// static +sp<JHwParcel> JHwParcel::SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwParcel> &context) { + sp<JHwParcel> old = (JHwParcel *)env->GetLongField(thiz, gFields.contextID); + + if (context != NULL) { + context->incStrong(NULL /* id */); + } + + if (old != NULL) { + old->decStrong(NULL /* id */); + } + + env->SetLongField(thiz, gFields.contextID, (long)context.get()); + + return old; +} + +// static +sp<JHwParcel> JHwParcel::GetNativeContext(JNIEnv *env, jobject thiz) { + return (JHwParcel *)env->GetLongField(thiz, gFields.contextID); +} + +JHwParcel::JHwParcel(JNIEnv *env, jobject thiz) + : mParcel(NULL), + mOwnsParcel(false), + mTransactCallback(nullptr), + mWasSent(false) { + jclass clazz = env->GetObjectClass(thiz); + CHECK(clazz != NULL); + + mClass = (jclass)env->NewGlobalRef(clazz); + mObject = env->NewWeakGlobalRef(thiz); +} + +JHwParcel::~JHwParcel() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + mStorage.release(env); + + setParcel(NULL, false /* assumeOwnership */); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + + env->DeleteGlobalRef(mClass); + mClass = NULL; +} + +hardware::Parcel *JHwParcel::getParcel() { + return mParcel; +} + +EphemeralStorage *JHwParcel::getStorage() { + return &mStorage; +} + +void JHwParcel::setParcel(hardware::Parcel *parcel, bool assumeOwnership) { + if (mParcel && mOwnsParcel) { + delete mParcel; + } + + mParcel = parcel; + mOwnsParcel = assumeOwnership; +} + +// static +jobject JHwParcel::NewObject(JNIEnv *env) { + ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH)); + + return env->NewObject( + clazz.get(), gFields.constructID, false /* allocate */); +} + +void JHwParcel::setTransactCallback( + ::android::hardware::IBinder::TransactCallback cb) { + mTransactCallback = cb; +} + +void JHwParcel::send() { + CHECK(mTransactCallback != nullptr); + CHECK(mParcel != nullptr); + + mTransactCallback(*mParcel); + mTransactCallback = nullptr; + + mWasSent = true; +} + +bool JHwParcel::wasSent() const { + return mWasSent; +} + +} // namespace android + +//////////////////////////////////////////////////////////////////////////////// + +using namespace android; + +static void releaseNativeContext(void *nativeContext) { + sp<JHwParcel> parcel = (JHwParcel *)nativeContext; + + if (parcel != NULL) { + parcel->decStrong(NULL /* id */); + } +} + +static jlong JHwParcel_native_init(JNIEnv *env) { + JHwParcel::InitClass(env); + + return reinterpret_cast<jlong>(&releaseNativeContext); +} + +static void JHwParcel_native_setup( + JNIEnv *env, jobject thiz, jboolean allocate) { + sp<JHwParcel> context = new JHwParcel(env, thiz); + + if (allocate) { + context->setParcel(new hardware::Parcel, true /* assumeOwnership */); + } + + JHwParcel::SetNativeContext(env, thiz, context); +} + +static void JHwParcel_native_writeInterfaceToken( + JNIEnv *env, jobject thiz, jstring interfaceNameObj) { + if (interfaceNameObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + const jchar *interfaceName = env->GetStringCritical(interfaceNameObj, NULL); + if (interfaceName) { + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + status_t err = parcel->writeInterfaceToken( + String16( + reinterpret_cast<const char16_t *>(interfaceName), + env->GetStringLength(interfaceNameObj))); + + env->ReleaseStringCritical(interfaceNameObj, interfaceName); + interfaceName = NULL; + + signalExceptionForError(env, err); + } +} + +static void JHwParcel_native_enforceInterface( + JNIEnv *env, jobject thiz, jstring interfaceNameObj) { + // XXX original binder Parcel enforceInterface implementation does some + // mysterious things regarding strictModePolicy(), figure out if we need + // that here as well. + if (interfaceNameObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + const jchar *interfaceName = env->GetStringCritical(interfaceNameObj, NULL); + if (interfaceName) { + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + bool valid = parcel->enforceInterface( + String16( + reinterpret_cast<const char16_t *>(interfaceName), + env->GetStringLength(interfaceNameObj))); + + env->ReleaseStringCritical(interfaceNameObj, interfaceName); + interfaceName = NULL; + + if (!valid) { + jniThrowException( + env, + "java/lang/SecurityException", + "HWBinder invocation to an incorrect interface"); + } + } +} + +#define DEFINE_PARCEL_WRITER(Suffix,Type) \ +static void JHwParcel_native_write ## Suffix( \ + JNIEnv *env, jobject thiz, Type val) { \ + hardware::Parcel *parcel = \ + JHwParcel::GetNativeContext(env, thiz)->getParcel(); \ + \ + status_t err = parcel->write ## Suffix(val); \ + signalExceptionForError(env, err); \ +} + +#define DEFINE_PARCEL_READER(Suffix,Type) \ +static Type JHwParcel_native_read ## Suffix( \ + JNIEnv *env, jobject thiz) { \ + hardware::Parcel *parcel = \ + JHwParcel::GetNativeContext(env, thiz)->getParcel(); \ + \ + Type val; \ + status_t err = parcel->read ## Suffix(&val); \ + signalExceptionForError(env, err); \ + \ + return val; \ +} + +DEFINE_PARCEL_WRITER(Int8,jbyte) +DEFINE_PARCEL_WRITER(Int16,jshort) +DEFINE_PARCEL_WRITER(Int32,jint) +DEFINE_PARCEL_WRITER(Int64,jlong) +DEFINE_PARCEL_WRITER(Float,jfloat) +DEFINE_PARCEL_WRITER(Double,jdouble) + +DEFINE_PARCEL_READER(Int8,jbyte) +DEFINE_PARCEL_READER(Int16,jshort) +DEFINE_PARCEL_READER(Int32,jint) +DEFINE_PARCEL_READER(Int64,jlong) +DEFINE_PARCEL_READER(Float,jfloat) +DEFINE_PARCEL_READER(Double,jdouble) + +static void JHwParcel_native_writeStatus( + JNIEnv *env, jobject thiz, jint statusCode) { + using hardware::Status; + + Status status; + switch (statusCode) { + case 0: // kStatusSuccess + status = Status::ok(); + break; + case -1: // kStatusError + status = Status::fromStatusT(UNKNOWN_ERROR); + break; + default: + CHECK(!"Should not be here"); + } + + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + status_t err = status.writeToParcel(parcel); + signalExceptionForError(env, err); +} + +static void JHwParcel_native_verifySuccess(JNIEnv *env, jobject thiz) { + using hardware::Status; + + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + Status status; + status_t err = status.readFromParcel(*parcel); + signalExceptionForError(env, err); +} + +static void JHwParcel_native_releaseTemporaryStorage( + JNIEnv *env, jobject thiz) { + JHwParcel::GetNativeContext(env, thiz)->getStorage()->release(env); +} + +static void JHwParcel_native_send(JNIEnv *env, jobject thiz) { + JHwParcel::GetNativeContext(env, thiz)->send(); +} + +static void JHwParcel_native_writeString( + JNIEnv *env, jobject thiz, jstring valObj) { + if (valObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz); + + const hidl_string *s = + impl->getStorage()->allocTemporaryString(env, valObj); + + hardware::Parcel *parcel = impl->getParcel(); + + size_t parentHandle; + status_t err = parcel->writeBuffer(s, sizeof(*s), &parentHandle); + + if (err == OK) { + err = s->writeEmbeddedToParcel( + parcel, parentHandle, 0 /* parentOffset */); + } + + signalExceptionForError(env, err); +} + +#define DEFINE_PARCEL_ARRAY_WRITER(Suffix,Type) \ +static void JHwParcel_native_write ## Suffix ## Array( \ + JNIEnv *env, jobject thiz, jint size, Type ## Array valObj) { \ + if (valObj == NULL) { \ + jniThrowException(env, "java/lang/NullPointerException", NULL); \ + return; \ + } \ + \ + jsize len = env->GetArrayLength(valObj); \ + \ + if (len != size) { \ + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); \ + return; \ + } \ + \ + sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz); \ + \ + const Type *val = \ + impl->getStorage()->allocTemporary ## Suffix ## Array(env, valObj); \ + \ + hardware::Parcel *parcel = impl->getParcel(); \ + \ + size_t parentHandle; \ + status_t err = parcel->writeBuffer( \ + val, size * sizeof(*val), &parentHandle); \ + \ + signalExceptionForError(env, err); \ +} + +#define DEFINE_PARCEL_VECTOR_WRITER(Suffix,Type) \ +static void JHwParcel_native_write ## Suffix ## Vector( \ + JNIEnv *env, jobject thiz, Type ## Array valObj) { \ + if (valObj == NULL) { \ + jniThrowException(env, "java/lang/NullPointerException", NULL); \ + return; \ + } \ + \ + sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz); \ + \ + const hidl_vec<Type> *vec = \ + impl->getStorage()->allocTemporary ## Suffix ## Vector(env, valObj); \ + \ + hardware::Parcel *parcel = impl->getParcel(); \ + \ + size_t parentHandle; \ + status_t err = parcel->writeBuffer(vec, sizeof(*vec), &parentHandle); \ + \ + if (err == OK) { \ + size_t childHandle; \ + \ + err = vec->writeEmbeddedToParcel( \ + parcel, \ + parentHandle, \ + 0 /* parentOffset */, \ + &childHandle); \ + } \ + \ + signalExceptionForError(env, err); \ +} + +DEFINE_PARCEL_ARRAY_WRITER(Int8,jbyte) +DEFINE_PARCEL_ARRAY_WRITER(Int16,jshort) +DEFINE_PARCEL_ARRAY_WRITER(Int32,jint) +DEFINE_PARCEL_ARRAY_WRITER(Int64,jlong) +DEFINE_PARCEL_ARRAY_WRITER(Float,jfloat) +DEFINE_PARCEL_ARRAY_WRITER(Double,jdouble) + +DEFINE_PARCEL_VECTOR_WRITER(Int8,jbyte) +DEFINE_PARCEL_VECTOR_WRITER(Int16,jshort) +DEFINE_PARCEL_VECTOR_WRITER(Int32,jint) +DEFINE_PARCEL_VECTOR_WRITER(Int64,jlong) +DEFINE_PARCEL_VECTOR_WRITER(Float,jfloat) +DEFINE_PARCEL_VECTOR_WRITER(Double,jdouble) + +static void JHwParcel_native_writeStrongBinder( + JNIEnv *env, jobject thiz, jobject binderObj) { + sp<hardware::IBinder> binder; + if (binderObj != NULL) { + ScopedLocalRef<jclass> hwBinderKlass( + env, FindClassOrDie(env, PACKAGE_PATH "/HwBinder")); + + ScopedLocalRef<jclass> hwRemoteBinderKlass( + env, FindClassOrDie(env, PACKAGE_PATH "/HwRemoteBinder")); + + if (env->IsInstanceOf(binderObj, hwBinderKlass.get())) { + binder = JHwBinder::GetNativeContext(env, binderObj); + } else if (env->IsInstanceOf(binderObj, hwRemoteBinderKlass.get())) { + binder = JHwRemoteBinder::GetNativeContext( + env, binderObj)->getBinder(); + } else { + signalExceptionForError(env, INVALID_OPERATION); + return; + } + } + + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + status_t err = parcel->writeStrongBinder(binder); + signalExceptionForError(env, err); +} + +static jstring MakeStringObjFromHidlString(JNIEnv *env, const hidl_string &s) { + String16 utf16String(s.c_str(), s.size()); + + return env->NewString( + reinterpret_cast<const jchar *>(utf16String.string()), + utf16String.size()); +} + +static jstring JHwParcel_native_readString(JNIEnv *env, jobject thiz) { + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + size_t parentHandle; + + const hidl_string *s = static_cast<const hidl_string *>( + parcel->readBuffer(&parentHandle)); + + if (s == NULL) { + signalExceptionForError(env, UNKNOWN_ERROR); + return NULL; + } + + status_t err = const_cast<hidl_string *>(s)->readEmbeddedFromParcel( + *parcel, parentHandle, 0 /* parentOffset */); + + if (err != OK) { + signalExceptionForError(env, err); + return NULL; + } + + return MakeStringObjFromHidlString(env, *s); +} + +#define DEFINE_PARCEL_ARRAY_READER(Suffix,Type,NewType) \ +static Type ## Array JHwParcel_native_read ## Suffix ## Array( \ + JNIEnv *env, jobject thiz, jint size) { \ + hardware::Parcel *parcel = \ + JHwParcel::GetNativeContext(env, thiz)->getParcel(); \ + \ + size_t parentHandle; \ + const Type *val = static_cast<const Type *>( \ + parcel->readBuffer(&parentHandle)); \ + \ + Type ## Array valObj = env->New ## NewType ## Array(size); \ + env->Set ## NewType ## ArrayRegion(valObj, 0, size, val); \ + \ + return valObj; \ +} + +#define DEFINE_PARCEL_VECTOR_READER(Suffix,Type,NewType) \ +static Type ## Array JHwParcel_native_read ## Suffix ## Vector( \ + JNIEnv *env, jobject thiz) { \ + hardware::Parcel *parcel = \ + JHwParcel::GetNativeContext(env, thiz)->getParcel(); \ + \ + size_t parentHandle; \ + \ + const hidl_vec<Type> *vec = \ + (const hidl_vec<Type> *)parcel->readBuffer(&parentHandle); \ + \ + if (vec == NULL) { \ + signalExceptionForError(env, UNKNOWN_ERROR); \ + return NULL; \ + } \ + \ + size_t childHandle; \ + \ + status_t err = const_cast<hidl_vec<Type> *>(vec) \ + ->readEmbeddedFromParcel( \ + *parcel, \ + parentHandle, \ + 0 /* parentOffset */, \ + &childHandle); \ + \ + if (err != OK) { \ + signalExceptionForError(env, err); \ + return NULL; \ + } \ + \ + Type ## Array valObj = env->New ## NewType ## Array(vec->size()); \ + env->Set ## NewType ## ArrayRegion(valObj, 0, vec->size(), &(*vec)[0]); \ + \ + return valObj; \ +} + +DEFINE_PARCEL_ARRAY_READER(Int8,jbyte,Byte) +DEFINE_PARCEL_ARRAY_READER(Int16,jshort,Short) +DEFINE_PARCEL_ARRAY_READER(Int32,jint,Int) +DEFINE_PARCEL_ARRAY_READER(Int64,jlong,Long) +DEFINE_PARCEL_ARRAY_READER(Float,jfloat,Float) +DEFINE_PARCEL_ARRAY_READER(Double,jdouble,Double) + +DEFINE_PARCEL_VECTOR_READER(Int8,jbyte,Byte) +DEFINE_PARCEL_VECTOR_READER(Int16,jshort,Short) +DEFINE_PARCEL_VECTOR_READER(Int32,jint,Int) +DEFINE_PARCEL_VECTOR_READER(Int64,jlong,Long) +DEFINE_PARCEL_VECTOR_READER(Float,jfloat,Float) +DEFINE_PARCEL_VECTOR_READER(Double,jdouble,Double) + +static jobjectArray MakeStringArray( + JNIEnv *env, const hidl_string *array, size_t size) { + ScopedLocalRef<jclass> stringKlass( + env, + env->FindClass("java/lang/String")); + + // XXX Why can't I use ScopedLocalRef<> for the arrayObj and the stringObjs? + + jobjectArray arrayObj = env->NewObjectArray(size, stringKlass.get(), NULL); + + for (size_t i = 0; i < size; ++i) { + jstring stringObj = MakeStringObjFromHidlString(env, array[i]); + + env->SetObjectArrayElement( + arrayObj, + i, + stringObj); + } + + return arrayObj; +} + +static jobjectArray JHwParcel_native_readStringArray( + JNIEnv *env, jobject thiz, jint size) { + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + size_t parentHandle; + const hidl_string *val = static_cast<const hidl_string *>( + parcel->readBuffer(&parentHandle)); + + if (val == NULL) { + signalExceptionForError(env, UNKNOWN_ERROR); + return NULL; + } + + status_t err = OK; + for (jint i = 0; (err == OK) && (i < size); ++i) { + err = const_cast<hidl_string *>(&val[i]) + ->readEmbeddedFromParcel( + *parcel, + parentHandle, + i * sizeof(hidl_string)); + } + + if (err != OK) { + signalExceptionForError(env, err); + return NULL; + } + + return MakeStringArray(env, val, size); +} + +static void JHwParcel_native_writeStringArray( + JNIEnv *env, jobject thiz, jint size, jobjectArray arrayObj) { + if (arrayObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + jsize len = env->GetArrayLength(arrayObj); + + if (len != size) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return; + } + + sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz); + + hidl_string *strings = impl->getStorage()->allocStringArray(len); + + for (jsize i = 0; i < len; ++i) { + ScopedLocalRef<jstring> stringObj( + env, + (jstring)env->GetObjectArrayElement(arrayObj, i)); + + const hidl_string *s = + impl->getStorage()->allocTemporaryString(env, stringObj.get()); + + strings[i].setToExternal(s->c_str(), s->size()); + } + + hardware::Parcel *parcel = impl->getParcel(); + + size_t parentHandle; + status_t err = parcel->writeBuffer( + strings, sizeof(hidl_string) * len, &parentHandle); + + for (jsize i = 0; (err == OK) && (i < len); ++i) { + err = strings[i].writeEmbeddedToParcel( + parcel, parentHandle, i * sizeof(hidl_string)); + } + + signalExceptionForError(env, err); +} + +static jobjectArray JHwParcel_native_readStringVector( + JNIEnv *env, jobject thiz) { + typedef hidl_vec<hidl_string> string_vec; + + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + size_t parentHandle; + + const string_vec *vec= + (const string_vec *)parcel->readBuffer(&parentHandle); + + if (vec == NULL) { + signalExceptionForError(env, UNKNOWN_ERROR); + return NULL; + } + + size_t childHandle; + status_t err = const_cast<string_vec *>(vec)->readEmbeddedFromParcel( + *parcel, parentHandle, 0 /* parentOffset */, &childHandle); + + for (size_t i = 0; (err == OK) && (i < vec->size()); ++i) { + err = const_cast<hidl_vec<hidl_string> *>(vec) + ->readEmbeddedFromParcel( + *parcel, + childHandle, + i * sizeof(hidl_string), + nullptr /* childHandle */); + } + + if (err != OK) { + signalExceptionForError(env, err); + return NULL; + } + + return MakeStringArray(env, &(*vec)[0], vec->size()); +} + +static void JHwParcel_native_writeStringVector( + JNIEnv *env, jobject thiz, jobjectArray arrayObj) { + typedef hidl_vec<hidl_string> string_vec; + + if (arrayObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + jsize len = env->GetArrayLength(arrayObj); + + sp<JHwParcel> impl = JHwParcel::GetNativeContext(env, thiz); + + string_vec *vec = + (string_vec *)impl->getStorage()->allocTemporaryStorage( + sizeof(string_vec)); + + hidl_string *strings = impl->getStorage()->allocStringArray(len); + vec->setToExternal(strings, len); + + for (jsize i = 0; i < len; ++i) { + ScopedLocalRef<jstring> stringObj( + env, + (jstring)env->GetObjectArrayElement(arrayObj, i)); + + const hidl_string *s = + impl->getStorage()->allocTemporaryString(env, stringObj.get()); + + strings[i].setToExternal(s->c_str(), s->size()); + } + + hardware::Parcel *parcel = impl->getParcel(); + + size_t parentHandle; + status_t err = parcel->writeBuffer(vec, sizeof(*vec), &parentHandle); + + if (err == OK) { + size_t childHandle; + err = vec->writeEmbeddedToParcel( + parcel, + parentHandle, + 0 /* parentOffset */, + &childHandle); + + for (size_t i = 0; (err == OK) && (i < vec->size()); ++i) { + err = (*vec)[i].writeEmbeddedToParcel( + parcel, + childHandle, + i * sizeof(hidl_string)); + } + } + + signalExceptionForError(env, err); +} + +static jobject JHwParcel_native_readStrongBinder(JNIEnv *env, jobject thiz) { + hardware::Parcel *parcel = + JHwParcel::GetNativeContext(env, thiz)->getParcel(); + + sp<hardware::IBinder> binder = parcel->readStrongBinder(); + + if (binder == NULL) { + return NULL; + } + + return JHwRemoteBinder::NewObject(env, binder); +} + +static JNINativeMethod gMethods[] = { + { "native_init", "()J", (void *)JHwParcel_native_init }, + { "native_setup", "(Z)V", (void *)JHwParcel_native_setup }, + + { "writeInterfaceToken", "(Ljava/lang/String;)V", + (void *)JHwParcel_native_writeInterfaceToken }, + + { "writeInt8", "(B)V", (void *)JHwParcel_native_writeInt8 }, + { "writeInt16", "(S)V", (void *)JHwParcel_native_writeInt16 }, + { "writeInt32", "(I)V", (void *)JHwParcel_native_writeInt32 }, + { "writeInt64", "(J)V", (void *)JHwParcel_native_writeInt64 }, + { "writeFloat", "(F)V", (void *)JHwParcel_native_writeFloat }, + { "writeDouble", "(D)V", (void *)JHwParcel_native_writeDouble }, + + { "writeString", "(Ljava/lang/String;)V", + (void *)JHwParcel_native_writeString }, + + { "writeInt8Array", "(I[B)V", (void *)JHwParcel_native_writeInt8Array }, + { "writeInt8Vector", "([B)V", (void *)JHwParcel_native_writeInt8Vector }, + { "writeInt16Array", "(I[S)V", (void *)JHwParcel_native_writeInt16Array }, + { "writeInt16Vector", "([S)V", (void *)JHwParcel_native_writeInt16Vector }, + { "writeInt32Array", "(I[I)V", (void *)JHwParcel_native_writeInt32Array }, + { "writeInt32Vector", "([I)V", (void *)JHwParcel_native_writeInt32Vector }, + { "writeInt64Array", "(I[J)V", (void *)JHwParcel_native_writeInt64Array }, + { "writeInt64Vector", "([J)V", (void *)JHwParcel_native_writeInt64Vector }, + { "writeFloatArray", "(I[F)V", (void *)JHwParcel_native_writeFloatArray }, + { "writeFloatVector", "([F)V", (void *)JHwParcel_native_writeFloatVector }, + { "writeDoubleArray", "(I[D)V", (void *)JHwParcel_native_writeDoubleArray }, + + { "writeDoubleVector", "([D)V", + (void *)JHwParcel_native_writeDoubleVector }, + + { "writeStringArray", "(I[Ljava/lang/String;)V", + (void *)JHwParcel_native_writeStringArray }, + + { "writeStringVector", "([Ljava/lang/String;)V", + (void *)JHwParcel_native_writeStringVector }, + + { "writeStrongBinder", "(L" PACKAGE_PATH "/IHwBinder;)V", + (void *)JHwParcel_native_writeStrongBinder }, + + { "enforceInterface", "(Ljava/lang/String;)V", + (void *)JHwParcel_native_enforceInterface }, + + { "readInt8", "()B", (void *)JHwParcel_native_readInt8 }, + { "readInt16", "()S", (void *)JHwParcel_native_readInt16 }, + { "readInt32", "()I", (void *)JHwParcel_native_readInt32 }, + { "readInt64", "()J", (void *)JHwParcel_native_readInt64 }, + { "readFloat", "()F", (void *)JHwParcel_native_readFloat }, + { "readDouble", "()D", (void *)JHwParcel_native_readDouble }, + + { "readString", "()Ljava/lang/String;", + (void *)JHwParcel_native_readString }, + + { "readInt8Array", "(I)[B", (void *)JHwParcel_native_readInt8Array }, + { "readInt8Vector", "()[B", (void *)JHwParcel_native_readInt8Vector }, + { "readInt16Array", "(I)[S", (void *)JHwParcel_native_readInt16Array }, + { "readInt16Vector", "()[S", (void *)JHwParcel_native_readInt16Vector }, + { "readInt32Array", "(I)[I", (void *)JHwParcel_native_readInt32Array }, + { "readInt32Vector", "()[I", (void *)JHwParcel_native_readInt32Vector }, + { "readInt64Array", "(I)[J", (void *)JHwParcel_native_readInt64Array }, + { "readInt64Vector", "()[J", (void *)JHwParcel_native_readInt64Vector }, + { "readFloatArray", "(I)[F", (void *)JHwParcel_native_readFloatArray }, + { "readFloatVector", "()[F", (void *)JHwParcel_native_readFloatVector }, + { "readDoubleArray", "(I)[D", (void *)JHwParcel_native_readDoubleArray }, + { "readDoubleVector", "()[D", (void *)JHwParcel_native_readDoubleVector }, + + { "readStringArray", "(I)[Ljava/lang/String;", + (void *)JHwParcel_native_readStringArray }, + + { "readStringVector", "()[Ljava/lang/String;", + (void *)JHwParcel_native_readStringVector }, + + { "readStrongBinder", "()L" PACKAGE_PATH "/IHwBinder;", + (void *)JHwParcel_native_readStrongBinder }, + + { "writeStatus", "(I)V", (void *)JHwParcel_native_writeStatus }, + + { "verifySuccess", "()V", (void *)JHwParcel_native_verifySuccess }, + + { "releaseTemporaryStorage", "()V", + (void *)JHwParcel_native_releaseTemporaryStorage }, + + { "send", "()V", (void *)JHwParcel_native_send }, +}; + +namespace android { + +int register_android_os_HwParcel(JNIEnv *env) { + return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods)); +} + +} // namespace android diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h new file mode 100644 index 000000000000..708bbba1901c --- /dev/null +++ b/core/jni/android_os_HwParcel.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_OS_HW_PARCEL_H +#define ANDROID_OS_HW_PARCEL_H + +#include "hwbinder/EphemeralStorage.h" + +#include <android-base/macros.h> +#include <hwbinder/IBinder.h> +#include <hwbinder/Parcel.h> +#include <jni.h> +#include <utils/RefBase.h> + +namespace android { + +struct JHwParcel : public RefBase { + static void InitClass(JNIEnv *env); + + static sp<JHwParcel> SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwParcel> &context); + + static sp<JHwParcel> GetNativeContext(JNIEnv *env, jobject thiz); + + static jobject NewObject(JNIEnv *env); + + JHwParcel(JNIEnv *env, jobject thiz); + + void setParcel(hardware::Parcel *parcel, bool assumeOwnership); + hardware::Parcel *getParcel(); + + EphemeralStorage *getStorage(); + + void setTransactCallback(::android::hardware::IBinder::TransactCallback cb); + + void send(); + bool wasSent() const; + +protected: + virtual ~JHwParcel(); + +private: + jclass mClass; + jobject mObject; + + hardware::Parcel *mParcel; + bool mOwnsParcel; + + EphemeralStorage mStorage; + + ::android::hardware::IBinder::TransactCallback mTransactCallback; + bool mWasSent; + + DISALLOW_COPY_AND_ASSIGN(JHwParcel); +}; + +void signalExceptionForError(JNIEnv *env, status_t err); +int register_android_os_HwParcel(JNIEnv *env); + +} // namespace android + +#endif // ANDROID_OS_HW_PARCEL_H diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp new file mode 100644 index 000000000000..23d4fced2183 --- /dev/null +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "JHwRemoteBinder" +#include <android-base/logging.h> + +#include "android_os_HwRemoteBinder.h" + +#include "android_os_HwParcel.h" + +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include <hwbinder/IServiceManager.h> +#include <hwbinder/Status.h> +#include <nativehelper/ScopedLocalRef.h> + +#include "core_jni_helpers.h" + +using android::AndroidRuntime; + +#define PACKAGE_PATH "android/os" +#define CLASS_NAME "HwRemoteBinder" +#define CLASS_PATH PACKAGE_PATH "/" CLASS_NAME + +namespace android { + +static struct fields_t { + jfieldID contextID; + jmethodID constructID; + +} gFields; + +// static +void JHwRemoteBinder::InitClass(JNIEnv *env) { + ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH)); + + gFields.contextID = + GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J"); + + gFields.constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V"); +} + +// static +sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context) { + sp<JHwRemoteBinder> old = + (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID); + + if (context != NULL) { + context->incStrong(NULL /* id */); + } + + if (old != NULL) { + old->decStrong(NULL /* id */); + } + + env->SetLongField(thiz, gFields.contextID, (long)context.get()); + + return old; +} + +// static +sp<JHwRemoteBinder> JHwRemoteBinder::GetNativeContext( + JNIEnv *env, jobject thiz) { + return (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID); +} + +// static +jobject JHwRemoteBinder::NewObject( + JNIEnv *env, const sp<hardware::IBinder> &binder) { + ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH)); + + // XXX Have to look up the constructor here because otherwise that static + // class initializer isn't called and gFields.constructID is undefined :( + + jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V"); + + jobject obj = env->NewObject(clazz.get(), constructID); + JHwRemoteBinder::GetNativeContext(env, obj)->setBinder(binder); + + return obj; +} + +JHwRemoteBinder::JHwRemoteBinder( + JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder) + : mBinder(binder) { + jclass clazz = env->GetObjectClass(thiz); + CHECK(clazz != NULL); + + mClass = (jclass)env->NewGlobalRef(clazz); + mObject = env->NewWeakGlobalRef(thiz); +} + +JHwRemoteBinder::~JHwRemoteBinder() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; + + env->DeleteGlobalRef(mClass); + mClass = NULL; +} + +sp<hardware::IBinder> JHwRemoteBinder::getBinder() { + return mBinder; +} + +void JHwRemoteBinder::setBinder(const sp<hardware::IBinder> &binder) { + mBinder = binder; +} + +} // namespace android + +//////////////////////////////////////////////////////////////////////////////// + +using namespace android; + +static void releaseNativeContext(void *nativeContext) { + sp<JHwRemoteBinder> binder = (JHwRemoteBinder *)nativeContext; + + if (binder != NULL) { + binder->decStrong(NULL /* id */); + } +} + +static jlong JHwRemoteBinder_native_init(JNIEnv *env) { + JHwRemoteBinder::InitClass(env); + + return reinterpret_cast<jlong>(&releaseNativeContext); +} + +static void JHwRemoteBinder_native_setup_empty(JNIEnv *env, jobject thiz) { + sp<JHwRemoteBinder> context = + new JHwRemoteBinder(env, thiz, NULL /* service */); + + JHwRemoteBinder::SetNativeContext(env, thiz, context); +} + +static void JHwRemoteBinder_native_transact( + JNIEnv *env, + jobject thiz, + jint code, + jobject requestObj, + jobject replyObj, + jint flags) { + sp<hardware::IBinder> binder = + JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder(); + + if (requestObj == NULL) { + jniThrowException(env, "java/lang/NullPointerException", NULL); + return; + } + + const hardware::Parcel *request = + JHwParcel::GetNativeContext(env, requestObj)->getParcel(); + + hardware::Parcel *reply = + JHwParcel::GetNativeContext(env, replyObj)->getParcel(); + + status_t err = binder->transact(code, *request, reply, flags); + signalExceptionForError(env, err); +} + +static JNINativeMethod gMethods[] = { + { "native_init", "()J", (void *)JHwRemoteBinder_native_init }, + + { "native_setup_empty", "()V", + (void *)JHwRemoteBinder_native_setup_empty }, + + { "transact", + "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V", + (void *)JHwRemoteBinder_native_transact }, +}; + +namespace android { + +int register_android_os_HwRemoteBinder(JNIEnv *env) { + return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods)); +} + +} // namespace android + diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h new file mode 100644 index 000000000000..fd33338986a0 --- /dev/null +++ b/core/jni/android_os_HwRemoteBinder.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef ANDROID_OS_HW_REMOTE_BINDER_H +#define ANDROID_OS_HW_REMOTE_BINDER_H + +#include <android-base/macros.h> +#include <hwbinder/Binder.h> +#include <jni.h> +#include <utils/RefBase.h> + +namespace android { + +struct JHwRemoteBinder : public RefBase { + static void InitClass(JNIEnv *env); + + static sp<JHwRemoteBinder> SetNativeContext( + JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context); + + static sp<JHwRemoteBinder> GetNativeContext(JNIEnv *env, jobject thiz); + + static jobject NewObject(JNIEnv *env, const sp<hardware::IBinder> &binder); + + JHwRemoteBinder( + JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder); + + sp<hardware::IBinder> getBinder(); + void setBinder(const sp<hardware::IBinder> &binder); + +protected: + virtual ~JHwRemoteBinder(); + +private: + jclass mClass; + jobject mObject; + + sp<hardware::IBinder> mBinder; + + DISALLOW_COPY_AND_ASSIGN(JHwRemoteBinder); +}; + +int register_android_os_HwRemoteBinder(JNIEnv *env); + +} // namespace android + +#endif // ANDROID_OS_HW_REMOTE_BINDER_H + diff --git a/core/jni/hwbinder/EphemeralStorage.cpp b/core/jni/hwbinder/EphemeralStorage.cpp new file mode 100644 index 000000000000..e5087081ba55 --- /dev/null +++ b/core/jni/hwbinder/EphemeralStorage.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 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. + */ + +#define LOG_TAG "EphemeralStorage" +//#define LOG_NDEBUG 0 + +#include <android-base/logging.h> + +#include "EphemeralStorage.h" + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; + +namespace android { + +EphemeralStorage::EphemeralStorage() { +} + +EphemeralStorage::~EphemeralStorage() { + CHECK(mItems.empty()) + << "All item storage should have been released by now."; +} + +hidl_string *EphemeralStorage::allocStringArray(size_t size) { + Item item; + item.mType = TYPE_STRING_ARRAY; + item.mObj = NULL; + item.mPtr = new hidl_string[size]; + mItems.push_back(item); + + return static_cast<hidl_string *>(item.mPtr); +} + +void *EphemeralStorage::allocTemporaryStorage(size_t size) { + Item item; + item.mType = TYPE_STORAGE; + item.mObj = NULL; + item.mPtr = malloc(size); + mItems.push_back(item); + + return item.mPtr; +} + +const hidl_string *EphemeralStorage::allocTemporaryString( + JNIEnv *env, jstring stringObj) { + jstring obj = (jstring)env->NewGlobalRef(stringObj); + const char *val = env->GetStringUTFChars(obj, NULL); + + Item item; + item.mType = TYPE_STRING; + item.mObj = obj; + item.mPtr = (void *)val; + mItems.push_back(item); + + hidl_string *s = allocStringArray(1 /* size */); + s->setToExternal((char *)val, strlen(val)); + + return s; +} + +#define DEFINE_ALLOC_ARRAY_METHODS(Suffix,Type,NewType) \ +const Type *EphemeralStorage::allocTemporary ## Suffix ## Array( \ + JNIEnv *env, Type ## Array arrayObj) { \ + Type ## Array obj = (Type ## Array)env->NewGlobalRef(arrayObj); \ + const Type *val = env->Get ## NewType ## ArrayElements(obj, NULL); \ + \ + Item item; \ + item.mType = TYPE_ ## Suffix ## _ARRAY; \ + item.mObj = obj; \ + item.mPtr = (void *)val; \ + mItems.push_back(item); \ + \ + return val; \ +} + +#define DEFINE_ALLOC_VECTOR_METHODS(Suffix,Type,NewType) \ +const hidl_vec<Type> *EphemeralStorage::allocTemporary ## Suffix ## Vector( \ + JNIEnv *env, Type ## Array arrayObj) { \ + Type ## Array obj = (Type ## Array)env->NewGlobalRef(arrayObj); \ + jsize len = env->GetArrayLength(obj); \ + const Type *val = env->Get ## NewType ## ArrayElements(obj, NULL); \ + \ + Item item; \ + item.mType = TYPE_ ## Suffix ## _ARRAY; \ + item.mObj = obj; \ + item.mPtr = (void *)val; \ + mItems.push_back(item); \ + \ + hidl_vec<Type> *vec = \ + (hidl_vec<Type> *)allocTemporaryStorage(sizeof(hidl_vec<Type>)); \ + \ + vec->setToExternal(const_cast<Type *>(val), len); \ + \ + return vec; \ +} + +DEFINE_ALLOC_ARRAY_METHODS(Int8,jbyte,Byte) +DEFINE_ALLOC_ARRAY_METHODS(Int16,jshort,Short) +DEFINE_ALLOC_ARRAY_METHODS(Int32,jint,Int) +DEFINE_ALLOC_ARRAY_METHODS(Int64,jlong,Long) +DEFINE_ALLOC_ARRAY_METHODS(Float,jfloat,Float) +DEFINE_ALLOC_ARRAY_METHODS(Double,jdouble,Double) + +DEFINE_ALLOC_VECTOR_METHODS(Int8,jbyte,Byte) +DEFINE_ALLOC_VECTOR_METHODS(Int16,jshort,Short) +DEFINE_ALLOC_VECTOR_METHODS(Int32,jint,Int) +DEFINE_ALLOC_VECTOR_METHODS(Int64,jlong,Long) +DEFINE_ALLOC_VECTOR_METHODS(Float,jfloat,Float) +DEFINE_ALLOC_VECTOR_METHODS(Double,jdouble,Double) + +#define DEFINE_RELEASE_ARRAY_CASE(Suffix,Type,NewType) \ + case TYPE_ ## Suffix ## _ARRAY: \ + { \ + env->Release ## NewType ## ArrayElements( \ + (Type ## Array)item.mObj, \ + (Type *)item.mPtr, \ + 0 /* mode */); \ + \ + env->DeleteGlobalRef(item.mObj); \ + break; \ + } + +void EphemeralStorage::release(JNIEnv *env) { + for (size_t i = mItems.size(); i--;) { + const Item &item = mItems[i]; + + switch (item.mType) { + case TYPE_STRING_ARRAY: + { + delete[] static_cast<hidl_string *>(item.mPtr); + break; + } + + case TYPE_STORAGE: + { + free(item.mPtr); + break; + } + + case TYPE_STRING: + { + env->ReleaseStringUTFChars( + (jstring)item.mObj, (const char *)item.mPtr); + + env->DeleteGlobalRef(item.mObj); + break; + } + + DEFINE_RELEASE_ARRAY_CASE(Int8,jbyte,Byte) + DEFINE_RELEASE_ARRAY_CASE(Int16,jshort,Short) + DEFINE_RELEASE_ARRAY_CASE(Int32,jint,Int) + DEFINE_RELEASE_ARRAY_CASE(Int64,jlong,Long) + DEFINE_RELEASE_ARRAY_CASE(Float,jfloat,Float) + DEFINE_RELEASE_ARRAY_CASE(Double,jdouble,Double) + + default: + CHECK(!"Should not be here"); + } + } + + mItems.clear(); +} + +} // namespace android + diff --git a/core/jni/hwbinder/EphemeralStorage.h b/core/jni/hwbinder/EphemeralStorage.h new file mode 100644 index 000000000000..1273003e6176 --- /dev/null +++ b/core/jni/hwbinder/EphemeralStorage.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef EPHEMERAL_STORAGE_H_ + +#define EPHEMERAL_STORAGE_H_ + +#include <android-base/macros.h> +#include <hwbinder/HidlSupport.h> +#include <jni.h> +#include <utils/Vector.h> + +namespace android { + +#define DECLARE_ALLOC_METHODS(Suffix,Type) \ + const Type *allocTemporary ## Suffix ## Array( \ + JNIEnv *env, Type ## Array arrayObj); \ + \ + const ::android::hardware::hidl_vec<Type> * \ + allocTemporary ## Suffix ## Vector( \ + JNIEnv *env, Type ## Array arrayObj); + +struct EphemeralStorage { + EphemeralStorage(); + ~EphemeralStorage(); + + void release(JNIEnv *env); + + hardware::hidl_string *allocStringArray(size_t size); + + void *allocTemporaryStorage(size_t size); + + const ::android::hardware::hidl_string *allocTemporaryString( + JNIEnv *env, jstring stringObj); + + DECLARE_ALLOC_METHODS(Int8,jbyte) + DECLARE_ALLOC_METHODS(Int16,jshort) + DECLARE_ALLOC_METHODS(Int32,jint) + DECLARE_ALLOC_METHODS(Int64,jlong) + DECLARE_ALLOC_METHODS(Float,jfloat) + DECLARE_ALLOC_METHODS(Double,jdouble) + +private: + enum Type { + TYPE_STRING_ARRAY, + TYPE_STORAGE, + TYPE_STRING, + TYPE_Int8_ARRAY, + TYPE_Int16_ARRAY, + TYPE_Int32_ARRAY, + TYPE_Int64_ARRAY, + TYPE_Float_ARRAY, + TYPE_Double_ARRAY, + }; + + struct Item { + Type mType; + jobject mObj; + void *mPtr; + }; + + Vector<Item> mItems; + + DISALLOW_COPY_AND_ASSIGN(EphemeralStorage); +}; + +#undef DECLARE_ALLOC_METHODS + +} // namespace android + +#endif // EPHEMERAL_STORAGE_H_ diff --git a/docs/html/guide/topics/renderscript/compute.jd b/docs/html/guide/topics/renderscript/compute.jd index fe686547fe6c..13880ec6b796 100755 --- a/docs/html/guide/topics/renderscript/compute.jd +++ b/docs/html/guide/topics/renderscript/compute.jd @@ -16,6 +16,13 @@ parent.link=index.html </ol> </li> <li><a href="#using-rs-from-java">Using RenderScript from Java Code</a></li> + <li><a href="#reduction-in-depth">Reduction Kernels in Depth</a> + <ol> + <li><a href="#writing-reduction-kernel">Writing a reduction kernel</a></li> + <li><a href="#calling-reduction-kernel">Calling a reduction kernel from Java code</a></li> + <li><a href="#more-example">More example reduction kernels</a></li> + </ol> + </li> </ol> <h2>Related Samples</h2> @@ -29,16 +36,18 @@ parent.link=index.html <p>RenderScript is a framework for running computationally intensive tasks at high performance on Android. RenderScript is primarily oriented for use with data-parallel computation, although serial -computationally intensive workloads can benefit as well. The RenderScript runtime will parallelize -work across all processors available on a device, such as multi-core CPUs, GPUs, or DSPs, allowing -you to focus on expressing algorithms rather than scheduling work or load balancing. RenderScript is +workloads can benefit as well. The RenderScript runtime parallelizes +work across processors available on a device, such as multi-core CPUs and GPUs. This allows +you to focus on expressing algorithms rather than scheduling work. RenderScript is especially useful for applications performing image processing, computational photography, or computer vision.</p> <p>To begin with RenderScript, there are two main concepts you should understand:</p> <ul> -<li>High-performance compute kernels are written in a C99-derived language.</li> +<li>High-performance compute kernels are written in a C99-derived language. A <i>compute + kernel</i> is a function or collection of functions that you can direct the RenderScript runtime + to execute in parallel across a collection of data.</li> <li>A Java API is used for managing the lifetime of RenderScript resources and controlling kernel execution.</li> @@ -48,7 +57,7 @@ execution.</li> <p>A RenderScript kernel typically resides in a <code>.rs</code> file in the <code><project_root>/src/</code> directory; each <code>.rs</code> file is called a -script. Every script contains its own set of kernels, functions, and variables. A script can +<i>script</i>. Every script contains its own set of kernels, functions, and variables. A script can contain:</p> <ul> @@ -57,23 +66,32 @@ RenderScript kernel language used in this script. Currently, 1 is the only valid <li>A pragma declaration (<code>#pragma rs java_package_name(com.example.app)</code>) that declares the package name of the Java classes reflected from this script. -Note that your .rs file must be part of your application package, and not in a +Note that your <code>.rs</code> file must be part of your application package, and not in a library project.</li> -<li>Some number of invokable functions. An invokable function is a single-threaded RenderScript +<li>Zero or more <strong><i>invokable functions</i></strong>. An invokable function is a single-threaded RenderScript function that you can call from your Java code with arbitrary arguments. These are often useful for initial setup or serial computations within a larger processing pipeline.</li> -<li>Some number of script globals. A script global is equivalent to a global variable in C. You can +<li><p>Zero or more <strong><i>script globals</i></strong>. A script global is equivalent to a global variable in C. You can access script globals from Java code, and these are often used for parameter passing to RenderScript -kernels.</li> +kernels.</p></li> -<li>Some number of compute kernels. A kernel is a parallel function that executes across every -{@link android.renderscript.Element} within an {@link android.renderscript.Allocation}. +<li><p>Zero or more <strong><i>compute kernels</i></strong>. There are two kinds of compute +kernels: <i>mapping</i> kernels (also called <i>foreach</i> kernels) +and <i>reduction</i> kernels.</p> -<p>A simple kernel may look like the following:</p> +<p>A <em>mapping kernel</em> is a parallel function that operates on a collection of {@link + android.renderscript.Allocation Allocations} of the same dimensions. By default, it executes + once for every coordinate in those dimensions. It is typically (but not exclusively) used to + transform a collection of input {@link android.renderscript.Allocation Allocations} to an + output {@link android.renderscript.Allocation} one {@link android.renderscript.Element} at a + time.</p> -<pre>uchar4 __attribute__((kernel)) invert(uchar4 in, uint32_t x, uint32_t y) { +<ul> +<li><p>Here is an example of a simple <strong>mapping kernel</strong>:</p> + +<pre>uchar4 RS_KERNEL invert(uchar4 in, uint32_t x, uint32_t y) { uchar4 out = in; out.r = 255 - in.r; out.g = 255 - in.g; @@ -81,40 +99,113 @@ kernels.</li> return out; }</pre> -<p>In most respects, this is identical to a standard C function. The first notable feature is the -<code>__attribute__((kernel))</code> applied to the function prototype. This denotes that the -function is a RenderScript kernel instead of an invokable function. The next feature is the -<code>in</code> argument and its type. In a RenderScript kernel, this is a special argument that is -automatically filled in based on the input {@link android.renderscript.Allocation} passed to the -kernel launch. By default, the kernel is run across an entire {@link -android.renderscript.Allocation}, with one execution of the kernel body per {@link -android.renderscript.Element} in the {@link android.renderscript.Allocation}. The third notable -feature is the return type of the kernel. The value returned from the kernel is automatically -written to the appropriate location in the output {@link android.renderscript.Allocation}. The -RenderScript runtime checks to ensure that the {@link android.renderscript.Element} types of the -input and output Allocations match the kernel's prototype; if they do not match, an exception is -thrown.</p> - -<p>A kernel may have an input {@link android.renderscript.Allocation}, an output {@link -android.renderscript.Allocation}, or both. A kernel may not have more than one input or one output -{@link android.renderscript.Allocation}. If more than one input or output is required, those objects -should be bound to <code>rs_allocation</code> script globals and accessed from a kernel or invokable -function via <code>rsGetElementAt_<em>type</em>()</code> or -<code>rsSetElementAt_<em>type</em>()</code>.</p> - -<p>A kernel may access the coordinates of the current execution using the <code>x</code>, -<code>y</code>, and <code>z</code> arguments. These arguments are optional, but the type of the -coordinate arguments must be <code>uint32_t</code>.</p></li> +<p>In most respects, this is identical to a standard C + function. The <a href="#RS_KERNEL"><code>RS_KERNEL</code></a> property applied to the + function prototype specifies that the function is a RenderScript mapping kernel instead of an + invokable function. The <code>in</code> argument is automatically filled in based on the + input {@link android.renderscript.Allocation} passed to the kernel launch. The + arguments <code>x</code> and <code>y</code> are + discussed <a href="#special-arguments">below</a>. The value returned from the kernel is + automatically written to the appropriate location in the output {@link + android.renderscript.Allocation}. By default, this kernel is run across its entire input + {@link android.renderscript.Allocation}, with one execution of the kernel function per {@link + android.renderscript.Element} in the {@link android.renderscript.Allocation}.</p> + +<p>A mapping kernel may have one or more input {@link android.renderscript.Allocation + Allocations}, a single output {@link android.renderscript.Allocation}, or both. The + RenderScript runtime checks to ensure that all input and output Allocations have the same + dimensions, and that the {@link android.renderscript.Element} types of the input and output + Allocations match the kernel's prototype; if either of these checks fails, RenderScript + throws an exception.</p> + +<p class="note"><strong>NOTE:</strong> Before Android 6.0 (API level 23), a mapping kernel may + not have more than one input {@link android.renderscript.Allocation}.</p> + +<p>If you need more input or output {@link android.renderscript.Allocation Allocations} than + the kernel has, those objects should be bound to <code>rs_allocation</code> script globals + and accessed from a kernel or invokable function + via <code>rsGetElementAt_<i>type</i>()</code> or <code>rsSetElementAt_<i>type</i>()</code>.</p> + +<p><strong>NOTE:</strong> <a id="RS_KERNEL"><code>RS_KERNEL</code></a> is a macro + defined automatically by RenderScript for your convenience:</p> +<pre> +#define RS_KERNEL __attribute__((kernel)) +</pre> +</li> +</ul> + +<p>A <em>reduction kernel</em> is a family of functions that operates on a collection of input + {@link android.renderscript.Allocation Allocations} of the same dimensions. By default, + its <a href="#accumulator-function">accumulator function</a> executes once for every + coordinate in those dimensions. It is typically (but not exclusively) used to "reduce" a + collection of input {@link android.renderscript.Allocation Allocations} to a single + value.</p> + +<ul> +<li><p>Here is an <a id="example-addint">example</a> of a simple <strong>reduction +kernel</strong> that adds up the {@link android.renderscript.Element Elements} of its +input:</p> + +<pre>#pragma rs reduce(addint) accumulator(addintAccum) + +static void addintAccum(int *accum, int val) { + *accum += val; +}</pre> + +<p>A reduction kernel consists of one or more user-written functions. +<code>#pragma rs reduce</code> is used to define the kernel by specifying its name +(<code>addint</code>, in this example) and the names and roles of the functions that make +up the kernel (an <code>accumulator</code> function <code>addintAccum</code>, in this +example). All such functions must be <code>static</code>. A reduction kernel always +requires an <code>accumulator</code> function; it may also have other functions, depending +on what you want the kernel to do.</p> + +<p>A reduction kernel accumulator function must return <code>void</code> and must have at least +two arguments. The first argument (<code>accum</code>, in this example) is a pointer to +an <i>accumulator data item</i> and the second (<code>val</code>, in this example) is +automatically filled in based on the input {@link android.renderscript.Allocation} passed to +the kernel launch. The accumulator data item is created by the RenderScript runtime; by +default, it is initialized to zero. By default, this kernel is run across its entire input +{@link android.renderscript.Allocation}, with one execution of the accumulator function per +{@link android.renderscript.Element} in the {@link android.renderscript.Allocation}. By +default, the final value of the accumulator data item is treated as the result of the +reduction, and is returned to Java. The RenderScript runtime checks to ensure that the {@link +android.renderscript.Element} type of the input Allocation matches the accumulator function's +prototype; if it does not match, RenderScript throws an exception.</p> + +<p>A reduction kernel has one or more input {@link android.renderscript.Allocation +Allocations} but no output {@link android.renderscript.Allocation Allocations}.</p></li> + +<p>Reduction kernels are explained in more detail <a href="#reduction-in-depth">here</a>.</p> + +<p>Reduction kernels are supported in Android 7.0 (API level 24) and later.</p> +</li> +</ul> + +<p>A mapping kernel function or a reduction kernel accumulator function may access the coordinates +of the current execution using the <a id="special-arguments">special arguments</a> <code>x</code>, +<code>y</code>, and <code>z</code>, which must be of type <code>int</code> or <code>uint32_t</code>. +These arguments are optional.</p> + +<p>A mapping kernel function or a reduction kernel accumulator +function may also take the optional special argument +<code>context</code> of type <a +href='reference/rs_for_each.html#android_rs:rs_kernel_context'>rs_kernel_context</a>. +It is needed by a family of runtime APIs that are used to query +certain properties of the current execution -- for example, <a +href='reference/rs_for_each.html#android_rs:rsGetDimX'>rsGetDimX</a>. +(The <code>context</code> argument is available in Android 6.0 (API level 23) and later.)</p> +</li> <li>An optional <code>init()</code> function. An <code>init()</code> function is a special type of -invokable function that is run when the script is first instantiated. This allows for some +invokable function that RenderScript runs when the script is first instantiated. This allows for some computation to occur automatically at script creation.</li> -<li>Some number of static script globals and functions. A static script global is equivalent to a -script global except that it cannot be set from Java code. A static function is a standard C +<li>Zero or more <strong><i>static script globals and functions</i></strong>. A static script global is equivalent to a +script global except that it cannot be accessed from Java code. A static function is a standard C function that can be called from any kernel or invokable function in the script but is not exposed to the Java API. If a script global or function does not need to be called from Java code, it is -highly recommended that those be declared <code>static</code>.</li> </ul> +highly recommended that it be declared <code>static</code>.</li> </ul> <h4>Setting floating point precision</h4> @@ -129,13 +220,13 @@ different level of floating point precision:</p> </li> - <li><code>#pragma rs_fp_relaxed</code> - For apps that don’t require strict IEEE 754-2008 + <li><code>#pragma rs_fp_relaxed</code>: For apps that don’t require strict IEEE 754-2008 compliance and can tolerate less precision. This mode enables flush-to-zero for denorms and round-towards-zero. </li> - <li><code>#pragma rs_fp_imprecise</code> - For apps that don’t have stringent precision + <li><code>#pragma rs_fp_imprecise</code>: For apps that don’t have stringent precision requirements. This mode enables everything in <code>rs_fp_relaxed</code> along with the following: @@ -162,14 +253,21 @@ precision (such as SIMD CPU instructions).</p> available on devices running Android 3.0 (API level 11) and higher. </li> <li><strong>{@link android.support.v8.renderscript}</strong> - The APIs in this package are available through a <a href="{@docRoot}tools/support-library/features.html#v8">Support - Library</a>, which allows you to use them on devices running Android 2.2 (API level 8) and + Library</a>, which allows you to use them on devices running Android 2.3 (API level 9) and higher.</li> </ul> -<p>We strongly recommend using the Support Library APIs for accessing RenderScript because they - provide a wider range of device compatibility. Developers targeting specific versions of - Android can use {@link android.renderscript} if necessary.</p> +<p>Here are the tradeoffs:</p> +<ul> +<li>If you use the Support Library APIs, the RenderScript portion of your application will be + compatible with devices running Android 2.3 (API level 9) and higher, regardless of which RenderScript + features you use. This allows your application to work on more devices than if you use the + native (<strong>{@link android.renderscript}</strong>) APIs.</li> +<li>Certain RenderScript features are not available through the Support Library APIs.</li> +<li>If you use the Support Library APIs, you will get (possibly significantly) larger APKs than +if you use the native (<strong>{@link android.renderscript}</strong>) APIs.</li> +</ul> <h3 id="ide-setup">Using the RenderScript Support Library APIs</h3> @@ -202,7 +300,7 @@ android { buildToolsVersion "23.0.3" defaultConfig { - minSdkVersion 8 + minSdkVersion 9 targetSdkVersion 19 <strong> renderscriptTargetApi 18 @@ -250,7 +348,7 @@ import android.support.v8.renderscript.*; <p>Using RenderScript from Java code relies on the API classes located in the {@link android.renderscript} or the {@link android.support.v8.renderscript} package. Most -applications follow the same basic usage patterns:</p> +applications follow the same basic usage pattern:</p> <ol> @@ -266,12 +364,12 @@ possible. Typically, an application will have only a single RenderScript context script.</strong> An {@link android.renderscript.Allocation} is a RenderScript object that provides storage for a fixed amount of data. Kernels in scripts take {@link android.renderscript.Allocation} objects as their input and output, and {@link android.renderscript.Allocation} objects can be -accessed in kernels using <code>rsGetElementAt_<em>type</em>()</code> and -<code>rsSetElementAt_<em>type</em>()</code> when bound as script globals. {@link +accessed in kernels using <code>rsGetElementAt_<i>type</i>()</code> and +<code>rsSetElementAt_<i>type</i>()</code> when bound as script globals. {@link android.renderscript.Allocation} objects allow arrays to be passed from Java code to RenderScript code and vice-versa. {@link android.renderscript.Allocation} objects are typically created using -{@link android.renderscript.Allocation#createTyped} or {@link -android.renderscript.Allocation#createFromBitmap}.</li> +{@link android.renderscript.Allocation#createTyped createTyped()} or {@link +android.renderscript.Allocation#createFromBitmap createFromBitmap()}.</li> <li><strong>Create whatever scripts are necessary.</strong> There are two types of scripts available to you when using RenderScript: @@ -281,9 +379,9 @@ to you when using RenderScript: <li><strong>ScriptC</strong>: These are the user-defined scripts as described in <a href="#writing-an-rs-kernel">Writing a RenderScript Kernel</a> above. Every script has a Java class reflected by the RenderScript compiler in order to make it easy to access the script from Java code; -this class will have the name <code>ScriptC_<em>filename</em></code>. For example, if the kernel -above was located in <code>invert.rs</code> and a RenderScript context was already located in -<code>mRS</code>, the Java code to instantiate the script would be: +this class has the name <code>ScriptC_<i>filename</i></code>. For example, if the mapping kernel +above were located in <code>invert.rs</code> and a RenderScript context were already located in +<code>mRenderScript</code>, the Java code to instantiate the script would be: <pre>ScriptC_invert invert = new ScriptC_invert(mRenderScript);</pre></li> @@ -294,35 +392,926 @@ such as Gaussian blur, convolution, and image blending. For more information, se </ul></li> <li><strong>Populate Allocations with data.</strong> Except for Allocations created with {@link -android.renderscript.Allocation#createFromBitmap}, an Allocation will be populated with empty data when it is -first created. To populate an Allocation, use one of the <code>copy</code> methods in {@link -android.renderscript.Allocation}.</li> - -<li><strong>Set any necessary script globals.</strong> Globals may be set using methods in the same -<code>ScriptC_<em>filename</em></code> class with methods named -<code>set_<em>globalname</em></code>. For example, in order to set an <code>int</code> named -<code>elements</code>, use the Java method <code>set_elements(int)</code>. RenderScript objects can -also be set in kernels; for example, the <code>rs_allocation</code> variable named -<code>lookup</code> can be set with the method <code>set_lookup(Allocation)</code>.</li> - -<li><strong>Launch the appropriate kernels.</strong> Methods to launch a given kernel will be -reflected in the same <code>ScriptC_<em>filename</em></code> class with methods named -<code>forEach_<em>kernelname</em>()</code>. These launches are asynchronous, and launches will be -serialized in the order in which they are launched. Depending on the arguments to the kernel, the -method will take either one or two Allocations. By default, a kernel will execute over the entire -input or output Allocation; to execute over a subset of that Allocation, pass an appropriate {@link -android.renderscript.Script.LaunchOptions} as the last argument to the <code>forEach</code> method. - -<p>Invoked functions can be launched using the <code>invoke_<em>functionname</em></code> methods -reflected in the same <code>ScriptC_<em>filename</em></code> class.</p></li> - -<li><strong>Copy data out of {@link android.renderscript.Allocation} objects.</strong> In order to -access data from an {@link android.renderscript.Allocation} from Java code, that data must be copied -back to Java buffers using one of the <code>copy</code> methods in {@link -android.renderscript.Allocation}. These functions will synchronize with asynchronous kernel and -function launches as necessary.</li> - -<li><strong>Tear down the RenderScript context.</strong> The RenderScript context can be destroyed +android.renderscript.Allocation#createFromBitmap createFromBitmap()}, an Allocation is populated with empty data when it is +first created. To populate an Allocation, use one of the "copy" methods in {@link +android.renderscript.Allocation}. The "copy" methods are <a href="#asynchronous-model">synchronous</a>.</li> + +<li><strong>Set any necessary script globals.</strong> You may set globals using methods in the + same <code>ScriptC_<i>filename</i></code> class named <code>set_<i>globalname</i></code>. For + example, in order to set an <code>int</code> variable named <code>threshold</code>, use the + Java method <code>set_threshold(int)</code>; and in order to set + an <code>rs_allocation</code> variable named <code>lookup</code>, use the Java + method <code>set_lookup(Allocation)</code>. The <code>set</code> methods + are <a href="#asynchronous-model">asynchronous</a>.</li> + +<li><strong>Launch the appropriate kernels and invokable functions.</strong> +<p>Methods to launch a given kernel are +reflected in the same <code>ScriptC_<i>filename</i></code> class with methods named +<code>forEach_<i>mappingKernelName</i>()</code> +or <code>reduce_<i>reductionKernelName</i>()</code>. +These launches are <a href="#asynchronous-model">asynchronous</a>. +Depending on the arguments to the kernel, the +method takes one or more Allocations, all of which must have the same dimensions. By default, a +kernel executes over every coordinate in those dimensions; to execute a kernel over a subset of those coordinates, +pass an appropriate {@link +android.renderscript.Script.LaunchOptions} as the last argument to the <code>forEach</code> or <code>reduce</code> method.</p> + +<p>Launch invokable functions using the <code>invoke_<i>functionName</i></code> methods +reflected in the same <code>ScriptC_<i>filename</i></code> class. +These launches are <a href="#asynchronous-model">asynchronous</a>.</p></li> + +<li><strong>Retrieve data from {@link android.renderscript.Allocation} objects +and <i><a href="#javaFutureType">javaFutureType</a></i> objects.</strong> +In order to +access data from an {@link android.renderscript.Allocation} from Java code, you must copy that data +back to Java using one of the "copy" methods in {@link +android.renderscript.Allocation}. +In order to obtain the result of a reduction kernel, you must use the <code><i>javaFutureType</i>.get()</code> method. +The "copy" and <code>get()</code> methods are <a href="#asynchronous-model">synchronous</a>.</li> + +<li><strong>Tear down the RenderScript context.</strong> You can destroy the RenderScript context with {@link android.renderscript.RenderScript#destroy} or by allowing the RenderScript context -object to be garbage collected. This will cause any further use of any object belonging to that +object to be garbage collected. This causes any further use of any object belonging to that context to throw an exception.</li> </ol> + +<h3 id="asynchronous-model">Asynchronous execution model</h3> + +<p>The reflected <code>forEach</code>, <code>invoke</code>, <code>reduce</code>, + and <code>set</code> methods are asynchronous -- each may return to Java before completing the + requested action. However, the individual actions are serialized in the order in which they are launched.</p> + +<p>The {@link android.renderscript.Allocation} class provides "copy" methods to copy data to + and from Allocations. A "copy" method is synchronous, and is serialized with respect to any + of the asynchronous actions above that touch the same Allocation.</p> + +<p>The reflected <i><a href="#javaFutureType">javaFutureType</a></i> classes provide + a <code>get()</code> method to obtain the result of a reduction. <code>get()</code> is + synchronous, and is serialized with respect to the reduction (which is asynchronous).</p> + +<h2 id="reduction-in-depth">Reduction Kernels in Depth</h2> + +<p><i>Reduction</i> is the process of combining a collection of data into a single +value. This is a useful primitive in parallel programming, with applications such as the +following:</p> +<ul> + <li>computing the sum or product over all the data</li> + <li>computing logical operations (<code>and</code>, <code>or</code>, <code>xor</code>) + over all the data</li> + <li>finding the minimum or maximum value within the data</li> + <li>searching for a specific value or for the coordinate of a specific value within the data</li> +</ul> + +<p>In Android 7.0 (API level 24) and later, RenderScript supports <i>reduction kernels</i> to allow +efficient user-written reduction algorithms. You may launch reduction kernels on inputs with +1, 2, or 3 dimensions.<p> + +<p>An example above shows a simple <a href="#example-addint">addint</a> reduction kernel. +Here is a more complicated <a id="example-findMinAndMax">findMinAndMax</a> reduction kernel +that finds the locations of the minimum and maximum <code>long</code> values in a +1-dimensional {@link android.renderscript.Allocation}:</p> + +<pre> +#define LONG_MAX (long)((1UL << 63) - 1) +#define LONG_MIN (long)(1UL << 63) + +#pragma rs reduce(findMinAndMax) \ + initializer(fMMInit) accumulator(fMMAccumulator) \ + combiner(fMMCombiner) outconverter(fMMOutConverter) + +// Either a value and the location where it was found, or <a href="#INITVAL">INITVAL</a>. +typedef struct { + long val; + int idx; // -1 indicates <a href="#INITVAL">INITVAL</a> +} IndexedVal; + +typedef struct { + IndexedVal min, max; +} MinAndMax; + +// In discussion below, this initial value { { LONG_MAX, -1 }, { LONG_MIN, -1 } } +// is called <a id="INITVAL">INITVAL</a>. +static void fMMInit(MinAndMax *accum) { + accum->min.val = LONG_MAX; + accum->min.idx = -1; + accum->max.val = LONG_MIN; + accum->max.idx = -1; +} + +//---------------------------------------------------------------------- +// In describing the behavior of the accumulator and combiner functions, +// it is helpful to describe hypothetical functions +// IndexedVal min(IndexedVal a, IndexedVal b) +// IndexedVal max(IndexedVal a, IndexedVal b) +// MinAndMax minmax(MinAndMax a, MinAndMax b) +// MinAndMax minmax(MinAndMax accum, IndexedVal val) +// +// The effect of +// IndexedVal min(IndexedVal a, IndexedVal b) +// is to return the IndexedVal from among the two arguments +// whose val is lesser, except that when an IndexedVal +// has a negative index, that IndexedVal is never less than +// any other IndexedVal; therefore, if exactly one of the +// two arguments has a negative index, the min is the other +// argument. Like ordinary arithmetic min and max, this function +// is commutative and associative; that is, +// +// min(A, B) == min(B, A) // commutative +// min(A, min(B, C)) == min((A, B), C) // associative +// +// The effect of +// IndexedVal max(IndexedVal a, IndexedVal b) +// is analogous (greater . . . never greater than). +// +// Then there is +// +// MinAndMax minmax(MinAndMax a, MinAndMax b) { +// return MinAndMax(min(a.min, b.min), max(a.max, b.max)); +// } +// +// Like ordinary arithmetic min and max, the above function +// is commutative and associative; that is: +// +// minmax(A, B) == minmax(B, A) // commutative +// minmax(A, minmax(B, C)) == minmax((A, B), C) // associative +// +// Finally define +// +// MinAndMax minmax(MinAndMax accum, IndexedVal val) { +// return minmax(accum, MinAndMax(val, val)); +// } +//---------------------------------------------------------------------- + +// This function can be explained as doing: +// *accum = minmax(*accum, IndexedVal(in, x)) +// +// This function simply computes minimum and maximum values as if +// INITVAL.min were greater than any other minimum value and +// INITVAL.max were less than any other maximum value. Note that if +// *accum is INITVAL, then this function sets +// *accum = IndexedVal(in, x) +// +// After this function is called, both accum->min.idx and accum->max.idx +// will have nonnegative values: +// - x is always nonnegative, so if this function ever sets one of the +// idx fields, it will set it to a nonnegative value +// - if one of the idx fields is negative, then the corresponding +// val field must be LONG_MAX or LONG_MIN, so the function will always +// set both the val and idx fields +static void fMMAccumulator(MinAndMax *accum, long in, int x) { + IndexedVal me; + me.val = in; + me.idx = x; + + if (me.val <= accum->min.val) + accum->min = me; + if (me.val >= accum->max.val) + accum->max = me; +} + +// This function can be explained as doing: +// *accum = minmax(*accum, *val) +// +// This function simply computes minimum and maximum values as if +// INITVAL.min were greater than any other minimum value and +// INITVAL.max were less than any other maximum value. Note that if +// one of the two accumulator data items is INITVAL, then this +// function sets *accum to the other one. +static void fMMCombiner(MinAndMax *accum, + const MinAndMax *val) { + if ((accum->min.idx < 0) || (val->min.val < accum->min.val)) + accum->min = val->min; + if ((accum->max.idx < 0) || (val->max.val > accum->max.val)) + accum->max = val->max; +} + +static void fMMOutConverter(int2 *result, + const MinAndMax *val) { + result->x = val->min.idx; + result->y = val->max.idx; +} +</pre> + +<p class="note"><strong>NOTE:</strong> There are more example reduction + kernels <a href="#more-example">here</a>.</p> + +<p>In order to run a reduction kernel, the RenderScript runtime creates <em>one or more</em> +variables called <a id="accumulator-data-items"><strong><i>accumulator data +items</i></strong></a> to hold the state of the reduction process. The RenderScript runtime +picks the number of accumulator data items in such a way as to maximize performance. The type +of the accumulator data items (<i>accumType</i>) is determined by the kernel's <i>accumulator +function</i> -- the first argument to that function is a pointer to an accumulator data +item. By default, every accumulator data item is initialized to zero (as if +by <code>memset</code>); however, you may write an <i>initializer function</i> to do something +different.</p> + +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> +kernel, the accumulator data items (of type <code>int</code>) are used to add up input +values. There is no initializer function, so each accumulator data item is initialized to +zero.</p> + +<p class="note"><strong>Example:</strong> In +the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the accumulator data items +(of type <code>MinAndMax</code>) are used to keep track of the minimum and maximum values +found so far. There is an initializer function to set these to <code>LONG_MAX</code> and +<code>LONG_MIN</code>, respectively; and to set the locations of these values to -1, indicating that +the values are not actually present in the (empty) portion of the input that has been +processed.</p> + +<p>RenderScript calls your accumulator function once for every coordinate in the +input(s). Typically, your function should update the accumulator data item in some way +according to the input.</p> + +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> +kernel, the accumulator function adds the value of an input Element to the accumulator +data item.</p> + +<p class="note"><strong>Example:</strong> In +the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the accumulator function +checks to see whether the value of an input Element is less than or equal to the minimum +value recorded in the accumulator data item and/or greater than or equal to the maximum +value recorded in the accumulator data item, and updates the accumulator data item +accordingly.</p> + +<p>After the accumulator function has been called once for every coordinate in the input(s), +RenderScript must <strong>combine</strong> the <a href="#accumulator-data-items">accumulator +data items</a> together into a single accumulator data item. You may write a <i>combiner +function</i> to do this. If the accumulator function has a single input and +no <a href="#special-arguments">special arguments</a>, then you do not need to write a combiner +function; RenderScript will use the accumulator function to combine the accumulator data +items. (You may still write a combiner function if this default behavior is not what you +want.)</p> + +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> +kernel, there is no combiner function, so the accumulator function will be used. This is +the correct behavior, because if we split a collection of values into two pieces, and we +add up the values in those two pieces separately, adding up those two sums is the same as +adding up the entire collection.</p> + +<p class="note"><strong>Example:</strong> In +the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the combiner function +checks to see whether the minimum value recorded in the "source" accumulator data +item <code>*val</code> is less then the minimum value recorded in the "destination" +accumulator data item <code>*accum</code>, and updates <code>*accum</code> +accordingly. It does similar work for the maximum value. This updates <code>*accum</code> +to the state it would have had if all of the input values had been accumulated into +<code>*accum</code> rather than some into <code>*accum</code> and some into +<code>*val</code>.</p> + +<p>After all of the accumulator data items have been combined, RenderScript determines +the result of the reduction to return to Java. You may write an <i>outconverter +function</i> to do this. You do not need to write an outconverter function if you want +the final value of the combined accumulator data items to be the result of the reduction.</p> + +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> kernel, +there is no outconverter function. The final value of the combined data items is the sum of +all Elements of the input, which is the value we want to return.</p> + +<p class="note"><strong>Example:</strong> In +the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the outconverter function +initializes an <code>int2</code> result value to hold the locations of the minimum and +maximum values resulting from the combination of all of the accumulator data items.</p> + +<h3 id="writing-reduction-kernel">Writing a reduction kernel</h3> + +<p><code>#pragma rs reduce</code> defines a reduction kernel by +specifying its name and the names and roles of the functions that make +up the kernel. All such functions must be +<code>static</code>. A reduction kernel always requires an <code>accumulator</code> +function; you can omit some or all of the other functions, depending on what you want the +kernel to do.</p> + +<pre>#pragma rs reduce(<i>kernelName</i>) \ + initializer(<i>initializerName</i>) \ + accumulator(<i>accumulatorName</i>) \ + combiner(<i>combinerName</i>) \ + outconverter(<i>outconverterName</i>) +</pre> + +<p>The meaning of the items in the <code>#pragma</code> is as follows:</p> +<ul> + +<li><code>reduce(<i>kernelName</i>)</code> (mandatory): Specifies that a reduction kernel is +being defined. A reflected Java method <code>reduce_<i>kernelName</i></code> will launch the +kernel.</li> + +<li><p><code>initializer(<i>initializerName</i>)</code> (optional): Specifies the name of the +initializer function for this reduction kernel. When you launch the kernel, RenderScript calls +this function once for each <a href="#accumulator-data-items">accumulator data item</a>. The +function must be defined like this:</p> + +<pre>static void <i>initializerName</i>(<i>accumType</i> *accum) { … }</pre> + +<p><code>accum</code> is a pointer to an accumulator data item for this function to +initialize.</p> + +<p>If you do not provide an initializer function, RenderScript initializes every accumulator +data item to zero (as if by <code>memset</code>), behaving as if there were an initializer +function that looks like this:</p> +<pre>static void <i>initializerName</i>(<i>accumType</i> *accum) { + memset(accum, 0, sizeof(*accum)); +}</pre> +</li> + +<li><p><code><a id="accumulator-function">accumulator(<i>accumulatorName</i>)</a></code> +(mandatory): Specifies the name of the accumulator function for this +reduction kernel. When you launch the kernel, RenderScript calls +this function once for every coordinate in the input(s), to update an +accumulator data item in some way according to the input(s). The function +must be defined like this:</p> + +<pre> +static void <i>accumulatorName</i>(<i>accumType</i> *accum, + <i>in1Type</i> in1, <i>…,</i> <i>inNType</i> in<i>N</i> + <i>[, specialArguments]</i>) { … } +</pre> + +<p><code>accum</code> is a pointer to an accumulator data item for this function to +modify. <code>in1</code> through <code>in<i>N</i></code> are one <em>or more</em> arguments that +are automatically filled in based on the inputs passed to the kernel launch, one argument +per input. The accumulator function may optionally take any of the <a +href="#special-arguments">special arguments</a>.</p> + +<p>An example kernel with multiple inputs is <a href="#dot-product"><code>dotProduct</code></a>.</p> +</li> + +<li><code><a id="combiner-function">combiner(<i>combinerName</i>)</a></code> +(optional): Specifies the name of the combiner function for this +reduction kernel. After RenderScript calls the accumulator function +once for every coordinate in the input(s), it calls this function as many +times as necessary to combine all accumulator data items into a single +accumulator data item. The function must be defined like this:</p> + +<pre>static void <i>combinerName</i>(<i>accumType</i> *accum, const <i>accumType</i> *other) { … }</pre> + +<p><code>accum</code> is a pointer to a "destination" accumulator data item for this +function to modify. <code>other</code> is a pointer to a "source" accumulator data item +for this function to "combine" into <code>*accum</code>.</p> + +<p class="note"><strong>NOTE:</strong> It is possible + that <code>*accum</code>, <code>*other</code>, or both have been initialized but have never + been passed to the accumulator function; that is, one or both have never been updated + according to any input data. For example, in + the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the combiner + function <code>fMMCombiner</code> explicitly checks for <code>idx < 0</code> because that + indicates such an accumulator data item, whose value is <a href="#INITVAL">INITVAL</a>.</p> + +<p>If you do not provide a combiner function, RenderScript uses the accumulator function in its +place, behaving as if there were a combiner function that looks like this:</p> + +<pre>static void <i>combinerName</i>(<i>accumType</i> *accum, const <i>accumType</i> *other) { + <i>accumulatorName</i>(accum, *other); +}</pre> + +<p>A combiner function is mandatory if the kernel has more than one input, if the input data + type is not the same as the accumulator data type, or if the accumulator function takes one + or more <a href="#special-arguments">special arguments</a>.</p> +</li> + +<li><p><code><a id="outconverter-function">outconverter(<i>outconverterName</i>)</a></code> +(optional): Specifies the name of the outconverter function for this +reduction kernel. After RenderScript combines all of the accumulator +data items, it calls this function to determine the result of the +reduction to return to Java. The function must be defined like +this:</p> + +<pre>static void <i>outconverterName</i>(<i>resultType</i> *result, const <i>accumType</i> *accum) { … }</pre> + +<p><code>result</code> is a pointer to a result data item (allocated but not initialized +by the RenderScript runtime) for this function to initialize with the result of the +reduction. <i>resultType</i> is the type of that data item, which need not be the same +as <i>accumType</i>. <code>accum</code> is a pointer to the final accumulator data item +computed by the <a href="#combiner-function">combiner function</a>.</p> + +<p>If you do not provide an outconverter function, RenderScript copies the final accumulator +data item to the result data item, behaving as if there were an outconverter function that +looks like this:</p> + +<pre>static void <i>outconverterName</i>(<i>accumType</i> *result, const <i>accumType</i> *accum) { + *result = *accum; +}</pre> + +<p>If you want a different result type than the accumulator data type, then the outconverter function is mandatory.</p> +</li> + +</ul> + +<p>Note that a kernel has input types, an accumulator data item type, and a result type, + none of which need to be the same. For example, in + the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, the input + type <code>long</code>, accumulator data item type <code>MinAndMax</code>, and result + type <code>int2</code> are all different.</p> + +<h4 id="assume">What can't you assume?</h4> + +<p>You must not rely on the number of accumulator data items created by RenderScript for a + given kernel launch. There is no guarantee that two launches of the same kernel with the + same input(s) will create the same number of accumulator data items.</p> + +<p>You must not rely on the order in which RenderScript calls the initializer, accumulator, and + combiner functions; it may even call some of them in parallel. There is no guarantee that + two launches of the same kernel with the same input will follow the same order. The only + guarantee is that only the initializer function will ever see an uninitialized accumulator + data item. For example:</p> +<ul> +<li>There is no guarantee that all accumulator data items will be initialized before the + accumulator function is called, although it will only be called on an initialized accumulator + data item.</li> +<li>There is no guarantee on the order in which input Elements are passed to the accumulator + function.</li> +<li>There is no guarantee that the accumulator function has been called for all input Elements + before the combiner function is called.</li> +</ul> + +<p>One consequence of this is that the <a href="#example-findMinAndMax">findMinAndMax</a> + kernel is not deterministic: If the input contains more than one occurrence of the same + minimum or maximum value, you have no way of knowing which occurrence the kernel will + find.</p> + +<h4 id="guarantee">What must you guarantee?</h4> + +<p>Because the RenderScript system can choose to execute a kernel <a href="#assume">in many + different ways</a>, you must follow certain rules to ensure that your kernel behaves the + way you want. If you do not follow these rules, you may get incorrect results, + nondeterministic behavior, or runtime errors.</p> + +<p>The rules below often say that two accumulator data items must have "<a id="the-same">the + same value"</a>. What does this mean? That depends on what you want the kernel to do. For + a mathematical reduction such as <a href="#example-addint">addint</a>, it usually makes sense + for "the same" to mean mathematical equality. For a "pick any" search such + as <a href="#example-findMinAndMax">findMinAndMax</a> ("find the location of minimum and + maximum input values") where there might be more than one occurrence of identical input + values, all locations of a given input value must be considered "the same". You could write + a similar kernel to "find the location of <em>leftmost</em> minimum and maximum input values" + where (say) a minimum value at location 100 is preferred over an identical minimum value at location + 200; for this kernel, "the same" would mean identical <em>location</em>, not merely + identical <em>value</em>, and the accumulator and combiner functions would have to be + different than those for <a href="#example-findMinAndMax">findMinAndMax</a>.</p> + +<strong>The initializer function must create an <i>identity value</i>.</strong> That is, + if <code><i>I</i></code> and <code><i>A</i></code> are accumulator data items initialized + by the initializer function, and <code><i>I</i></code> has never been passed to the + accumulator function (but <code><i>A</i></code> may have been), then +<ul> +<li><code><i>combinerName</i>(&<i>A</i>, &<i>I</i>)</code> must + leave <code><i>A</i></code> <a href="#the-same">the same</a></li> +<li><code><i>combinerName</i>(&<i>I</i>, &<i>A</i>)</code> must + leave <code><i>I</i></code> <a href="#the-same">the same</a> as <code><i>A</i></code></li> +</ul> +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> + kernel, an accumulator data item is initialized to zero. The combiner function for this + kernel performs addition; zero is the identity value for addition.</p> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-findMinAndMax">findMinAndMax</a> + kernel, an accumulator data item is initialized + to <a href="#INITVAL"><code>INITVAL</code></a>. +<ul> +<li><code>fMMCombiner(&<i>A</i>, &<i>I</i>)</code> leaves <code><i>A</i></code> the same, + because <code><i>I</i></code> is <code>INITVAL</code>.</li> +<li><code>fMMCombiner(&<i>I</i>, &<i>A</i>)</code> sets <code><i>I</i></code> + to <code><i>A</i></code>, because <code><i>I</i></code> is <code>INITVAL</code>.</li> +</ul> +Therefore, <code>INITVAL</code> is indeed an identity value. +</p></div> + +<p><strong>The combiner function must be <i>commutative</i>.</strong> That is, + if <code><i>A</i></code> and <code><i>B</i></code> are accumulator data items initialized + by the initializer function, and that may have been passed to the accumulator function zero + or more times, then <code><i>combinerName</i>(&<i>A</i>, &<i>B</i>)</code> must + set <code><i>A</i></code> to <a href="#the-same">the same value</a> + that <code><i>combinerName</i>(&<i>B</i>, &<i>A</i>)</code> + sets <code><i>B</i></code>.</p> +<p class="note"><strong>Example:</strong> In the <a href="#example-addint">addint</a> + kernel, the combiner function adds the two accumulator data item values; addition is + commutative.</p> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, +<pre> +fMMCombiner(&<i>A</i>, &<i>B</i>) +</pre> +is the same as +<pre> +<i>A</i> = minmax(<i>A</i>, <i>B</i>) +</pre> +and <code>minmax</code> is commutative, so <code>fMMCombiner</code> is also. +</p> +</div> + +<p><strong>The combiner function must be <i>associative</i>.</strong> That is, + if <code><i>A</i></code>, <code><i>B</i></code>, and <code><i>C</i></code> are + accumulator data items initialized by the initializer function, and that may have been passed + to the accumulator function zero or more times, then the following two code sequences must + set <code><i>A</i></code> to <a href="#the-same">the same value</a>:</p> +<ul> +<li><pre> +<i>combinerName</i>(&<i>A</i>, &<i>B</i>); +<i>combinerName</i>(&<i>A</i>, &<i>C</i>); +</pre></li> +<li><pre> +<i>combinerName</i>(&<i>B</i>, &<i>C</i>); +<i>combinerName</i>(&<i>A</i>, &<i>B</i>); +</pre></li> +</ul> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-addint">addint</a> kernel, the + combiner function adds the two accumulator data item values: +<ul> +<li><pre> +<i>A</i> = <i>A</i> + <i>B</i> +<i>A</i> = <i>A</i> + <i>C</i> +// Same as +// <i>A</i> = (<i>A</i> + <i>B</i>) + <i>C</i> +</pre></li> +<li><pre> +<i>B</i> = <i>B</i> + <i>C</i> +<i>A</i> = <i>A</i> + <i>B</i> +// Same as +// <i>A</i> = <i>A</i> + (<i>B</i> + <i>C</i>) +// <i>B</i> = <i>B</i> + <i>C</i> +</li> +</ul> +Addition is associative, and so the combiner function is also. +</p> +</div> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, +<pre> +fMMCombiner(&<i>A</i>, &<i>B</i>) +</pre> +is the same as +<pre> +<i>A</i> = minmax(<i>A</i>, <i>B</i>) +</pre> +So the two sequences are +<ul> +<li><pre> +<i>A</i> = minmax(<i>A</i>, <i>B</i>) +<i>A</i> = minmax(<i>A</i>, <i>C</i>) +// Same as +// <i>A</i> = minmax(minmax(<i>A</i>, <i>B</i>), <i>C</i>) +</pre></li> +<li><pre> +<i>B</i> = minmax(<i>B</i>, <i>C</i>) +<i>A</i> = minmax(<i>A</i>, <i>B</i>) +// Same as +// <i>A</i> = minmax(<i>A</i>, minmax(<i>B</i>, <i>C</i>)) +// <i>B</i> = minmax(<i>B</i>, <i>C</i>) +</pre></li> +<code>minmax</code> is associative, and so <code>fMMCombiner</code> is also. +</p> +</div> + +<p><strong>The accumulator function and combiner function together must obey the <i>basic + folding rule</i>.</strong> That is, if <code><i>A</i></code> + and <code><i>B</i></code> are accumulator data items, <code><i>A</i></code> has been + initialized by the initializer function and may have been passed to the accumulator function + zero or more times, <code><i>B</i></code> has not been initialized, and <i>args</i> is + the list of input arguments and special arguments for a particular call to the accumulator + function, then the following two code sequences must set <code><i>A</i></code> + to <a href="#the-same">the same value</a>:</p> +<ul> +<li><pre> +<i>accumulatorName</i>(&<i>A</i>, <i>args</i>); // statement 1 +</pre></li> +<li><pre> +<i>initializerName</i>(&<i>B</i>); // statement 2 +<i>accumulatorName</i>(&<i>B</i>, <i>args</i>); // statement 3 +<i>combinerName</i>(&<i>A</i>, &<i>B</i>); // statement 4 +</pre></li> +</ul> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-addint">addint</a> kernel, for an input value <i>V</i>: +<ul> +<li>Statement 1 is the same as <code>A += <i>V</i></code></li> +<li>Statement 2 is the same as <code>B = 0</code></li> +<li>Statement 3 is the same as <code>B += <i>V</i></code>, which is the same as <code>B = <i>V</i></code></li> +<li>Statement 4 is the same as <code>A += B</code>, which is the same as <code>A += <i>V</i></code></li> +</ul> +Statements 1 and 4 set <code><i>A</i></code> to the same value, and so this kernel obeys the +basic folding rule. +</p> +</div> +<div class="note"> +<p><strong>Example:</strong> In the <a href="#example-findMinAndMax">findMinAndMax</a> kernel, for an input + value <i>V</i> at coordinate <i>X</i>: +<ul> +<li>Statement 1 is the same as <code>A = minmax(A, IndexedVal(<i>V</i>, <i>X</i>))</code></li> +<li>Statement 2 is the same as <code>B = <a href="#INITVAL">INITVAL</a></code></li> +<li>Statement 3 is the same as +<pre> +B = minmax(B, IndexedVal(<i>V</i>, <i>X</i>)) +</pre> +which, because <i>B</i> is the initial value, is the same as +<pre> +B = IndexedVal(<i>V</i>, <i>X</i>) +</pre> +</li> +<li>Statement 4 is the same as +<pre> +A = minmax(A, B) +</pre> +which is the same as +<pre> +A = minmax(A, IndexedVal(<i>V</i>, <i>X</i>)) +</pre> +</ul> +Statements 1 and 4 set <code><i>A</i></code> to the same value, and so this kernel obeys the +basic folding rule. +</p> +</div> + +<h3 id="calling-reduction-kernel">Calling a reduction kernel from Java code</h3> + +<p>For a reduction kernel named <i>kernelName</i> defined in the +file <code><i>filename</i>.rs</code>, there are three methods reflected in the +class <code>ScriptC_<i>filename</i></code>:</p> + +<pre> +// Method 1 +public <i>javaFutureType</i> reduce_<i>kernelName</i>(Allocation ain1, <i>…,</i> + Allocation ain<i>N</i>); + +// Method 2 +public <i>javaFutureType</i> reduce_<i>kernelName</i>(Allocation ain1, <i>…,</i> + Allocation ain<i>N</i>, + Script.LaunchOptions sc); + +// Method 3 +public <i>javaFutureType</i> reduce_<i>kernelName</i>(<i><a href="#devec">devecSiIn1Type</a></i>[] in1, …, + <i><a href="#devec">devecSiInNType</a></i>[] in<i>N</i>); +</pre> + +<p>Here are some examples of calling the <a href="#example-addint">addint</a> kernel:</p> +<pre> +ScriptC_example script = new ScriptC_example(mRenderScript); + +// 1D array +// and obtain answer immediately +int input1[] = <i>…</i>; +int sum1 = script.reduce_addint(input1).get(); // Method 3 + +// 2D allocation +// and do some additional work before obtaining answer +Type.Builder typeBuilder = + new Type.Builder(RS, Element.I32(RS)); +typeBuilder.setX(<i>…</i>); +typeBuilder.setY(<i>…</i>); +Allocation input2 = createTyped(RS, typeBuilder.create()); +<i>populateSomehow</i>(input2); // fill in input Allocation with data +script.result_int result2 = script.reduce_addint(input2); // Method 1 +<i>doSomeAdditionalWork</i>(); // might run at same time as reduction +int sum2 = result2.get(); +</pre> + +<p><strong>Method 1</strong> has one input {@link android.renderscript.Allocation} argument for + every input argument in the kernel's <a href="#accumulator-function">accumulator + function</a>. The RenderScript runtime checks to ensure that all of the input Allocations + have the same dimensions and that the {@link android.renderscript.Element} type of each of + the input Allocations matches that of the corresponding input argument of the accumulator + function's prototype. If any of these checks fail, RenderScript throws an exception. The + kernel executes over every coordinate in those dimensions.</p> + +<p><strong>Method 2</strong> is the same as Method 1 except that Method 2 takes an additional + argument <code>sc</code> that can be used to limit the kernel execution to a subset of the + coordinates.</p> + +<p><strong><a id="reduce-method-3">Method 3</a></strong> is the same as Method 1 except that + instead of taking Allocation inputs it takes Java array inputs. This is a convenience that + saves you from having to write code to explicitly create an Allocation and copy data to it + from a Java array. <em>However, using Method 3 instead of Method 1 does not increase the + performance of the code</em>. For each input array, Method 3 creates a temporary + 1-dimensional Allocation with the appropriate {@link android.renderscript.Element} type and + {@link android.renderscript.Allocation#setAutoPadding} enabled, and copies the array to the + Allocation as if by the appropriate <code>copyFrom()</code> method of {@link + android.renderscript.Allocation}. It then calls Method 1, passing those temporary + Allocations.</p> +<p class="note"><strong>NOTE:</strong> If your application will make multiple kernel calls with + the same array, or with different arrays of the same dimensions and Element type, you may improve + performance by explicitly creating, populating, and reusing Allocations yourself, instead of + by using Method 3.</p> +<p><strong><i><a id="javaFutureType">javaFutureType</a></i></strong>, + the return type of the reflected reduction methods, is a reflected + static nested class within the <code>ScriptC_<i>filename</i></code> + class. It represents the future result of a reduction + kernel run. To obtain the actual result of the run, call + the <code>get()</code> method of that class, which returns a value + of type <i>javaResultType</i>. <code>get()</code> is <a href="#asynchronous-model">synchronous</a>.</p> + +<pre> +public class ScriptC_<i>filename</i> extends ScriptC { + public static class <i>javaFutureType</i> { + public <i>javaResultType</i> get() { … } + } +} +</pre> + +<p><strong><i>javaResultType</i></strong> is determined from the <i>resultType</i> of the + <a href="#outconverter-function">outconverter function</a>. Unless <i>resultType</i> is an + unsigned type (scalar, vector, or array), <i>javaResultType</i> is the directly corresponding + Java type. If <i>resultType</i> is an unsigned type and there is a larger Java signed type, + then <i>javaResultType</i> is that larger Java signed type; otherwise, it is the directly + corresponding Java type. For example:</p> +<ul> +<li>If <i>resultType</i> is <code>int</code>, <code>int2</code>, or <code>int[15]</code>, + then <i>javaResultType</i> is <code>int</code>, <code>Int2</code>, + or <code>int[]</code>. All values of <i>resultType</i> can be represented + by <i>javaResultType</i>.</li> +<li>If <i>resultType</i> is <code>uint</code>, <code>uint2</code>, or <code>uint[15]</code>, + then <i>javaResultType</i> is <code>long</code>, <code>Long2</code>, + or <code>long[]</code>. All values of <i>resultType</i> can be represented + by <i>javaResultType</i>.</li> +<li>If <i>resultType</i> is <code>ulong</code>, <code>ulong2</code>, + or <code>ulong[15]</code>, then <i>javaResultType</i> + is <code>long</code>, <code>Long2</code>, or <code>long[]</code>. There are certain values + of <i>resultType</i> that cannot be represented by <i>javaResultType</i>.</li> +</ul> + +<p><strong><i>javaFutureType</i></strong> is the future result type corresponding + to the <i>resultType</i> of the <a href="#outconverter-function">outconverter + function</a>.</p> +<ul> +<li>If <i>resultType</i> is not an array type, then <i>javaFutureType</i> + is <code>result_<i>resultType</i></code>.</li> +<li>If <i>resultType</i> is an array of length <i>Count</i> with members of type <i>memberType</i>, + then <i>javaFutureType</i> is <code>resultArray<i>Count</i>_<i>memberType</i></code>.</li> +</ul> + +<p>For example:</p> + +<pre> +public class ScriptC_<i>filename</i> extends ScriptC { + // for kernels with int result + public static class result_int { + public int get() { … } + } + + // for kernels with int[10] result + public static class resultArray10_int { + public int[] get() { … } + } + + // for kernels with int2 result + // note that the Java type name "Int2" is not the same as the script type name "int2" + public static class result_int2 { + public Int2 get() { … } + } + + // for kernels with int2[10] result + // note that the Java type name "Int2" is not the same as the script type name "int2" + public static class resultArray10_int2 { + public Int2[] get() { … } + } + + // for kernels with uint result + // note that the Java type "long" is a wider signed type than the unsigned script type "uint" + public static class result_uint { + public long get() { … } + } + + // for kernels with uint[10] result + // note that the Java type "long" is a wider signed type than the unsigned script type "uint" + public static class resultArray10_uint { + public long[] get() { … } + } + + // for kernels with uint2 result + // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" + public static class result_uint2 { + public Long2 get() { … } + } + + // for kernels with uint2[10] result + // note that the Java type "Long2" is a wider signed type than the unsigned script type "uint2" + public static class resultArray10_uint2 { + public Long2[] get() { … } + } +} +</pre> + +<p>If <i>javaResultType</i> is an object type (including an array type), each call + to <code><i>javaFutureType</i>.get()</code> on the same instance will return the same + object.</p> + +<p>If <i>javaResultType</i> cannot represent all values of type <i>resultType</i>, and a + reduction kernel produces an unrepresentible value, + then <code><i>javaFutureType</i>.get()</code> throws an exception.</p> + +<h4 id="devec">Method 3 and <i>devecSiInXType</i></h4> + +<p><strong><i>devecSiInXType</i></strong> is the Java type corresponding to + the <i>inXType</i> of the corresponding argument of + the <a href="#accumulator-function">accumulator function</a>. Unless <i>inXType</i> is an + unsigned type or a vector type, <i>devecSiInXType</i> is the directly corresponding Java + type. If <i>inXType</i> is an unsigned scalar type, then <i>devecSiInXType</i> is the + Java type directly corresponding to the signed scalar type of the same + size. If <i>inXType</i> is a signed vector type, then <i>devecSiInXType</i> is the Java + type directly corresponding to the vector component type. If <i>inXType</i> is an unsigned + vector type, then <i>devecSiInXType</i> is the Java type directly corresponding to the + signed scalar type of the same size as the vector component type. For example:</p> +<ul> +<li>If <i>inXType</i> is <code>int</code>, then <i>devecSiInXType</i> + is <code>int</code>.</li> +<li>If <i>inXType</i> is <code>int2</code>, then <i>devecSiInXType</i> + is <code>int</code>. The array is a <em>flattened</em> representation: It has twice as + many <em>scalar</em> Elements as the Allocation has 2-component <em>vector</em> + Elements. This is the same way that the <code>copyFrom()</code> methods of {@link + android.renderscript.Allocation} work.</li> +<li>If <i>inXType</i> is <code>uint</code>, then <i>deviceSiInXType</i> + is <code>int</code>. A signed value in the Java array is interpreted as an unsigned value of + the same bitpattern in the Allocation. This is the same way that the <code>copyFrom()</code> + methods of {@link android.renderscript.Allocation} work.</li> +<li>If <i>inXType</i> is <code>uint2</code>, then <i>deviceSiInXType</i> + is <code>int</code>. This is a combination of the way <code>int2</code> and <code>uint</code> + are handled: The array is a flattened representation, and Java array signed values are + interpreted as RenderScript unsigned Element values.</li> +</ul> + +<p>Note that for <a href="#reduce-method-3">Method 3</a>, input types are handled differently +than result types:</p> + +<ul> +<li>A script's vector input is flattened on the Java side, whereas a script's vector result is not.</li> +<li>A script's unsigned input is represented as a signed input of the same size on the Java + side, whereas a script's unsigned result is represented as a widened signed type on the Java + side (except in the case of <code>ulong</code>).</li> +</ul> + +<h3 id="more-example">More example reduction kernels</h3> + +<pre id="dot-product"> +#pragma rs reduce(dotProduct) \ + accumulator(dotProductAccum) combiner(dotProductSum) + +// Note: No initializer function -- therefore, +// each accumulator data item is implicitly initialized to 0.0f. + +static void dotProductAccum(float *accum, float in1, float in2) { + *accum += in1*in2; +} + +// combiner function +static void dotProductSum(float *accum, const float *val) { + *accum += *val; +} +</pre> + +<pre> +// Find a zero Element in a 2D allocation; return (-1, -1) if none +#pragma rs reduce(fz2) \ + initializer(fz2Init) \ + accumulator(fz2Accum) combiner(fz2Combine) + +static void fz2Init(int2 *accum) { accum->x = accum->y = -1; } + +static void fz2Accum(int2 *accum, + int inVal, + int x /* special arg */, + int y /* special arg */) { + if (inVal==0) { + accum->x = x; + accum->y = y; + } +} + +static void fz2Combine(int2 *accum, const int2 *accum2) { + if (accum2->x >= 0) *accum = *accum2; +} +</pre> + +<pre> +// Note that this kernel returns an array to Java +#pragma rs reduce(histogram) \ + accumulator(hsgAccum) combiner(hsgCombine) + +#define BUCKETS 256 +typedef uint32_t Histogram[BUCKETS]; + +// Note: No initializer function -- +// therefore, each bucket is implicitly initialized to 0. + +static void hsgAccum(Histogram *h, uchar in) { ++(*h)[in]; } + +static void hsgCombine(Histogram *accum, + const Histogram *addend) { + for (int i = 0; i < BUCKETS; ++i) + (*accum)[i] += (*addend)[i]; +} + +// Determines the mode (most frequently occurring value), and returns +// the value and the frequency. +// +// If multiple values have the same highest frequency, returns the lowest +// of those values. +// +// Shares functions with the histogram reduction kernel. +#pragma rs reduce(mode) \ + accumulator(hsgAccum) combiner(hsgCombine) \ + outconverter(modeOutConvert) + +static void modeOutConvert(int2 *result, const Histogram *h) { + uint32_t mode = 0; + for (int i = 1; i < BUCKETS; ++i) + if ((*h)[i] > (*h)[mode]) mode = i; + result->x = mode; + result->y = (*h)[mode]; +} +</pre> diff --git a/docs/html/training/implementing-navigation/nav-drawer.jd b/docs/html/training/implementing-navigation/nav-drawer.jd index d359a47e6413..abc79b649926 100644 --- a/docs/html/training/implementing-navigation/nav-drawer.jd +++ b/docs/html/training/implementing-navigation/nav-drawer.jd @@ -173,7 +173,7 @@ android.app.Fragment} into the main content view (the <pre> private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index c16e08a7c428..0ca36f6a89b8 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Grootste"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Gepasmaak (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hulp en terugvoer"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 6e9dcd7fc193..d40691cd9267 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"በጣም ተለቅ ያለ"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ብጁ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"እገዛ እና ግብረመልስ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index ee579dc30b00..12b9302d9100 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"أكبر مستوى"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"مخصص (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"المساعدة والتعليقات"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-az-rAZ/strings.xml b/packages/SettingsLib/res/values-az-rAZ/strings.xml index 60d0169be4b0..296725f6a0c2 100644 --- a/packages/SettingsLib/res/values-az-rAZ/strings.xml +++ b/packages/SettingsLib/res/values-az-rAZ/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Ən böyük"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Fərdi (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Yardım və rəy"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 060a5fb15be3..fb0bf542a6e4 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Najveći"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Prilagođeni (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-be-rBY/strings.xml b/packages/SettingsLib/res/values-be-rBY/strings.xml index 03de8bab32c1..e9c7f9b8b10a 100644 --- a/packages/SettingsLib/res/values-be-rBY/strings.xml +++ b/packages/SettingsLib/res/values-be-rBY/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Найвялікшы"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Карыстальніцкі (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Даведка і водгукі"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index f6596dc48add..f5e13866b92e 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Най-голямо"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Персонализирано (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Помощ и отзиви"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bn-rBD/strings.xml b/packages/SettingsLib/res/values-bn-rBD/strings.xml index 4a11198be5f9..614637228421 100644 --- a/packages/SettingsLib/res/values-bn-rBD/strings.xml +++ b/packages/SettingsLib/res/values-bn-rBD/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"বৃহত্তম"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"কাস্টম (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"সহায়তা ও মতামত"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-bs-rBA/strings.xml b/packages/SettingsLib/res/values-bs-rBA/strings.xml index 40a3630a4e55..6426ac808f3c 100644 --- a/packages/SettingsLib/res/values-bs-rBA/strings.xml +++ b/packages/SettingsLib/res/values-bs-rBA/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Najveće"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Prilagodi (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 99850a5d5cd4..46baecd0ca0a 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Màxim"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalitzat (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ajuda i suggeriments"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index 361ba1f0cef2..8743c0fa4052 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Největší"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Vlastní (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Nápověda a zpětná vazba"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 98f41fc8945d..9c7e76c74552 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Størst"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Tilpasset (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hjælp og feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index c9da6c96c0ff..c878d93ee043 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Am größten"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Benutzerdefiniert (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hilfe & Feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 05e4e64c383f..bda970a0c848 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Μεγαλύτερα"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Προσαρμοσμένη (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Βοήθεια και σχόλια"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 799802b86b3a..0f41d1991f61 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Largest"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Custom (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 799802b86b3a..0f41d1991f61 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Largest"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Custom (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 799802b86b3a..0f41d1991f61 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Largest"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Custom (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Help & feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 08e739a00f50..aa98329237c7 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Máximo"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizado (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ayuda y comentarios"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 3d2f6c132da0..23e01eab7f74 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Lo más grande posible"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizado (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ayuda y sugerencias"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-et-rEE/strings.xml b/packages/SettingsLib/res/values-et-rEE/strings.xml index a7b0f64f9a4d..6b98a193b71b 100644 --- a/packages/SettingsLib/res/values-et-rEE/strings.xml +++ b/packages/SettingsLib/res/values-et-rEE/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Suurim"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Kohandatud (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Abi ja tagasiside"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-eu-rES/strings.xml b/packages/SettingsLib/res/values-eu-rES/strings.xml index f476511b8106..c7525fbe7206 100644 --- a/packages/SettingsLib/res/values-eu-rES/strings.xml +++ b/packages/SettingsLib/res/values-eu-rES/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Handiena"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Pertsonalizatua (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Laguntza eta iritziak"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 44918513bcd7..ab4f70b14972 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"بزرگترین"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"سفارشی (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"راهنما و بازخورد"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index cdef968d90aa..d2d6959ac3bc 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Suurin"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Muokattu (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ohje ja palaute"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 958b8fd4d5d3..8230f87088af 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"La plus grande"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personnalisée (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Aide et commentaires"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index cf08f3b5f237..bad9ab28a389 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Le plus grand"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personnalisé (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Aide et commentaires"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gl-rES/strings.xml b/packages/SettingsLib/res/values-gl-rES/strings.xml index cdc4581ca80c..ed0d3f33448a 100644 --- a/packages/SettingsLib/res/values-gl-rES/strings.xml +++ b/packages/SettingsLib/res/values-gl-rES/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"O máis grande"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizado (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Axuda e suxestións"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-gu-rIN/strings.xml b/packages/SettingsLib/res/values-gu-rIN/strings.xml index 1b40e01737d1..79014b6a2ba3 100644 --- a/packages/SettingsLib/res/values-gu-rIN/strings.xml +++ b/packages/SettingsLib/res/values-gu-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"સૌથી મોટું"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"કસ્ટમ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"સહાય અને પ્રતિસાદ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 8dfbb4a58780..5edb8283088b 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"सबसे बड़ा"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"कस्टम (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"सहायता और फ़ीडबैक"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 07e492581d41..803a995ed149 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Najveće"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Prilagođeno (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomoć i povratne informacije"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 0d8ec26eb668..328fa2dfc877 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Legnagyobb"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Egyéni (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Súgó és visszajelzés"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-hy-rAM/strings.xml b/packages/SettingsLib/res/values-hy-rAM/strings.xml index cb12fbfaf502..d51527496587 100644 --- a/packages/SettingsLib/res/values-hy-rAM/strings.xml +++ b/packages/SettingsLib/res/values-hy-rAM/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Ամենամեծ"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Հատուկ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Օգնություն և հետադարձ կապ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index ffdb60745571..c56d58ded60e 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Terbesar"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"(<xliff:g id="DENSITYDPI">%d</xliff:g>) khusus"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Bantuan & masukan"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-is-rIS/strings.xml b/packages/SettingsLib/res/values-is-rIS/strings.xml index 25e13a4d69d8..5ce6be45cde5 100644 --- a/packages/SettingsLib/res/values-is-rIS/strings.xml +++ b/packages/SettingsLib/res/values-is-rIS/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Stærst"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Sérsniðið (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hjálp og ábendingar"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 2907fabef70e..24ec27786ca0 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Massimo"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizzato (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Guida e feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 7c16e4276347..f2235655dd0f 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"הכי גדול"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"מותאם אישית (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"עזרה ומשוב"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 25f4152a22fa..f356b95b5c08 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -343,4 +343,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"最大"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"カスタム(<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ヘルプとフィードバック"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ka-rGE/strings.xml b/packages/SettingsLib/res/values-ka-rGE/strings.xml index ffe194a0bc29..22a1f8a81228 100644 --- a/packages/SettingsLib/res/values-ka-rGE/strings.xml +++ b/packages/SettingsLib/res/values-ka-rGE/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"უდიდესი"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"მორგებული (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"დახმარება და გამოხმაურება"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-kk-rKZ/strings.xml b/packages/SettingsLib/res/values-kk-rKZ/strings.xml index 3dcc7eb50ced..30bae8bf5040 100644 --- a/packages/SettingsLib/res/values-kk-rKZ/strings.xml +++ b/packages/SettingsLib/res/values-kk-rKZ/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Ең үлкен"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Арнаулы (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Анықтама және пікір"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-km-rKH/strings.xml b/packages/SettingsLib/res/values-km-rKH/strings.xml index aa0ce2498737..8daf33862a99 100644 --- a/packages/SettingsLib/res/values-km-rKH/strings.xml +++ b/packages/SettingsLib/res/values-km-rKH/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ធំបំផុត"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ផ្ទាល់ខ្លួន (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ជំនួយ និងមតិស្ថាបនា"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-kn-rIN/strings.xml b/packages/SettingsLib/res/values-kn-rIN/strings.xml index 643875fa6ea0..8ca9035630e2 100644 --- a/packages/SettingsLib/res/values-kn-rIN/strings.xml +++ b/packages/SettingsLib/res/values-kn-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ದೊಡ್ಡ"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ಕಸ್ಟಮ್ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ಸಹಾಯ ಮತ್ತು ಪ್ರತಿಕ್ರಿಯೆ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index aaf34631b7fb..47781458516e 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -340,5 +340,7 @@ <string name="screen_zoom_summary_very_large" msgid="7108563375663670067">"더 크게"</string> <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"가장 크게"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"맞춤(<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> - <string name="help_feedback_label" msgid="6815040660801785649">"도움말 및 의견"</string> + <string name="help_feedback_label" msgid="6815040660801785649">"고객센터"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ky-rKG/strings.xml b/packages/SettingsLib/res/values-ky-rKG/strings.xml index 96c1ec024aa6..eff06a47ddc8 100644 --- a/packages/SettingsLib/res/values-ky-rKG/strings.xml +++ b/packages/SettingsLib/res/values-ky-rKG/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Эң чоң"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Ыңгайлаштырылган (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Жардам жана жооп пикир"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lo-rLA/strings.xml b/packages/SettingsLib/res/values-lo-rLA/strings.xml index 24f0c16c4962..87449fab6ec2 100644 --- a/packages/SettingsLib/res/values-lo-rLA/strings.xml +++ b/packages/SettingsLib/res/values-lo-rLA/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ໃຫຍ່ທີ່ສຸດ"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ປັບແຕ່ງເອງ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ຊ່ວຍເຫຼືອ & ຄຳຕິຊົມ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 4b8890d04d33..fa4812a8a1c1 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Didžiausias"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Tinkintas (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pagalba ir atsiliepimai"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 4d76adc3e291..568ab4c02b68 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Vislielākais"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Pielāgots (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Palīdzība un atsauksmes"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mk-rMK/strings.xml b/packages/SettingsLib/res/values-mk-rMK/strings.xml index 953360a7aa00..991d60c92a62 100644 --- a/packages/SettingsLib/res/values-mk-rMK/strings.xml +++ b/packages/SettingsLib/res/values-mk-rMK/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Најголем"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Приспособен (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Помош и повратни информации"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ml-rIN/strings.xml b/packages/SettingsLib/res/values-ml-rIN/strings.xml index 92c04487ace2..38d56e0e0917 100644 --- a/packages/SettingsLib/res/values-ml-rIN/strings.xml +++ b/packages/SettingsLib/res/values-ml-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ഏറ്റവും വലുത്"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ഇഷ്ടാനുസൃതം ( <xliff:g id="DENSITYDPI">%d</xliff:g> )"</string> <string name="help_feedback_label" msgid="6815040660801785649">"സഹായവും പ്രതികരണവും"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mn-rMN/strings.xml b/packages/SettingsLib/res/values-mn-rMN/strings.xml index 4c98c04c07d4..beb80e89c283 100644 --- a/packages/SettingsLib/res/values-mn-rMN/strings.xml +++ b/packages/SettingsLib/res/values-mn-rMN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Хамгийн том"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Тогтмол утга (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Тусламж, санал хүсэлт"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-mr-rIN/strings.xml b/packages/SettingsLib/res/values-mr-rIN/strings.xml index e3a7cc46cd44..10aa5601709f 100644 --- a/packages/SettingsLib/res/values-mr-rIN/strings.xml +++ b/packages/SettingsLib/res/values-mr-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"सर्वात मोठा"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"सानुकूल करा (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"मदत आणि अभिप्राय"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ms-rMY/strings.xml b/packages/SettingsLib/res/values-ms-rMY/strings.xml index a1caa2a94419..5c66ddb707ad 100644 --- a/packages/SettingsLib/res/values-ms-rMY/strings.xml +++ b/packages/SettingsLib/res/values-ms-rMY/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Terbesar"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Tersuai (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Bantuan & maklum balas"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-my-rMM/strings.xml b/packages/SettingsLib/res/values-my-rMM/strings.xml index 6a1c33190486..4f5ea65e47d0 100644 --- a/packages/SettingsLib/res/values-my-rMM/strings.xml +++ b/packages/SettingsLib/res/values-my-rMM/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"အကြီးဆုံး"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"စိတ်ကြိုက် (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"အကူအညီနှင့် အကြံပြုချက်"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index 5d8ae55fb592..1a0761400e2b 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Størst"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Egendefinert (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hjelp og tilbakemelding"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ne-rNP/strings.xml b/packages/SettingsLib/res/values-ne-rNP/strings.xml index 15cc8ea87c72..e73b07ec3903 100644 --- a/packages/SettingsLib/res/values-ne-rNP/strings.xml +++ b/packages/SettingsLib/res/values-ne-rNP/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"सबैभन्दा ठूलो"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"अनुकूलन (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"मद्दत र प्रतिक्रिया"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 12bdb4f30c06..83b6c7414f46 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Grootst"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Aangepast (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Help en feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pa-rIN/strings.xml b/packages/SettingsLib/res/values-pa-rIN/strings.xml index 21d11b00cd4b..47ed7e37d316 100644 --- a/packages/SettingsLib/res/values-pa-rIN/strings.xml +++ b/packages/SettingsLib/res/values-pa-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ਸਭ ਤੋਂ ਵੱਡਾ"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"ਵਿਸ਼ੇਸ਼-ਵਿਉਂਤਬੱਧ (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ਮਦਦ ਅਤੇ ਪ੍ਰਤੀਕਰਮ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index d5116a2e3d7c..1f07580ff1e2 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Największy"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Niestandardowe (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomoc i opinie"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index f0cfa23df334..dca0f8efd88d 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Maior"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizada (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 40dfc74806b3..d5910aca98e9 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"O maior"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizado (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e comentários"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index f0cfa23df334..dca0f8efd88d 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Maior"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizada (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ajuda e feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 6cc0f87e1b31..cfc0ea8fa69a 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Cel mai mare"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Personalizat (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ajutor și feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index ad4db89418b3..38c8f0eac7a8 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Максимальный"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Другой (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Справка/отзыв"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-si-rLK/strings.xml b/packages/SettingsLib/res/values-si-rLK/strings.xml index 5efb4001a638..d73df8a31b5d 100644 --- a/packages/SettingsLib/res/values-si-rLK/strings.xml +++ b/packages/SettingsLib/res/values-si-rLK/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"විශාලතම"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"අභිරුචි (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"උදව් සහ ප්රතිපෝෂණ"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 7a7e3d49d12d..10413d681b68 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Najväčšie"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Vlastné (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomocník a spätná väzba"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 10bff6e038a9..dba54539abbe 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Največje"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Po meri (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Pomoč in povratne informacije"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sq-rAL/strings.xml b/packages/SettingsLib/res/values-sq-rAL/strings.xml index e4f0eaa29596..326e8eab55a3 100644 --- a/packages/SettingsLib/res/values-sq-rAL/strings.xml +++ b/packages/SettingsLib/res/values-sq-rAL/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Më i madhi"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"I personalizuar (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Ndihma dhe komentet"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index fba58c50211c..bb1515db36c0 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Највећи"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Прилагођени (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Помоћ и повратне информације"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index ecc728a81eec..ecbdd91325ca 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Störst"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Anpassad (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Hjälp och feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 435a1bdd99be..b68dcb7c94e4 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Kubwa zaidi"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Kiwango maalum (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Usaidizi na maoni"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ta-rIN/strings.xml b/packages/SettingsLib/res/values-ta-rIN/strings.xml index 033955c77e91..03623bf592c3 100644 --- a/packages/SettingsLib/res/values-ta-rIN/strings.xml +++ b/packages/SettingsLib/res/values-ta-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"மிகப் பெரியது"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"தனிப்பயன் (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"உதவி & கருத்து"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-te-rIN/strings.xml b/packages/SettingsLib/res/values-te-rIN/strings.xml index 57581765ae4e..ec6aa02b8ec6 100644 --- a/packages/SettingsLib/res/values-te-rIN/strings.xml +++ b/packages/SettingsLib/res/values-te-rIN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"అతి పెద్దగా"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"అనుకూలం (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"సహాయం & అభిప్రాయం"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 4849e1955321..01348e0b984d 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"ใหญ่ที่สุด"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"กำหนดเอง (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"ความช่วยเหลือและความคิดเห็น"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 24f5499d4490..b58fb3156e7e 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Pinakamalaki"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Custom (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Tulong at feedback"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 950e3221ff7c..f50eeb046b32 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"En büyük"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Özel (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Yardım ve geri bildirim"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 6b49b14084e9..1ba10553da41 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Найбільші елементи"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Спеціальний масштаб (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Довідка й відгуки"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-ur-rPK/strings.xml b/packages/SettingsLib/res/values-ur-rPK/strings.xml index 2ac8a6145797..4211dd239407 100644 --- a/packages/SettingsLib/res/values-ur-rPK/strings.xml +++ b/packages/SettingsLib/res/values-ur-rPK/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"سب سے بڑا"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"حسب ضرورت (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"مدد اور تاثرات"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-uz-rUZ/strings.xml b/packages/SettingsLib/res/values-uz-rUZ/strings.xml index 9022ad31dacf..af30901ac8da 100644 --- a/packages/SettingsLib/res/values-uz-rUZ/strings.xml +++ b/packages/SettingsLib/res/values-uz-rUZ/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Eng katta"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Moslashtirilgan (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Yordam va fikr-mulohaza"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index c58f84993bb6..bb038e39d3cb 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Lớn nhất"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Tùy chỉnh (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Trợ giúp và phản hồi"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 18ae80e3d9b6..c4468e6e0ef1 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"最大"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"自定义 (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"帮助和反馈"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index 660071df7073..498988c1e0ec 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"最大"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"自訂 (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"說明與意見反映"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index bce6f244d1e4..69999c1f8629 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"最大"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"自訂 (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"說明與意見回饋"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 07c6fce2c3f9..ccf607a59fb4 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -341,4 +341,6 @@ <string name="screen_zoom_summary_extremely_large" msgid="7427320168263276227">"Okukhulu kakhulu"</string> <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Ngokwezifiso (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="help_feedback_label" msgid="6815040660801785649">"Usizo nempendulo"</string> + <!-- no translation found for content_description_menu_button (8182594799812351266) --> + <skip /> </resources> diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml index 0c8cc9bb5f02..f0c4e595106a 100644 --- a/packages/SystemUI/res/layout/remote_input.xml +++ b/packages/SystemUI/res/layout/remote_input.xml @@ -42,7 +42,6 @@ android:singleLine="true" android:ellipsize="start" android:inputType="textShortMessage|textAutoCorrect|textCapSentences" - android:textIsSelectable="true" android:imeOptions="actionNone|flagNoExtractUi" /> <FrameLayout diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 72420b2b88fb..808a2a158e7a 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -344,7 +344,7 @@ <string name="description_target_search" msgid="3091587249776033139">"ค้นหา"</string> <string name="description_direction_up" msgid="7169032478259485180">"เลื่อนขึ้นเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string> <string name="description_direction_left" msgid="7207478719805562165">"เลื่อนไปทางซ้ายเพื่อ <xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>"</string> - <string name="zen_priority_introduction" msgid="3070506961866919502">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นการปลุก การเตือนความจำ กิจกรรม และผู้โทรที่คุณระบุ"</string> + <string name="zen_priority_introduction" msgid="3070506961866919502">"คุณจะไม่ถูกรบกวนจากเสียงและการสั่น ยกเว้นการปลุก การช่วยเตือน กิจกรรม และผู้โทรที่คุณระบุ"</string> <string name="zen_priority_customize_button" msgid="7948043278226955063">"กำหนดค่า"</string> <string name="zen_silence_introduction_voice" msgid="2284540992298200729">"การใช้โหมดนี้จะบล็อกเสียงและการสั่นทั้งหมด ซึ่งรวมถึงเสียงปลุก เพลง วิดีโอ และเกม คุณจะยังโทรออกได้อยู่"</string> <string name="zen_silence_introduction" msgid="3137882381093271568">"การใช้โหมดนี้จะบล็อกเสียงและการสั่นทั้งหมด ซึ่งรวมถึงเสียงปลุก เพลง วิดีโอ และเกม"</string> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index 65d680509be2..15ae4ad6dac0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -126,8 +126,8 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene ? R.drawable.ic_qs_branded_vpn : R.drawable.ic_qs_vpn); if (mFooterIconId != footerIconId) { - mFooterIcon.setImageResource(footerIconId); mFooterIconId = footerIconId; + mMainHandler.post(mUpdateIcon); } mIsVisible = mIsIconVisible; } @@ -207,6 +207,13 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } } + private final Runnable mUpdateIcon = new Runnable() { + @Override + public void run() { + mFooterIcon.setImageResource(mFooterIconId); + } + }; + private final Runnable mUpdateDisplayState = new Runnable() { @Override public void run() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 2673ee8cb9f5..a6536a83183b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -2521,8 +2521,7 @@ public abstract class BaseStatusBar extends SystemUI implements boolean inUse = mPowerManager.isScreenOn() && (!mStatusBarKeyguardViewManager.isShowing() - || mStatusBarKeyguardViewManager.isOccluded()) - && !mStatusBarKeyguardViewManager.isInputRestricted(); + || mStatusBarKeyguardViewManager.isOccluded()); try { inUse = inUse && !mDreamManager.isDreaming(); } catch (RemoteException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 048c4bd4a770..8cabfb93bf32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -461,7 +461,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL // happens to occur during the launch. ActivityOptions o = ActivityOptions.makeBasic(); o.setRotationAnimationHint( - WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE); + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); try { result = ActivityManagerNative.getDefault().startActivityAsUser( null, getContext().getBasePackageName(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index e95cc6b3fed3..8db7c37b8fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3431,7 +3431,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // force the crossfade animation if an orientation change // happens to occur during the launch. options.setRotationAnimationHint( - WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE); + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); } try { result = ActivityManagerNative.getDefault().startActivityAsUser( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 22c67c952e5d..ea9ec5f46fdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -413,7 +413,10 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onDraw(Canvas canvas) { - canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint); + if (mCurrentBounds.top < mCurrentBounds.bottom) { + canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, + mBackgroundPaint); + } if (DEBUG) { int y = mTopPadding; canvas.drawLine(0, y, getWidth(), y, mDebugPaint); @@ -2049,11 +2052,12 @@ public class NotificationStackScrollLayout extends ViewGroup bottom = top; } if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) { - mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top); + top = (int) Math.max(mTopPadding + mStackTranslation, top); } else { // otherwise the animation from the shade to the keyguard will jump as it's maxed - mBackgroundBounds.top = Math.max(0, top); + top = Math.max(0, top); } + mBackgroundBounds.top = top; mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top)); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4994d6bc9206..ab83c11bd8e6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -364,6 +364,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean DEBUG_TRIAGED_MISSING = false; private static final boolean DEBUG_APP_DATA = false; + /** REMOVE. According to Svet, this was only used to reset permissions during development. */ static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false; private static final boolean DISABLE_EPHEMERAL_APPS = !Build.IS_DEBUGGABLE; @@ -801,10 +802,9 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.ActivityIntentInfo filter = filters.get(m); domainsSet.addAll(filter.getHostsList()); } - ArrayList<String> domainsList = new ArrayList<>(domainsSet); synchronized (mPackages) { if (mSettings.createIntentFilterVerificationIfNeededLPw( - packageName, domainsList) != null) { + packageName, domainsSet) != null) { scheduleWriteSettingsLocked(); } } @@ -2873,7 +2873,6 @@ public class PackageManagerService extends IPackageManager.Stub { SystemConfig systemConfig = SystemConfig.getInstance(); ArraySet<String> packages = systemConfig.getLinkedApps(); - ArraySet<String> domains = new ArraySet<String>(); for (String packageName : packages) { PackageParser.Package pkg = mPackages.get(packageName); @@ -2883,16 +2882,19 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } - domains.clear(); + ArraySet<String> domains = null; for (PackageParser.Activity a : pkg.activities) { for (ActivityIntentInfo filter : a.intents) { if (hasValidDomains(filter)) { + if (domains == null) { + domains = new ArraySet<String>(); + } domains.addAll(filter.getHostsList()); } } } - if (domains.size() > 0) { + if (domains != null && domains.size() > 0) { if (DEBUG_DOMAIN_VERIFICATION) { Slog.v(TAG, " + " + packageName); } @@ -2900,8 +2902,7 @@ public class PackageManagerService extends IPackageManager.Stub { // state w.r.t. the formal app-linkage "no verification attempted" state; // and then 'always' in the per-user state actually used for intent resolution. final IntentFilterVerificationInfo ivi; - ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, - new ArrayList<String>(domains)); + ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, domains); ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); mSettings.updateIntentFilterVerificationStatusLPw(packageName, INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, userId); @@ -8164,12 +8165,12 @@ public class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. - pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, - destResourceFile, pkg.applicationInfo.nativeLibraryRootDir, + pkgSetting = mSettings.getPackageWithBenefitsLPw(pkg, origPackage, realName, suid, + destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryRootDir, pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags, - user, false); + user); if (pkgSetting == null) { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Creating application package " + pkg.packageName + " failed"); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index e3866dfada26..a4604a65634c 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -30,15 +30,23 @@ final class PackageSetting extends PackageSettingBase { int appId; PackageParser.Package pkg; SharedUserSetting sharedUser; + /** + * Temporary holding space for the shared user ID. While parsing package settings, the + * shared users tag may be after the packages. In this case, we must delay linking the + * shared user setting with the package setting. The shared user ID lets us link the + * two objects. + */ + private int sharedUserId; PackageSetting(String name, String realName, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, String cpuAbiOverrideString, int pVersionCode, int pkgFlags, int privateFlags, String parentPackageName, - List<String> childPackageNames) { + List<String> childPackageNames, int sharedUserId) { super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode, pkgFlags, privateFlags, parentPackageName, childPackageNames); + this.sharedUserId = sharedUserId; } /** @@ -47,10 +55,14 @@ final class PackageSetting extends PackageSettingBase { */ PackageSetting(PackageSetting orig) { super(orig); + doCopy(orig); + } - appId = orig.appId; - pkg = orig.pkg; - sharedUser = orig.sharedUser; + public int getSharedUserId() { + if (sharedUser != null) { + return sharedUser.userId; + } + return sharedUserId; } @Override @@ -60,6 +72,18 @@ final class PackageSetting extends PackageSettingBase { + " " + name + "/" + appId + "}"; } + public void copyFrom(PackageSetting orig) { + super.copyFrom(orig); + doCopy(orig); + } + + private void doCopy(PackageSetting orig) { + appId = orig.appId; + pkg = orig.pkg; + sharedUser = orig.sharedUser; + sharedUserId = orig.sharedUserId; + } + public PermissionsState getPermissionsState() { return (sharedUser != null) ? sharedUser.getPermissionsState() diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 14ca72c14584..c08bc5743b57 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -147,51 +147,12 @@ abstract class PackageSettingBase extends SettingBase { secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode); } - /** - * New instance of PackageSetting with one-level-deep cloning. - */ - @SuppressWarnings("unchecked") + /** New instance of PackageSetting with one-level-deep cloning. */ PackageSettingBase(PackageSettingBase base) { super(base); - name = base.name; realName = base.realName; - codePath = base.codePath; - codePathString = base.codePathString; - resourcePath = base.resourcePath; - resourcePathString = base.resourcePathString; - legacyNativeLibraryPathString = base.legacyNativeLibraryPathString; - primaryCpuAbiString = base.primaryCpuAbiString; - secondaryCpuAbiString = base.secondaryCpuAbiString; - cpuAbiOverrideString = base.cpuAbiOverrideString; - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - versionCode = base.versionCode; - - uidError = base.uidError; - - signatures = new PackageSignatures(base.signatures); - - installPermissionsFixed = base.installPermissionsFixed; - userState.clear(); - for (int i=0; i<base.userState.size(); i++) { - userState.put(base.userState.keyAt(i), - new PackageUserState(base.userState.valueAt(i))); - } - installStatus = base.installStatus; - - origPackage = base.origPackage; - - installerPackageName = base.installerPackageName; - isOrphaned = base.isOrphaned; - volumeUuid = base.volumeUuid; - - keySetData = new PackageKeySetData(base.keySetData); - - parentPackageName = base.parentPackageName; - childPackageNames = (base.childPackageNames != null) - ? new ArrayList<>(base.childPackageNames) : null; + doCopy(base); } void init(File codePath, File resourcePath, String legacyNativeLibraryPathString, @@ -237,27 +198,47 @@ abstract class PackageSettingBase extends SettingBase { } /** - * Make a shallow copy of this package settings. + * Makes a shallow copy of the given package settings. + * + * NOTE: For some fields [such as keySetData, signatures, userState, verificationInfo, etc...], + * the original object is copied and a new one is not created. */ - public void copyFrom(PackageSettingBase base) { - mPermissionsState.copyFrom(base.mPermissionsState); - primaryCpuAbiString = base.primaryCpuAbiString; - secondaryCpuAbiString = base.secondaryCpuAbiString; - cpuAbiOverrideString = base.cpuAbiOverrideString; - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - signatures = base.signatures; - installPermissionsFixed = base.installPermissionsFixed; + public void copyFrom(PackageSettingBase orig) { + super.copyFrom(orig); + doCopy(orig); + } + + private void doCopy(PackageSettingBase orig) { + childPackageNames = (orig.childPackageNames != null) + ? new ArrayList<>(orig.childPackageNames) : null; + codePath = orig.codePath; + codePathString = orig.codePathString; + cpuAbiOverrideString = orig.cpuAbiOverrideString; + firstInstallTime = orig.firstInstallTime; + installPermissionsFixed = orig.installPermissionsFixed; + installStatus = orig.installStatus; + installerPackageName = orig.installerPackageName; + isOrphaned = orig.isOrphaned; + keySetData = orig.keySetData; + lastUpdateTime = orig.lastUpdateTime; + legacyNativeLibraryPathString = orig.legacyNativeLibraryPathString; + // Intentionally skip oldCodePaths; it's not relevant for copies + origPackage = orig.origPackage; + parentPackageName = orig.parentPackageName; + primaryCpuAbiString = orig.primaryCpuAbiString; + resourcePath = orig.resourcePath; + resourcePathString = orig.resourcePathString; + secondaryCpuAbiString = orig.secondaryCpuAbiString; + signatures = orig.signatures; + timeStamp = orig.timeStamp; + uidError = orig.uidError; userState.clear(); - for (int i=0; i<base.userState.size(); i++) { - userState.put(base.userState.keyAt(i), base.userState.valueAt(i)); + for (int i=0; i<orig.userState.size(); i++) { + userState.put(orig.userState.keyAt(i), orig.userState.valueAt(i)); } - installStatus = base.installStatus; - keySetData = base.keySetData; - verificationInfo = base.verificationInfo; - installerPackageName = base.installerPackageName; - volumeUuid = base.volumeUuid; + verificationInfo = orig.verificationInfo; + versionCode = orig.versionCode; + volumeUuid = orig.volumeUuid; } private PackageUserState modifyUserState(int userId) { diff --git a/services/core/java/com/android/server/pm/PendingPackage.java b/services/core/java/com/android/server/pm/PendingPackage.java deleted file mode 100644 index da73085985ba..000000000000 --- a/services/core/java/com/android/server/pm/PendingPackage.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 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.server.pm; - -import java.io.File; -import java.util.List; - -final class PendingPackage extends PackageSettingBase { - final int sharedId; - - PendingPackage(String name, String realName, File codePath, File resourcePath, - String legacyNativeLibraryPathString, String primaryCpuAbiString, - String secondaryCpuAbiString, String cpuAbiOverrideString, int sharedId, - int pVersionCode, int pkgFlags, int pkgPrivateFlags, String parentPackageName, - List<String> childPackageNames) { - super(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, - primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - pVersionCode, pkgFlags, pkgPrivateFlags, parentPackageName, childPackageNames); - this.sharedId = sharedId; - } -} diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java index 2921032b5572..71e8d515444b 100644 --- a/services/core/java/com/android/server/pm/SettingBase.java +++ b/services/core/java/com/android/server/pm/SettingBase.java @@ -30,10 +30,19 @@ abstract class SettingBase { mPermissionsState = new PermissionsState(); } - SettingBase(SettingBase base) { - pkgFlags = base.pkgFlags; - pkgPrivateFlags = base.pkgPrivateFlags; - mPermissionsState = new PermissionsState(base.mPermissionsState); + SettingBase(SettingBase orig) { + mPermissionsState = new PermissionsState(); + doCopy(orig); + } + + public void copyFrom(SettingBase orig) { + doCopy(orig); + } + + private void doCopy(SettingBase orig) { + pkgFlags = orig.pkgFlags; + pkgPrivateFlags = orig.pkgPrivateFlags; + mPermissionsState.copyFrom(orig.mPermissionsState); } public PermissionsState getPermissionsState() { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 31a1c36c7fd5..52313b1ce262 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -166,6 +166,7 @@ final class Settings { private static final boolean DEBUG_STOPPED = false; private static final boolean DEBUG_MU = false; private static final boolean DEBUG_KERNEL = false; + private static final boolean DEBUG_PARSER = false; private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml"; @@ -393,7 +394,7 @@ final class Settings { * TODO: make this just a local variable that is passed in during package * scanning to make it less confusing. */ - private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); + private final ArrayList<PackageSetting> mPendingPackages = new ArrayList<>(); private final File mSystemDir; @@ -427,18 +428,22 @@ final class Settings { mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); } - PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, + PackageSetting getPackageLPw(String pkgName) { + return peekPackageLPr(pkgName); + } + + PackageSetting getPackageWithBenefitsLPw(PackageParser.Package pkg, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbi, String secondaryCpuAbi, - int pkgFlags, int pkgPrivateFlags, UserHandle user, boolean add) + int pkgFlags, int pkgPrivateFlags, UserHandle user) throws PackageManagerException { final String name = pkg.packageName; final String parentPackageName = (pkg.parentPackage != null) ? pkg.parentPackage.packageName : null; - PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, - resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, secondaryCpuAbi, - pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user, add, true /*allowInstall*/, - parentPackageName, pkg.getChildPackageNames()); + PackageSetting p = getPackageWithBenefitsLPw(name, origPackage, realName, sharedUser, + codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbi, + secondaryCpuAbi, pkg.mVersionCode, pkgFlags, pkgPrivateFlags, user, + true /*allowInstall*/, parentPackageName, pkg.getChildPackageNames()); return p; } @@ -602,7 +607,7 @@ final class Settings { p = new PackageSetting(name, realName, codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName, - childPackageNames); + childPackageNames, 0 /*userId*/); p.appId = uid; if (addUserIdLPw(uid, p, name)) { mPackages.put(name, p); @@ -679,11 +684,11 @@ final class Settings { } } - private PackageSetting getPackageLPw(String name, PackageSetting origPackage, + private PackageSetting getPackageWithBenefitsLPw(String name, PackageSetting origPackage, String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags, - UserHandle installUser, boolean add, boolean allowInstall, String parentPackage, + UserHandle installUser, boolean allowInstall, String parentPackage, List<String> childPackageNames) throws PackageManagerException { final UserManagerService userManager = UserManagerService.getInstance(); final PackageSetting disabledPackage = getDisabledSystemPkgLPr(name); @@ -714,11 +719,6 @@ final class Settings { throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Creating application package " + name + " failed"); } - if (add) { - // Finish adding new package by adding it and updating shared - // user preferences - addPackageSettingLPw(p, name, sharedUser); - } } if (peekPackageLPr(name) != null) { final List<UserInfo> allUsers = getAllUsers(UserManagerService.getInstance()); @@ -857,7 +857,7 @@ final class Settings { pkgSetting = new PackageSetting(originalPkg.name, pkgName, codePath, resourcePath, legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi, null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags, - parentPkgName, childPkgNames); + parentPkgName, childPkgNames, 0 /*sharedUserId*/); if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + pkgName + " is adopting original package " + originalPkg.name); // Note that we will retain the new package's signature so @@ -876,7 +876,7 @@ final class Settings { pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, resourcePath, legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi, null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags, - parentPkgName, childPkgNames); + parentPkgName, childPkgNames, 0 /*sharedUserId*/); pkgSetting.setTimeStamp(codePath.lastModified()); pkgSetting.sharedUser = sharedUser; // If this is not a system app, it starts out stopped. @@ -1025,15 +1025,14 @@ final class Settings { if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { p.sharedUser.signatures.assignSignatures(pkg.mSignatures); } - addPackageSettingLPw(p, pkg.packageName, p.sharedUser); + addPackageSettingLPw(p, p.sharedUser); } // Utility method that adds a PackageSetting to mPackages and // completes updating the shared user attributes and any restored // app link verification state - private void addPackageSettingLPw(PackageSetting p, String name, - SharedUserSetting sharedUser) { - mPackages.put(name, p); + private void addPackageSettingLPw(PackageSetting p, SharedUserSetting sharedUser) { + mPackages.put(p.name, p); if (sharedUser != null) { if (p.sharedUser != null && p.sharedUser != sharedUser) { PackageManagerService.reportSettingsProblem(Log.ERROR, @@ -1067,12 +1066,12 @@ final class Settings { } } - IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(name); + IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(p.name); if (ivi != null) { if (DEBUG_DOMAIN_VERIFICATION) { - Slog.i(TAG, "Applying restored IVI for " + name + " : " + ivi.getStatusString()); + Slog.i(TAG, "Applying restored IVI for " + p.name + " : " + ivi.getStatusString()); } - mRestoredIntentFilterVerifications.remove(name); + mRestoredIntentFilterVerifications.remove(p.name); p.setIntentFilterVerificationInfo(ivi); } } @@ -1320,7 +1319,7 @@ final class Settings { /* package protected */ IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName, - ArrayList<String> domains) { + ArraySet<String> domains) { PackageSetting ps = mPackages.get(packageName); if (ps == null) { if (DEBUG_DOMAIN_VERIFICATION) { @@ -1595,7 +1594,9 @@ final class Settings { throws XmlPullParserException, IOException { IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser); packageSetting.setIntentFilterVerificationInfo(ivi); - Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName()); + if (DEBUG_PARSER) { + Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName()); + } } private void readRestoredIntentFilterVerifications(XmlPullParser parser) @@ -3039,30 +3040,22 @@ final class Settings { final int N = mPendingPackages.size(); for (int i = 0; i < N; i++) { - final PendingPackage pp = mPendingPackages.get(i); - Object idObj = getUserIdLPr(pp.sharedId); - if (idObj != null && idObj instanceof SharedUserSetting) { - try { - PackageSetting p = getPackageLPw(pp.name, null, pp.realName, - (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, - pp.legacyNativeLibraryPathString, pp.primaryCpuAbiString, - pp.secondaryCpuAbiString, pp.versionCode, pp.pkgFlags, - pp.pkgPrivateFlags, null /*installUser*/, true /*add*/, - false /*allowInstall*/, pp.parentPackageName, pp.childPackageNames); - p.copyFrom(pp); - } catch (PackageManagerException e) { - PackageManagerService.reportSettingsProblem(Log.WARN, - "Unable to create application package for " + pp.name); - continue; - } + final PackageSetting p = mPendingPackages.get(i); + final int sharedUserId = p.getSharedUserId(); + final Object idObj = getUserIdLPr(sharedUserId); + if (idObj instanceof SharedUserSetting) { + final SharedUserSetting sharedUser = (SharedUserSetting) idObj; + p.sharedUser = sharedUser; + p.appId = sharedUser.userId; + addPackageSettingLPw(p, sharedUser); } else if (idObj != null) { - String msg = "Bad package setting: package " + pp.name + " has shared uid " - + pp.sharedId + " that is not a shared uid\n"; + String msg = "Bad package setting: package " + p.name + " has shared uid " + + sharedUserId + " that is not a shared uid\n"; mReadMessages.append(msg); PackageManagerService.reportSettingsProblem(Log.ERROR, msg); } else { - String msg = "Bad package setting: package " + pp.name + " has shared uid " - + pp.sharedId + " that is not defined\n"; + String msg = "Bad package setting: package " + p.name + " has shared uid " + + sharedUserId + " that is not defined\n"; mReadMessages.append(msg); PackageManagerService.reportSettingsProblem(Log.ERROR, msg); } @@ -3535,7 +3528,7 @@ final class Settings { PackageSetting ps = new PackageSetting(name, realName, codePathFile, new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags, - parentPackageName, null); + parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/); String timeStampStr = parser.getAttributeValue(null, "ft"); if (timeStampStr != null) { try { @@ -3627,7 +3620,7 @@ final class Settings { long timeStamp = 0; long firstInstallTime = 0; long lastUpdateTime = 0; - PackageSettingBase packageSetting = null; + PackageSetting packageSetting = null; String version = null; int versionCode = 0; String parentPackageName; @@ -3746,7 +3739,8 @@ final class Settings { if (PackageManagerService.DEBUG_SETTINGS) Log.v(PackageManagerService.TAG, "Reading package: " + name + " userId=" + idStr + " sharedUserId=" + sharedIdStr); - int userId = idStr != null ? Integer.parseInt(idStr) : 0; + final int userId = idStr != null ? Integer.parseInt(idStr) : 0; + final int sharedUserId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; if (resourcePathStr == null) { resourcePathStr = codePathStr; } @@ -3765,7 +3759,7 @@ final class Settings { packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags, - pkgPrivateFlags, parentPackageName, null); + pkgPrivateFlags, parentPackageName, null /*childPackageNames*/); if (PackageManagerService.DEBUG_SETTINGS) Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId=" + userId + " pkg=" + packageSetting); @@ -3779,20 +3773,19 @@ final class Settings { packageSetting.lastUpdateTime = lastUpdateTime; } } else if (sharedIdStr != null) { - userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; - if (userId > 0) { - packageSetting = new PendingPackage(name.intern(), realName, new File( + if (sharedUserId > 0) { + packageSetting = new PackageSetting(name.intern(), realName, new File( codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, - userId, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName, - null); + versionCode, pkgFlags, pkgPrivateFlags, parentPackageName, + null /*childPackageNames*/, sharedUserId); packageSetting.setTimeStamp(timeStamp); packageSetting.firstInstallTime = firstInstallTime; packageSetting.lastUpdateTime = lastUpdateTime; - mPendingPackages.add((PendingPackage) packageSetting); + mPendingPackages.add(packageSetting); if (PackageManagerService.DEBUG_SETTINGS) Log.i(PackageManagerService.TAG, "Reading package " + name - + ": sharedUserId=" + userId + " pkg=" + packageSetting); + + ": sharedUserId=" + sharedUserId + " pkg=" + packageSetting); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: package " + name diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9ce147d29d63..7083ab7432bc 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2972,6 +2972,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } switch (animationHint) { case ROTATION_ANIMATION_CROSSFADE: + case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless. anim[0] = R.anim.rotation_animation_xfade_exit; anim[1] = R.anim.rotation_animation_enter; break; @@ -7829,14 +7830,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } + final WindowState w = mTopFullscreenOpaqueWindowState; + // We only enable seamless rotation if the top window has requested // it and is in the fullscreen opaque state. Seamless rotation // requires freezing various Surface states and won't work well // with animations, so we disable it in the animation case for now. - if (mTopFullscreenOpaqueWindowState != null && mTopIsFullscreen && - !mTopFullscreenOpaqueWindowState.isAnimatingLw() && - mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation == - ROTATION_ANIMATION_JUMPCUT) { + if (w != null && !w.isAnimatingLw() && + ((w.getAttrs().rotationAnimation == ROTATION_ANIMATION_JUMPCUT) || + (w.getAttrs().rotationAnimation == ROTATION_ANIMATION_SEAMLESS))) { return true; } return false; diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 216afe610261..c952ee65dc81 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -102,9 +102,8 @@ public class TrustManagerService extends SystemService { private static final int MSG_START_USER = 7; private static final int MSG_CLEANUP_USER = 8; private static final int MSG_SWITCH_USER = 9; - private static final int MSG_SET_DEVICE_LOCKED = 10; - private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 11; - private static final int MSG_UNLOCK_USER = 12; + private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10; + private static final int MSG_UNLOCK_USER = 11; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; @@ -352,20 +351,6 @@ public class TrustManagerService extends SystemService { } } - public void setDeviceLockedForUser(int userId, boolean locked) { - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { - synchronized (mDeviceLockedForUser) { - mDeviceLockedForUser.put(userId, locked); - } - if (locked) { - try { - ActivityManagerNative.getDefault().notifyLockedProfile(userId); - } catch (RemoteException e) { - } - } - } - } - boolean isDeviceLockedInner(int userId) { synchronized (mDeviceLockedForUser) { return mDeviceLockedForUser.get(userId, true); @@ -873,10 +858,19 @@ public class TrustManagerService extends SystemService { } @Override - public void setDeviceLockedForUser(int userId, boolean value) { + public void setDeviceLockedForUser(int userId, boolean locked) { enforceReportPermission(); - mHandler.obtainMessage(MSG_SET_DEVICE_LOCKED, value ? 1 : 0, userId) - .sendToTarget(); + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { + synchronized (mDeviceLockedForUser) { + mDeviceLockedForUser.put(userId, locked); + } + if (locked) { + try { + ActivityManagerNative.getDefault().notifyLockedProfile(userId); + } catch (RemoteException e) { + } + } + } } @Override @@ -952,9 +946,6 @@ public class TrustManagerService extends SystemService { mCurrentUser = msg.arg1; refreshDeviceLockedForUser(UserHandle.USER_ALL); break; - case MSG_SET_DEVICE_LOCKED: - setDeviceLockedForUser(msg.arg2, msg.arg1 != 0); - break; case MSG_FLUSH_TRUST_USUALLY_MANAGED: SparseBooleanArray usuallyManaged; synchronized (mTrustUsuallyManagedForUser) { diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index d3dab440c44a..05c05b159a78 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -498,6 +498,7 @@ class AppWindowToken extends WindowToken { } void clearAnimatingFlags() { + boolean wallpaperMightChange = false; for (int i = windows.size() - 1; i >= 0; i--) { final WindowState win = windows.get(i); // We don't want to clear it out for windows that get replaced, because the @@ -508,7 +509,6 @@ class AppWindowToken extends WindowToken { // by the client. We should let animation proceed and not clear this flag or // they won't eventually be removed by WindowStateAnimator#finishExit. if (!win.mWillReplaceWindow && !win.mRemoveOnExit) { - win.mAnimatingExit = false; // Clear mAnimating flag together with mAnimatingExit. When animation // changes from exiting to entering, we need to clear this flag until the // new animation gets applied, so that isAnimationStarting() becomes true @@ -516,15 +516,24 @@ class AppWindowToken extends WindowToken { // Otherwise applySurfaceChangesTransaction will faill to skip surface // placement for this window during this period, one or more frame will // show up with wrong position or scale. - win.mWinAnimator.mAnimating = false; - + if (win.mAnimatingExit) { + win.mAnimatingExit = false; + wallpaperMightChange = true; + } + if (win.mWinAnimator.mAnimating) { + win.mWinAnimator.mAnimating = false; + wallpaperMightChange = true; + } if (win.mDestroying) { win.mDestroying = false; mService.mDestroySurface.remove(win); + wallpaperMightChange = true; } } } - requestUpdateWallpaperIfNeeded(); + if (wallpaperMightChange) { + requestUpdateWallpaperIfNeeded(); + } } void destroySurfaces() { diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index db6ff7139360..b269d4c76c05 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -508,9 +508,14 @@ class WallpaperController { if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "New i: " + wallpaperTargetIndex + " old i: " + oldI); if (oldI >= 0) { - if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Animating wallpapers: old#" + oldI + "=" + oldW + "; new#" - + wallpaperTargetIndex + "=" + wallpaperTarget); + final boolean newTargetHidden = + wallpaperTarget.mAppToken != null && wallpaperTarget.mAppToken.hiddenRequested; + final boolean oldTargetHidden = + oldW.mAppToken != null && oldW.mAppToken.hiddenRequested; + if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + + " old#" + oldI + "=" + oldW + " hidden=" + oldTargetHidden + + " new#" + wallpaperTargetIndex + "=" + wallpaperTarget + + " hidden=" + newTargetHidden); // Set the upper and lower wallpaper targets correctly, // and make sure that we are positioning the wallpaper below the lower. @@ -520,6 +525,7 @@ class WallpaperController { "Found target above old target."); mUpperWallpaperTarget = wallpaperTarget; mLowerWallpaperTarget = oldW; + wallpaperTarget = oldW; wallpaperTargetIndex = oldI; } else { @@ -529,15 +535,21 @@ class WallpaperController { mUpperWallpaperTarget = oldW; mLowerWallpaperTarget = wallpaperTarget; } - - // If the new target is going hidden, set it back to the old target. - if (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.hiddenRequested) { + if (newTargetHidden && !oldTargetHidden) { if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target."); + // Use the old target if new target is hidden but old target + // is not. If they're both hidden, still use the new target. + mWallpaperTarget = oldW; + } else if (newTargetHidden == oldTargetHidden + && !mService.mOpeningApps.contains(wallpaperTarget.mAppToken) + && (mService.mOpeningApps.contains(oldW.mAppToken) + || mService.mClosingApps.contains(oldW.mAppToken))) { + // If they're both hidden (or both not hidden), prefer the one that's + // currently in opening or closing app list, this allows transition + // selection logic to better determine the wallpaper status of + // opening/closing apps. mWallpaperTarget = oldW; - wallpaperTarget = oldW; - wallpaperTargetIndex = oldI; } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 22120d016af4..19ad5e4563aa 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5776,6 +5776,20 @@ public class WindowManagerService extends IWindowManager.Stub rotateSeamlessly = false; break; } + // In what can only be called an unfortunate workaround we require + // seamlessly rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE + // flag. Due to limitations in the client API, there is no way for + // the client to set this flag in a race free fashion. If we seamlessly rotate + // a window which does not have this flag, but then gains it, we will get + // an incorrect visual result (rotated viewfinder). This means if we want to + // support seamlessly rotating windows which could gain this flag, we can't + // rotate windows without it. This limits seamless rotation in N to camera framework + // users, windows without children, and native code. This is unfortunate but + // having the camera work is our primary goal. + if (w.isChildWindow() & w.isVisibleNow() && + !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse()) { + rotateSeamlessly = false; + } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 546498a9a4bd..c83c083b2ec0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1325,7 +1325,8 @@ class WindowState extends WindowContainer implements WindowManagerPolicy.WindowS && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden) || mWinAnimator.mAnimation != null || ((atoken != null) && (atoken.mAppAnimator.animation != null) - && !mWinAnimator.isDummyAnimation())); + && !mWinAnimator.isDummyAnimation()) + || isAnimatingWithSavedSurface()); } /** diff --git a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java index daaa4f5c6848..2038c9e35f0c 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java +++ b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java @@ -136,6 +136,10 @@ class PreloadAppsInstaller { mApkToPackageMap.put(apkName, basePackageName); } installExistingPackage(basePackageName, userId, counter); + } else { + Log.e(TAG, "Package " + basePackageName + " cannot be installed from " + + apkName + ": " + msg + " (returnCode " + returnCode + ")"); + counter.appInstallFinished(); } } }.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId); diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index 7525e87c0e12..6b273210263d 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -89,8 +89,8 @@ public class RetailDemoModeService extends SystemService { private static final long SCREEN_WAKEUP_DELAY = 2500; private static final long USER_INACTIVITY_TIMEOUT_MIN = 10000; - private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 30000; - private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 6000; + private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 90000; + private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 0; private static final long MILLIS_PER_SECOND = 1000; private static final int[] VOLUME_STREAMS_TO_MUTE = { diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java index ba83be19bed9..64c562278227 100644 --- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java @@ -39,7 +39,7 @@ public class KeySetManagerServiceTest extends AndroidTestCase { public PackageSetting generateFakePackageSetting(String name) { return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"), new File(mContext.getCacheDir(), "fakeResPath"), "", "", "", - "", 1, 0, 0, null, null); + "", 1, 0, 0, null, null, 0 /*sharedUserId*/); } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index a9391344c7b4..09bd12c17a74 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -522,7 +522,8 @@ public class PackageManagerSettingsTests { pkgFlags, 0 /*privateFlags*/, null /*parentPackageName*/, - null /*childPackageNames*/); + null /*childPackageNames*/, + 0 /*sharedUserId*/); } private @NonNull List<UserInfo> createFakeUsers() { diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 306b3c15fa11..c4739ff13a19 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -224,6 +224,7 @@ final class RemoteConnectionService { conference.setState(parcel.getState()); conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); + conference.setConnectionProperties(parcel.getConnectionProperties()); mConferenceById.put(callId, conference); conference.registerCallback(new RemoteConference.Callback() { @Override diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index b4c4d05a7397..1e7875d435eb 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1033,7 +1033,6 @@ static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) return NO_ERROR; } - ResXMLTree tree; Asset* asset = assets.openNonAsset(cookie, "AndroidManifest.xml", Asset::ACCESS_STREAMING); if (asset == NULL) { fprintf(stderr, "ERROR: Platform AndroidManifest.xml not found\n"); @@ -1041,11 +1040,17 @@ static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) } ssize_t result = NO_ERROR; - if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { - fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n"); - result = UNKNOWN_ERROR; - } else { - result = extractPlatformBuildVersion(tree, bundle); + + // Create a new scope so that ResXMLTree is destroyed before we delete the memory over + // which it iterates (asset). + { + ResXMLTree tree; + if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n"); + result = UNKNOWN_ERROR; + } else { + result = extractPlatformBuildVersion(tree, bundle); + } } delete asset; diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index 1d39b722b053..a9794a419ca2 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -37,6 +37,16 @@ struct AppInfo { * The App's minimum SDK version. */ Maybe<std::string> minSdkVersion; + + /** + * The Version code of the app. + */ + Maybe<uint32_t> versionCode; + + /** + * The revision code of the app. + */ + Maybe<uint32_t> revisionCode; }; } // namespace aapt diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index e86f2a8830e8..725027c2c45a 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -41,13 +41,13 @@ private: public: DiagMessage() = default; - DiagMessage(const StringPiece& src) : mSource(src) { + explicit DiagMessage(const StringPiece& src) : mSource(src) { } - DiagMessage(const Source& src) : mSource(src) { + explicit DiagMessage(const Source& src) : mSource(src) { } - DiagMessage(size_t line) : mSource(Source().withLine(line)) { + explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) { } template <typename T> diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index a74b5aa2478e..ed55f852c24c 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "0"; +static const char* sMinorVersion = "1"; int printVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 505a982e5eab..b6aaa4df3bec 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -43,7 +43,7 @@ private: NameManglerPolicy mPolicy; public: - NameMangler(NameManglerPolicy policy) : mPolicy(policy) { + explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) { } Maybe<ResourceName> mangleName(const ResourceName& name) { diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a144c6ab2be7..bcdf401077ee 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -500,8 +500,8 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const }; // Process the raw value. - std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, - onCreateReference); + std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute( + rawValue, typeMask, onCreateReference); if (processedItem) { // Fix up the reference. if (Reference* ref = valueCast<Reference>(processedItem.get())) { @@ -528,20 +528,24 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { bool formatted = true; if (Maybe<StringPiece> formattedAttr = xml::findAttribute(parser, "formatted")) { - if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { + Maybe<bool> maybeFormatted = ResourceUtils::parseBool(formattedAttr.value()); + if (!maybeFormatted) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'formatted'. Must be a boolean"); return false; } + formatted = maybeFormatted.value(); } bool translateable = mOptions.translatable; if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { + Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } + translateable = maybeTranslateable.value(); } outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); @@ -590,7 +594,7 @@ bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* out outResource->name.type = *parsedType; if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) { - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value()); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeId.value() << "' in <public>"); @@ -630,7 +634,7 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource return false; } - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value()); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>"); @@ -1058,14 +1062,17 @@ bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* bool translateable = mOptions.translatable; if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { + Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } + translateable = maybeTranslateable.value(); } array->setTranslateable(translateable); + bool error = false; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 773616d6aa71..11619fa9c67d 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -124,7 +124,7 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, bool* ou return true; } -bool tryParseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate, +bool parseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate, bool* outPrivate) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr.empty()) { @@ -171,10 +171,10 @@ bool tryParseReference(const StringPiece& str, ResourceNameRef* outRef, bool* ou } bool isReference(const StringPiece& str) { - return tryParseReference(str, nullptr, nullptr, nullptr); + return parseReference(str, nullptr, nullptr, nullptr); } -bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) { +bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr.empty()) { return false; @@ -208,7 +208,7 @@ bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) } bool isAttributeReference(const StringPiece& str) { - return tryParseAttributeReference(str, nullptr); + return parseAttributeReference(str, nullptr); } /* @@ -271,13 +271,13 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) { ResourceNameRef ref; bool privateRef = false; - if (tryParseReference(str, &ref, outCreate, &privateRef)) { + if (parseReference(str, &ref, outCreate, &privateRef)) { std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); value->privateReference = privateRef; return value; } - if (tryParseAttributeReference(str, &ref)) { + if (parseAttributeReference(str, &ref)) { if (outCreate) { *outCreate = false; } @@ -420,23 +420,26 @@ std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) { return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); } -bool tryParseBool(const StringPiece& str, bool* outValue) { +Maybe<bool> parseBool(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") { - if (outValue) { - *outValue = true; - } - return true; + return Maybe<bool>(true); } else if (trimmedStr == "false" || trimmedStr == "FALSE" || trimmedStr == "False") { - if (outValue) { - *outValue = false; - } - return true; + return Maybe<bool>(false); } - return false; + return {}; +} + +Maybe<uint32_t> parseInt(const StringPiece& str) { + std::u16string str16 = util::utf8ToUtf16(str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return value.data; + } + return {}; } -Maybe<ResourceId> tryParseResourceId(const StringPiece& str) { +Maybe<ResourceId> parseResourceId(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); std::u16string str16 = util::utf8ToUtf16(trimmedStr); @@ -452,7 +455,7 @@ Maybe<ResourceId> tryParseResourceId(const StringPiece& str) { return {}; } -Maybe<int> tryParseSdkVersion(const StringPiece& str) { +Maybe<int> parseSdkVersion(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); std::u16string str16 = util::utf8ToUtf16(trimmedStr); @@ -470,12 +473,11 @@ Maybe<int> tryParseSdkVersion(const StringPiece& str) { } std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) { - bool result = false; - if (tryParseBool(str, &result)) { + if (Maybe<bool> maybeResult = parseBool(str)) { android::Res_value value = {}; value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - if (result) { + if (maybeResult.value()) { value.data = 0xffffffffu; } else { value.data = 0; @@ -542,7 +544,7 @@ uint32_t androidTypeToAttributeTypeMask(uint16_t type) { }; } -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, uint32_t typeMask, const std::function<void(const ResourceName&)>& onCreateReference) { @@ -602,11 +604,11 @@ std::unique_ptr<Item> parseItemForAttribute( * We successively try to parse the string as a resource type that the Attribute * allows. */ -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& str, const Attribute* attr, const std::function<void(const ResourceName&)>& onCreateReference) { const uint32_t typeMask = attr->typeMask; - std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); + std::unique_ptr<Item> value = tryParseItemForAttribute(str, typeMask, onCreateReference); if (value) { return value; } diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index a57d89dd0233..244047bae117 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -28,11 +28,6 @@ namespace aapt { namespace ResourceUtils { -/** - * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. - */ -Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name); - /* * Extracts the package, type, and name from a string of the format: * @@ -60,8 +55,8 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource, * If '+' was present in the reference, `outCreate` is set to true. * If '*' was present in the reference, `outPrivate` is set to true. */ -bool tryParseReference(const StringPiece& str, ResourceNameRef* outReference, - bool* outCreate = nullptr, bool* outPrivate = nullptr); +bool parseReference(const StringPiece& str, ResourceNameRef* outReference, + bool* outCreate = nullptr, bool* outPrivate = nullptr); /* * Returns true if the string is in the form of a resource reference (@[+][package:]type/name). @@ -72,7 +67,7 @@ bool isReference(const StringPiece& str); * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), * with `outReference` set to the parsed reference. */ -bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outReference); +bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference); /** * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). @@ -80,19 +75,29 @@ bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRefe bool isAttributeReference(const StringPiece& str); /** - * Returns true if the value is a boolean, putting the result in `outValue`. + * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. + */ +Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name); + +/** + * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, false, or False. + */ +Maybe<bool> parseBool(const StringPiece& str); + +/** + * Returns a uint32_t if the string is an integer. */ -bool tryParseBool(const StringPiece& str, bool* outValue); +Maybe<uint32_t> parseInt(const StringPiece& str); /** * Returns an ID if it the string represented a valid ID. */ -Maybe<ResourceId> tryParseResourceId(const StringPiece& str); +Maybe<ResourceId> parseResourceId(const StringPiece& str); /** * Parses an SDK version, which can be an integer, or a letter from A-Z. */ -Maybe<int> tryParseSdkVersion(const StringPiece& str); +Maybe<int> parseSdkVersion(const StringPiece& str); /* * Returns a Reference, or None Maybe instance if the string `str` was parsed as a @@ -161,11 +166,11 @@ std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr, * The callback function onCreateReference is called when the parsed item is a * reference to an ID that must be created (@+id/foo). */ -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, const Attribute* attr, const std::function<void(const ResourceName&)>& onCreateReference = {}); -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, uint32_t typeMask, const std::function<void(const ResourceName&)>& onCreateReference = {}); diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index fb76914cc495..894cfcf72144 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -21,24 +21,12 @@ namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - bool val = false; - EXPECT_TRUE(ResourceUtils::tryParseBool("true", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("TRUE", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("True", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("false", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("FALSE", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("False", &val)); - EXPECT_FALSE(val); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False")); } TEST(ResourceUtilsTest, ParseResourceName) { @@ -64,7 +52,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@color/foo", &actual, &create, &privateRef)); + EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_FALSE(privateRef); @@ -75,7 +63,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithPackage) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@android:color/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -87,7 +75,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("\t @android:color/foo\n \n\t", &actual, + EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -99,7 +87,7 @@ TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@+android:id/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_TRUE(create); @@ -111,7 +99,7 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@*android:id/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -122,7 +110,7 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool privateRef = false; ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::tryParseReference("@+android:color/foo", &actual, &create, + EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, &create, &privateRef)); } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 4a865799372d..73682ab110c7 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -449,9 +449,7 @@ void Attribute::print(std::ostream* out) const { printMask(out); if (!symbols.empty()) { - *out << " [" - << util::joiner(symbols.begin(), symbols.end(), ", ") - << "]"; + *out << " [" << util::joiner(symbols, ", ") << "]"; } if (minInt != std::numeric_limits<int32_t>::min()) { @@ -600,7 +598,7 @@ void Style::print(std::ostream* out) const { *out << parent.value().name.value(); } *out << " [" - << util::joiner(entries.begin(), entries.end(), ", ") + << util::joiner(entries, ", ") << "]"; } @@ -645,7 +643,7 @@ Array* Array::clone(StringPool* newPool) const { void Array::print(std::ostream* out) const { *out << "(array) [" - << util::joiner(items.begin(), items.end(), ", ") + << util::joiner(items, ", ") << "]"; } @@ -730,7 +728,7 @@ Styleable* Styleable::clone(StringPool* /*newPool*/) const { void Styleable::print(std::ostream* out) const { *out << "(styleable) " << " [" - << util::joiner(entries.begin(), entries.end(), ", ") + << util::joiner(entries, ", ") << "]"; } diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 319528e0ea1b..8a1021d49c39 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -35,7 +35,7 @@ struct Source { Source() = default; - inline Source(const StringPiece& path) : path(path.toString()) { + inline Source(const StringPiece& path) : path(path.toString()) { // NOLINT(implicit) } inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) { diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index b8bc5db7d6e4..9dc6a9c2b0d2 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -45,8 +45,9 @@ struct RawValueVisitor { virtual void visit(Styleable* value) {} }; +// NOLINT, do not add parentheses around T. #define DECL_VISIT_COMPOUND_VALUE(T) \ - virtual void visit(T* value) { \ + virtual void visit(T* value) { /* NOLINT */ \ visitSubValues(value); \ } diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index 345ff6c56870..f835b06e762b 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -32,7 +32,7 @@ struct PngOptions { class Png { public: - Png(IDiagnostics* diag) : mDiag(diag) { + explicit Png(IDiagnostics* diag) : mDiag(diag) { } bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h index 7db88de13536..91d17d174d29 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -43,7 +43,7 @@ public: kBidi, }; - Pseudolocalizer(Method method); + explicit Pseudolocalizer(Method method); void setMethod(Method method); std::string start() { return mImpl->start(); } std::string end() { return mImpl->end(); } diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index 9fc979c34353..3901419636b4 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -42,7 +42,7 @@ struct IdCollector : public xml::Visitor { for (xml::Attribute& attr : element->attributes) { ResourceNameRef name; bool create = false; - if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) { + if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) { if (create && name.type == ResourceType::kId) { auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), name, cmpName); diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h index 0ab01974044b..b416f202ced5 100644 --- a/tools/aapt2/flatten/TableFlattener.h +++ b/tools/aapt2/flatten/TableFlattener.h @@ -26,7 +26,7 @@ class ResourceTable; class TableFlattener : public IResourceTableConsumer { public: - TableFlattener(BigBuffer* buffer) : mBuffer(buffer) { + explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) { } bool consume(IAaptContext* context, ResourceTable* table) override; diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index f0559c03a8b8..72a932a499dd 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -29,7 +29,7 @@ namespace io { */ class RegularFile : public IFile { public: - RegularFile(const Source& source); + explicit RegularFile(const Source& source); std::unique_ptr<IData> openAsData() override; const Source& getSource() const override; @@ -42,7 +42,7 @@ class FileCollection; class FileCollectionIterator : public IFileCollectionIterator { public: - FileCollectionIterator(FileCollection* collection); + explicit FileCollectionIterator(FileCollection* collection); bool hasNext() override; io::IFile* next() override; diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 5ad119d1d6d4..565588e2db57 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -47,7 +47,7 @@ class ZipFileCollection; class ZipFileCollectionIterator : public IFileCollectionIterator { public: - ZipFileCollectionIterator(ZipFileCollection* collection); + explicit ZipFileCollectionIterator(ZipFileCollection* collection); bool hasNext() override; io::IFile* next() override; diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index d45328fedba2..e84c2741e4ce 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -110,7 +110,7 @@ using StringMember = PrimitiveMember<std::string>; template <typename T> class PrimitiveArrayMember : public ClassMember { public: - PrimitiveArrayMember(const StringPiece& name) : + explicit PrimitiveArrayMember(const StringPiece& name) : mName(name.toString()) { } diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index ff777a3b60eb..5a2bb6ad9a72 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -59,11 +59,14 @@ struct LinkOptions { std::string manifestPath; std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; + + // Java/Proguard options. Maybe<std::string> generateJavaClassPath; Maybe<std::string> customJavaPackage; std::set<std::string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; Maybe<std::string> generateMainDexProguardRulesPath; + bool noAutoVersion = false; bool noVersionVectors = false; bool staticLib = false; @@ -77,7 +80,13 @@ struct LinkOptions { Maybe<std::string> privateSymbols; ManifestFixerOptions manifestFixerOptions; std::unordered_set<std::string> products; + + // Split APK options. TableSplitterOptions tableSplitterOptions; + std::vector<SplitConstraints> splitConstraints; + std::vector<std::string> splitPaths; + + // Stable ID options. std::unordered_map<ResourceName, ResourceId> stableIdMap; Maybe<std::string> resourceIdMapPath; }; @@ -585,7 +594,7 @@ static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, const size_t resIdStrLen = line.size() - resIdStartIdx; StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen)); - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(resIdStr); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr); if (!maybeId) { diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '" << resIdStr << "'"); @@ -597,6 +606,28 @@ static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, return true; } +static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag, + std::string* outPath, SplitConstraints* outSplit) { + std::vector<std::string> parts = util::split(arg, ':'); + if (parts.size() != 2) { + diag->error(DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]"); + return false; + } + *outPath = parts[0]; + std::vector<ConfigDescription> configs; + for (const StringPiece& configStr : util::tokenize(parts[1], ',')) { + configs.push_back({}); + if (!ConfigDescription::parse(configStr, &configs.back())) { + diag->error(DiagMessage() << "invalid config '" << configStr + << "' in split parameter '" << arg << "'"); + return false; + } + } + outSplit->configs.insert(configs.begin(), configs.end()); + return true; +} + class LinkCommand { public: LinkCommand(LinkContext* context, const LinkOptions& options) : @@ -676,6 +707,30 @@ public: appInfo.package = packageAttr->value; + if (xml::Attribute* versionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:versionCode '" + << versionCodeAttr->value << "'"); + return {}; + } + appInfo.versionCode = maybeCode.value(); + } + + if (xml::Attribute* revisionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:revisionCode '" + << revisionCodeAttr->value << "'"); + return {}; + } + appInfo.revisionCode = maybeCode.value(); + } + if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) { if (xml::Attribute* minSdk = usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) { @@ -767,11 +822,11 @@ public: return true; } - std::unique_ptr<IArchiveWriter> makeArchiveWriter() { + std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createDirectoryArchiveWriter(mContext->getDiagnostics(), out); } else { - return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createZipFileArchiveWriter(mContext->getDiagnostics(), out); } } @@ -1179,6 +1234,94 @@ public: return true; } + std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo, + const SplitConstraints& constraints) { + std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + + std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>(); + namespaceAndroid->namespaceUri = xml::kSchemaAndroid; + namespaceAndroid->namespacePrefix = "android"; + + std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>(); + manifestEl->name = "manifest"; + manifestEl->attributes.push_back( + xml::Attribute{ "", "package", appInfo.package }); + + if (appInfo.versionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "versionCode", + std::to_string(appInfo.versionCode.value()) }); + } + + if (appInfo.revisionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "revisionCode", std::to_string(appInfo.revisionCode.value()) }); + } + + std::stringstream splitName; + splitName << "config." << util::joiner(constraints.configs, "_"); + + manifestEl->attributes.push_back( + xml::Attribute{ "", "split", splitName.str() }); + + std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>(); + applicationEl->name = "application"; + applicationEl->attributes.push_back( + xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" }); + + manifestEl->addChild(std::move(applicationEl)); + namespaceAndroid->addChild(std::move(manifestEl)); + doc->root = std::move(namespaceAndroid); + return doc; + } + + /** + * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + * to the IArchiveWriter. + */ + bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest, + ResourceTable* table) { + const bool keepRawValues = mOptions.staticLib; + bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer, + mContext); + if (!result) { + return false; + } + + ResourceFileFlattenerOptions fileFlattenerOptions; + fileFlattenerOptions.keepRawValues = keepRawValues; + fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; + fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; + fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; + fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; + fileFlattenerOptions.updateProguardSpec = + static_cast<bool>(mOptions.generateProguardRulesPath); + + ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet); + + if (!fileFlattener.flatten(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + return false; + } + + if (mOptions.staticLib) { + if (!flattenTableToPb(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc.flat"); + return false; + } + } else { + if (!flattenTable(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc"); + return false; + } + } + return true; + } + int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, @@ -1187,30 +1330,33 @@ public: return 1; } + // First extract the Package name without modifying it (via --rename-manifest-package). if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), mContext->getDiagnostics())) { - AppInfo& appInfo = maybeAppInfo.value(); + const AppInfo& appInfo = maybeAppInfo.value(); mContext->setCompilationPackage(appInfo.package); - if (appInfo.minSdkVersion) { - if (Maybe<int> maybeMinSdkVersion = - ResourceUtils::tryParseSdkVersion(appInfo.minSdkVersion.value())) { - mContext->setMinSdkVersion(maybeMinSdkVersion.value()); - } - } - } else { + } + + ManifestFixer manifestFixer(mOptions.manifestFixerOptions); + if (!manifestFixer.consume(mContext, manifestXml.get())) { return 1; } - if (!util::isJavaPackageName(mContext->getCompilationPackage())) { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "invalid package name '" - << mContext->getCompilationPackage() - << "'"); + Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), + mContext->getDiagnostics()); + if (!maybeAppInfo) { return 1; } - mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + const AppInfo& appInfo = maybeAppInfo.value(); + if (appInfo.minSdkVersion) { + if (Maybe<int> maybeMinSdkVersion = + ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) { + mContext->setMinSdkVersion(maybeMinSdkVersion.value()); + } + } + mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); if (mContext->getCompilationPackage() == "android") { mContext->setPackageId(0x01); } else { @@ -1258,9 +1404,7 @@ public: DiagMessage() << "failed moving private attributes"); return 1; } - } - if (!mOptions.staticLib) { // Assign IDs if we are building a regular app. IdAssigner idAssigner(&mOptions.stableIdMap); if (!idAssigner.consume(mContext, &mFinalTable)) { @@ -1304,45 +1448,118 @@ public: mContext->getExternalSymbols()->prependSource( util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); - { - ReferenceLinker linker; - if (!linker.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + ReferenceLinker linker; + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + return 1; + } + + if (mOptions.staticLib) { + if (!mOptions.products.empty()) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't select products when building static library"); + } + } else { + ProductFilter productFilter(mOptions.products); + if (!productFilter.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + return 1; + } + } + + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); return 1; } + } + + if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "collapsing resource versions for minimum SDK " + << mContext->getMinSdkVersion()); + } - if (mOptions.staticLib) { - if (!mOptions.products.empty()) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't select products when building static library"); + VersionCollapser collapser; + if (!collapser.consume(mContext, &mFinalTable)) { + return 1; + } + } + + proguard::KeepSet proguardKeepSet; + proguard::KeepSet proguardMainDexKeepSet; + + if (mOptions.staticLib) { + if (mOptions.tableSplitterOptions.configFilter != nullptr || + mOptions.tableSplitterOptions.preferredDensity) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't strip resources when building static library"); + } + } else { + // Adjust the SplitConstraints so that their SDK version is stripped if it is less + // than or equal to the minSdk. Otherwise the resources that have had their SDK version + // stripped due to minSdk won't ever match. + std::vector<SplitConstraints> adjustedConstraintsList; + adjustedConstraintsList.reserve(mOptions.splitConstraints.size()); + for (const SplitConstraints& constraints : mOptions.splitConstraints) { + SplitConstraints adjustedConstraints; + for (const ConfigDescription& config : constraints.configs) { + if (config.sdkVersion <= mContext->getMinSdkVersion()) { + adjustedConstraints.configs.insert(config.copyWithoutSdkVersion()); + } else { + adjustedConstraints.configs.insert(config); + } } + adjustedConstraintsList.push_back(std::move(adjustedConstraints)); + } - if (mOptions.tableSplitterOptions.configFilter != nullptr || - mOptions.tableSplitterOptions.preferredDensity) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't strip resources when building static library"); + TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions); + if (!tableSplitter.verifySplitConstraints(mContext)) { + return 1; + } + tableSplitter.splitTable(&mFinalTable); + + // Now we need to write out the Split APKs. + auto pathIter = mOptions.splitPaths.begin(); + auto splitConstraintsIter = adjustedConstraintsList.begin(); + for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(*pathIter) << "generating split with configurations '" + << util::joiner(splitConstraintsIter->configs, ", ") << "'"); } - } else { - ProductFilter productFilter(mOptions.products); - if (!productFilter.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter); + if (!archiveWriter) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); + return 1; + } + + // Generate an AndroidManifest.xml for each split. + std::unique_ptr<xml::XmlResource> splitManifest = + generateSplitManifest(appInfo, *splitConstraintsIter); + + XmlReferenceLinker linker; + if (!linker.consume(mContext, splitManifest.get())) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create Split AndroidManifest.xml"); return 1; } - // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file - // level. - TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); - if (!tableSplitter.verifySplitConstraints(mContext)) { + if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(), + splitTable.get())) { return 1; } - tableSplitter.splitTable(&mFinalTable); + + ++pathIter; + ++splitConstraintsIter; } } - proguard::KeepSet proguardKeepSet; - proguard::KeepSet proguardMainDexKeepSet; - - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); + // Start writing the base APK. + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath); if (!archiveWriter) { mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; @@ -1350,11 +1567,6 @@ public: bool error = false; { - ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(mContext, manifestXml.get())) { - error = true; - } - // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. @@ -1382,13 +1594,6 @@ public: error = true; } } - - const bool keepRawValues = mOptions.staticLib; - bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, - keepRawValues, archiveWriter.get(), mContext); - if (!result) { - error = true; - } } else { error = true; } @@ -1399,58 +1604,10 @@ public: return 1; } - if (!mOptions.noAutoVersion) { - AutoVersioner versioner; - if (!versioner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); - return 1; - } - } - - if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage() << "collapsing resource versions for minimum SDK " - << mContext->getMinSdkVersion()); - } - - VersionCollapser collapser; - if (!collapser.consume(mContext, &mFinalTable)) { - return 1; - } - } - - // Write out the table to an archive. Optimizations to the table should come before this - // step. - ResourceFileFlattenerOptions fileFlattenerOptions; - fileFlattenerOptions.keepRawValues = mOptions.staticLib; - fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; - fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; - fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; - fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; - fileFlattenerOptions.updateProguardSpec = - static_cast<bool>(mOptions.generateProguardRulesPath); - ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet); - - if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) { return 1; } - if (mOptions.staticLib) { - if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc.flat"); - return 1; - } - } else { - if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc"); - return 1; - } - } - if (mOptions.generateJavaClassPath) { JavaClassGeneratorOptions options; options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; @@ -1538,6 +1695,7 @@ int link(const std::vector<StringPiece>& args) { bool requireLocalization = false; bool verbose = false; Maybe<std::string> stableIdFilePath; + std::vector<std::string> splitArgs; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", @@ -1623,6 +1781,9 @@ int link(const std::vector<StringPiece>& args) { &options.manifestFixerOptions.renameInstrumentationTargetPackage) .optionalFlagList("-0", "File extensions not to compress", &options.extensionsToNotCompress) + .optionalFlagList("--split", "Split resources matching a set of configs out to a " + "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", + &splitArgs) .optionalSwitch("-v", "Enables verbose logging", &verbose); @@ -1741,6 +1902,16 @@ int link(const std::vector<StringPiece>& args) { ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); + // Parse the split parameters. + for (const std::string& splitArg : splitArgs) { + options.splitPaths.push_back({}); + options.splitConstraints.push_back({}); + if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(), + &options.splitConstraints.back())) { + return 1; + } + } + // Turn off auto versioning for static-libs. if (options.staticLib) { options.noAutoVersion = true; diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 55b587e57013..2e81266015e1 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -41,7 +41,7 @@ struct ManifestFixerOptions { */ class ManifestFixer : public IXmlResourceConsumer { public: - ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { + explicit ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { } bool consume(IAaptContext* context, xml::XmlResource* doc) override; diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h index d2edd38289dc..7724e140427b 100644 --- a/tools/aapt2/link/ProductFilter.h +++ b/tools/aapt2/link/ProductFilter.h @@ -29,7 +29,7 @@ class ProductFilter { public: using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; - ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { } + explicit ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { } ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name, const ResourceConfigValueIter begin, diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 6aa9c0edd059..be7aca3ca49f 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -138,7 +138,7 @@ private: const Attribute* attr) { if (RawString* rawString = valueCast<RawString>(value.get())) { std::unique_ptr<Item> transformed = - ResourceUtils::parseItemForAttribute(*rawString->value, attr); + ResourceUtils::tryParseItemForAttribute(*rawString->value, attr); // If we could not parse as any specific type, try a basic STRING. if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index a29d8dcfe3c6..59ffe15fd4a4 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -106,7 +106,7 @@ public: } const Attribute* attribute = &attr.compiledAttribute.value().attribute; - attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value, + attr.compiledValue = ResourceUtils::tryParseItemForAttribute(attr.value, attribute); if (!attr.compiledValue && !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index bd01b64281d5..bd31416a5cee 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -54,7 +54,7 @@ public: Symbol() : Symbol(Maybe<ResourceId>{}) { } - Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) { + explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) { } Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) : diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 95c0173f921a..a68d6f67324f 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,14 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.1 +### `aapt2 link ...` +- Configuration Split APK support: supports splitting resources that match a set of + configurations to a separate APK which can be loaded alongside the base APK on + API 21+ devices. This is done using the flag + `--split path/to/split.apk:<config1>[,<config2>,...]`. +- SDK version resource filtering: Resources with an SDK version qualifier that is unreachable + at runtime due to the minimum SDK level declared by the AndroidManifest.xml are stripped. + ## Version 2.0 ### `aapt2 compile ...` - Pseudo-localization: generates pseudolocalized versions of default strings when the diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 2dfe2a239900..08b9ee9cbe1b 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -17,6 +17,7 @@ #include "ConfigDescription.h" #include "ResourceTable.h" #include "split/TableSplitter.h" +#include "util/Util.h" #include <algorithm> #include <map> @@ -76,7 +77,6 @@ public: // in multiple splits. const ConfigDescription& config = entry.first; const std::vector<ResourceConfigValue*>& relatedValues = entry.second; - auto densityValueIter = mDensityDependentConfigToDensityMap.find(config); if (densityValueIter != mDensityDependentConfigToDensityMap.end()) { // Select the best one! @@ -89,12 +89,12 @@ public: thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { bestValue = thisValue; } - - // When we select one of these, they are all claimed such that the base - // doesn't include any anymore. - (*claimedValues)[thisValue] = true; } assert(bestValue); + + // When we select one of these, they are all claimed such that the base + // doesn't include any anymore. + (*claimedValues)[bestValue] = true; selected.push_back(bestValue); } } @@ -135,7 +135,6 @@ static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity, assert(bestValue); } } - bool TableSplitter::verifySplitConstraints(IAaptContext* context) { bool error = false; for (size_t i = 0; i < mSplitConstraints.size(); i++) { diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h index 15e0764c4259..2fa5c47d5bd6 100644 --- a/tools/aapt2/split/TableSplitter.h +++ b/tools/aapt2/split/TableSplitter.h @@ -60,7 +60,7 @@ public: void splitTable(ResourceTable* originalTable); - const std::vector<std::unique_ptr<ResourceTable>>& getSplits() { + std::vector<std::unique_ptr<ResourceTable>>& getSplits() { return mSplits; } diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp index bad02a5cb332..5150e82b6d93 100644 --- a/tools/aapt2/split/TableSplitter_test.cpp +++ b/tools/aapt2/split/TableSplitter_test.cpp @@ -52,6 +52,71 @@ TEST(TableSplitterTest, NoSplitPreferredDensity) { EXPECT_NE(nullptr, test::getValue<Id>(table.get(), "android:string/one")); } +TEST(TableSplitterTest, SplitTableByDensity) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png", + test::parseConfigOrDie("mdpi")) + .addFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png", + test::parseConfigOrDie("hdpi")) + .addFileReference("android:drawable/foo", "res/drawable-xhdpi/foo.png", + test::parseConfigOrDie("xhdpi")) + .addFileReference("android:drawable/foo", "res/drawable-xxhdpi/foo.png", + test::parseConfigOrDie("xxhdpi")) + .build(); + + std::vector<SplitConstraints> constraints; + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("mdpi") } }); + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("hdpi") } }); + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("xhdpi") } }); + + TableSplitter splitter(constraints, TableSplitterOptions{}); + splitter.splitTable(table.get()); + + ASSERT_EQ(3u, splitter.getSplits().size()); + + ResourceTable* splitOne = splitter.getSplits()[0].get(); + ResourceTable* splitTwo = splitter.getSplits()[1].get(); + ResourceTable* splitThree = splitter.getSplits()[2].get(); + + // Just xxhdpi should be in the base. + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + // Each split should have one and only one drawable. + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); +} + TEST(TableSplitterTest, SplitTableByConfigAndDensity) { ResourceTable table; @@ -78,12 +143,12 @@ TEST(TableSplitterTest, SplitTableByConfigAndDensity) { ResourceTable* splitOne = splitter.getSplits()[0].get(); ResourceTable* splitTwo = splitter.getSplits()[1].get(); - // Since a split was defined, all densities should be gone from base. + // All but the xxhdpi resource should be gone, since there were closer matches in land-xhdpi. EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-hdpi"))); EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", + EXPECT_NE(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-xxhdpi"))); EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo", diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index c0c016032921..637e991a573f 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -46,12 +46,12 @@ public: return *this; } - ResourceTableBuilder& addSimple(const StringPiece& name, const ResourceId id = {}) { + ResourceTableBuilder& addSimple(const StringPiece& name, const ResourceId& id = {}) { return addValue(name, id, util::make_unique<Id>()); } ResourceTableBuilder& addSimple(const StringPiece& name, const ConfigDescription& config, - const ResourceId id = {}) { + const ResourceId& id = {}) { return addValue(name, config, id, util::make_unique<Id>()); } @@ -59,7 +59,7 @@ public: return addReference(name, {}, ref); } - ResourceTableBuilder& addReference(const StringPiece& name, const ResourceId id, + ResourceTableBuilder& addReference(const StringPiece& name, const ResourceId& id, const StringPiece& ref) { return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref))); } @@ -68,12 +68,12 @@ public: return addString(name, {}, str); } - ResourceTableBuilder& addString(const StringPiece& name, const ResourceId id, + ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, const StringPiece& str) { return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); } - ResourceTableBuilder& addString(const StringPiece& name, const ResourceId id, + ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, const ConfigDescription& config, const StringPiece& str) { return addValue(name, config, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); @@ -83,7 +83,7 @@ public: return addFileReference(name, {}, path); } - ResourceTableBuilder& addFileReference(const StringPiece& name, const ResourceId id, + ResourceTableBuilder& addFileReference(const StringPiece& name, const ResourceId& id, const StringPiece& path) { return addValue(name, id, util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); @@ -100,13 +100,13 @@ public: return addValue(name, {}, std::move(value)); } - ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId id, + ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId& id, std::unique_ptr<Value> value) { return addValue(name, {}, id, std::move(value)); } ResourceTableBuilder& addValue(const StringPiece& name, const ConfigDescription& config, - const ResourceId id, std::unique_ptr<Value> value) { + const ResourceId& id, std::unique_ptr<Value> value) { ResourceName resName = parseNameOrDie(name); bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value), &mDiagnostics); @@ -114,7 +114,7 @@ public: return *this; } - ResourceTableBuilder& setSymbolState(const StringPiece& name, ResourceId id, + ResourceTableBuilder& setSymbolState(const StringPiece& name, const ResourceId& id, SymbolState state) { ResourceName resName = parseNameOrDie(name); Symbol symbol; @@ -130,7 +130,7 @@ public: }; inline std::unique_ptr<Reference> buildReference(const StringPiece& ref, - Maybe<ResourceId> id = {}) { + const Maybe<ResourceId>& id = {}) { std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref)); reference->id = id; return reference; @@ -151,7 +151,7 @@ private: public: template <typename... Args> - ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) { + explicit ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) { } template <typename... Args> @@ -175,7 +175,7 @@ private: std::unique_ptr<Attribute> mAttr; public: - AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) { + explicit AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) { mAttr->typeMask = android::ResTable_map::TYPE_ANY; } @@ -211,7 +211,7 @@ public: return *this; } - StyleBuilder& addItem(const StringPiece& str, ResourceId id, std::unique_ptr<Item> value) { + StyleBuilder& addItem(const StringPiece& str, const ResourceId& id, std::unique_ptr<Item> value) { addItem(str, std::move(value)); mStyle->entries.back().key.id = id; return *this; @@ -227,7 +227,7 @@ private: std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>(); public: - StyleableBuilder& addItem(const StringPiece& str, Maybe<ResourceId> id = {}) { + StyleableBuilder& addItem(const StringPiece& str, const Maybe<ResourceId>& id = {}) { mStyleable->entries.push_back(Reference(parseNameOrDie(str))); mStyleable->entries.back().id = id; return *this; diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 624a71a382c9..7fafcbeb1a04 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -104,7 +104,7 @@ private: Source mSource; public: - TestFile(const StringPiece& path) : mSource(path) {} + explicit TestFile(const StringPiece& path) : mSource(path) {} std::unique_ptr<io::IData> openAsData() override { return {}; diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index b053e07eafde..54f16db3d2f9 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -87,7 +87,7 @@ public: return *this; } - ContextBuilder& setNameManglerPolicy(NameManglerPolicy policy) { + ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) { mContext->mNameMangler = NameMangler(policy); return *this; } diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index cad2a2e63be1..ba8532f829e6 100644 --- a/tools/aapt2/util/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -63,7 +63,7 @@ public: * Create a BigBuffer with block allocation sizes * of blockSize. */ - BigBuffer(size_t blockSize); + explicit BigBuffer(size_t blockSize); BigBuffer(const BigBuffer&) = delete; // No copying. diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 4d8a1feb63b1..52c2052aec3d 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -109,7 +109,7 @@ bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outAr */ class FileFilter { public: - FileFilter(IDiagnostics* diag) : mDiag(diag) { + explicit FileFilter(IDiagnostics* diag) : mDiag(diag) { } /* diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index a31bc89fa98b..129f6d9d9716 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -43,12 +43,12 @@ public: Maybe(const Maybe& rhs); template <typename U> - Maybe(const Maybe<U>& rhs); + Maybe(const Maybe<U>& rhs); // NOLINT(implicit) Maybe(Maybe&& rhs); template <typename U> - Maybe(Maybe<U>&& rhs); + Maybe(Maybe<U>&& rhs); // NOLINT(implicit) Maybe& operator=(const Maybe& rhs); @@ -63,12 +63,12 @@ public: /** * Construct a Maybe holding a value. */ - Maybe(const T& value); + Maybe(const T& value); // NOLINT(implicit) /** * Construct a Maybe holding a value. */ - Maybe(T&& value); + Maybe(T&& value); // NOLINT(implicit) /** * True if this holds a value, false if diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h index f5c985bd9fff..4300a67d3581 100644 --- a/tools/aapt2/util/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h @@ -41,8 +41,8 @@ public: BasicStringPiece(); BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); - BasicStringPiece(const TChar* str); + BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit) + BasicStringPiece(const TChar* str); // NOLINT(implicit) BasicStringPiece(const TChar* str, size_t len); BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 4a10987b4400..998ecf7702bd 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -101,12 +101,16 @@ std::unique_ptr<T> make_unique(Args&&... args) { * Writes a set of items to the std::ostream, joining the times with the provided * separator. */ -template <typename Iterator> -::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end, - const char* sep) { - return [begin, end, sep](::std::ostream& out) -> ::std::ostream& { - for (auto iter = begin; iter != end; ++iter) { - if (iter != begin) { +template <typename Container> +::std::function<::std::ostream&(::std::ostream&)> joiner(const Container& container, + const char* sep) { + using std::begin; + using std::end; + const auto beginIter = begin(container); + const auto endIter = end(container); + return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& { + for (auto iter = beginIter; iter != endIter; ++iter) { + if (iter != beginIter) { out << sep; } out << *iter; @@ -242,7 +246,7 @@ private: const iterator mEnd; }; -inline Tokenizer tokenize(StringPiece str, char sep) { +inline Tokenizer tokenize(const StringPiece& str, char sep) { return Tokenizer(str, sep); } @@ -281,7 +285,7 @@ bool extractResFilePathParts(const StringPiece& path, StringPiece* outPrefix, * In the aapt namespace for lookup. */ inline ::std::ostream& operator<<(::std::ostream& out, - ::std::function<::std::ostream&(::std::ostream&)> f) { + const ::std::function<::std::ostream&(::std::ostream&)>& f) { return f(out); } diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index ee51b360feb4..a24d109abf34 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -60,7 +60,7 @@ public: static bool skipCurrentElement(XmlPullParser* parser); static bool isGoodEvent(Event event); - XmlPullParser(std::istream& in); + explicit XmlPullParser(std::istream& in); ~XmlPullParser(); /** diff --git a/tools/split-select/SplitSelector.h b/tools/split-select/SplitSelector.h index 193fda7077d3..120354f608f3 100644 --- a/tools/split-select/SplitSelector.h +++ b/tools/split-select/SplitSelector.h @@ -29,7 +29,7 @@ namespace split { class SplitSelector { public: SplitSelector(); - SplitSelector(const android::Vector<SplitDescription>& splits); + explicit SplitSelector(const android::Vector<SplitDescription>& splits); android::Vector<SplitDescription> getBestSplits(const SplitDescription& target) const; |