Loading core/java/android/app/AutomaticZenRule.java +1 −1 Original line number Diff line number Diff line Loading @@ -125,7 +125,7 @@ public final class AutomaticZenRule implements Parcelable { name = getTrimmedString(source.readString()); } interruptionFilter = source.readInt(); conditionId = source.readParcelable(null); conditionId = getTrimmedUri(source.readParcelable(null)); owner = getTrimmedComponentName(source.readParcelable(null)); configurationActivity = getTrimmedComponentName(source.readParcelable(null)); creationTime = source.readLong(); Loading core/java/android/hardware/usb/UsbDeviceConnection.java +64 −7 Original line number Diff line number Diff line Loading @@ -52,6 +52,8 @@ public class UsbDeviceConnection { private final CloseGuard mCloseGuard = CloseGuard.get(); private final Object mLock = new Object(); /** * UsbDevice should only be instantiated by UsbService implementation * @hide Loading @@ -62,6 +64,8 @@ public class UsbDeviceConnection { /* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) { mContext = context.getApplicationContext(); synchronized (mLock) { boolean wasOpened = native_open(name, pfd.getFileDescriptor()); if (wasOpened) { Loading @@ -70,6 +74,14 @@ public class UsbDeviceConnection { return wasOpened; } } /*** * @return If this connection is currently open and usable. */ boolean isOpen() { return mNativeContext != 0; } /** * @return The application context the connection was created for. Loading @@ -80,6 +92,49 @@ public class UsbDeviceConnection { return mContext; } /** * Cancel a request which relates to this connection. * * @return true if the request was successfully cancelled. */ /* package */ boolean cancelRequest(UsbRequest request) { synchronized (mLock) { if (!isOpen()) { return false; } return request.cancelIfOpen(); } } /** * This is meant to be called by UsbRequest's queue() in order to synchronize on * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. */ /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { synchronized (mLock) { if (!isOpen()) { return false; } return request.queueIfConnectionOpen(buffer, length); } } /** * This is meant to be called by UsbRequest's queue() in order to synchronize on * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. */ /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { synchronized (mLock) { if (!isOpen()) { return false; } return request.queueIfConnectionOpen(buffer); } } /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. Loading @@ -87,11 +142,13 @@ public class UsbDeviceConnection { * to retrieve a new instance to reestablish communication with the device. */ public void close() { if (mNativeContext != 0) { synchronized (mLock) { if (isOpen()) { native_close(); mCloseGuard.close(); } } } /** * Returns the native file descriptor for the device, or Loading core/java/android/hardware/usb/UsbRequest.java +79 −7 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ public class UsbRequest { * Releases all resources related to this request. */ public void close() { synchronized (mLock) { if (mNativeContext != 0) { mEndpoint = null; mConnection = null; Loading @@ -119,6 +120,7 @@ public class UsbRequest { mCloseGuard.close(); } } } @Override protected void finalize() throws Throwable { Loading Loading @@ -190,10 +192,32 @@ public class UsbRequest { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { UsbDeviceConnection connection = mConnection; if (connection == null) { // The expected exception by CTS Verifier - USB Device test throw new NullPointerException("invalid connection"); } // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent // the connection being closed while queueing. return connection.queueRequest(this, buffer, length); } /** * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over * there, to prevent the connection being closed while queueing. */ /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { UsbDeviceConnection connection = mConnection; if (connection == null || !connection.isOpen()) { // The expected exception by CTS Verifier - USB Device test throw new NullPointerException("invalid connection"); } boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } Loading Loading @@ -242,6 +266,28 @@ public class UsbRequest { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { UsbDeviceConnection connection = mConnection; if (connection == null) { // The expected exception by CTS Verifier - USB Device test throw new IllegalStateException("invalid connection"); } // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent // the connection being closed while queueing. return connection.queueRequest(this, buffer); } /** * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over * there, to prevent the connection being closed while queueing. */ /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { UsbDeviceConnection connection = mConnection; if (connection == null || !connection.isOpen()) { // The expected exception by CTS Verifier - USB Device test throw new IllegalStateException("invalid connection"); } // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); Loading @@ -259,7 +305,7 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { if (mConnection.getContext().getApplicationInfo().targetSdkVersion if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, Loading Loading @@ -362,6 +408,32 @@ public class UsbRequest { * @return true if cancelling succeeded */ public boolean cancel() { UsbDeviceConnection connection = mConnection; if (connection == null) { return false; } return connection.cancelRequest(this); } /** * Cancels a pending queue operation (for use when the UsbDeviceConnection associated * with this request is synchronized). This ensures we don't have a race where the * device is closed and then the request is canceled which would lead to a * use-after-free because the cancel operation uses the device connection * information freed in the when UsbDeviceConnection is closed.<br/> * * This method assumes the connected is not closed while this method is executed. * * @return true if cancelling succeeded. */ /* package */ boolean cancelIfOpen() { UsbDeviceConnection connection = mConnection; if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { Log.w(TAG, "Detected attempt to cancel a request on a connection which isn't open"); return false; } return native_cancel(); } Loading core/java/android/service/notification/Condition.java +34 −4 Original line number Diff line number Diff line Loading @@ -89,6 +89,12 @@ public final class Condition implements Parcelable { public final int flags; public final int icon; /** * The maximum string length for any string contained in this condition. * @hide */ public static final int MAX_STRING_LENGTH = 1000; /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule Loading @@ -103,16 +109,19 @@ public final class Condition implements Parcelable { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); this.id = id; this.summary = summary; this.line1 = line1; this.line2 = line2; this.id = getTrimmedUri(id); this.summary = getTrimmedString(summary); this.line1 = getTrimmedString(line1); this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { // This constructor passes all fields directly into the constructor that takes all the // fields as arguments; that constructor will trim each of the input strings to // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), source.readString(), Loading Loading @@ -239,4 +248,25 @@ public final class Condition implements Parcelable { return new Condition[size]; } }; /** * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. */ private static String getTrimmedString(String input) { if (input != null && input.length() > MAX_STRING_LENGTH) { return input.substring(0, MAX_STRING_LENGTH); } return input; } /** * Returns a truncated copy of the Uri by trimming the string representation to the maximum * string length. */ private static Uri getTrimmedUri(Uri input) { if (input != null && input.toString().length() > MAX_STRING_LENGTH) { return Uri.parse(getTrimmedString(input.toString())); } return input; } } core/tests/coretests/src/android/service/notification/ConditionTest.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.service.notification; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import android.net.Uri; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.common.base.Strings; import org.junit.Test; import org.junit.runner.RunWith; import java.lang.reflect.Field; @RunWith(AndroidJUnit4.class) @SmallTest public class ConditionTest { private static final String CLASS = "android.service.notification.Condition"; @Test public void testLongFields_inConstructors() { String longString = Strings.repeat("A", 65536); Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); // Confirm strings are truncated via short constructor Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); // Confirm strings are truncated via long constructor Condition cond2 = new Condition(longUri, longString, longString, longString, -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); } @Test public void testLongFields_viaParcel() { // Set fields via reflection to force them to be long, then parcel and unparcel to make sure // it gets truncated upon unparcelling. Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", Condition.STATE_TRUE); try { String longString = Strings.repeat("A", 65536); Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); Field id = Class.forName(CLASS).getDeclaredField("id"); id.setAccessible(true); id.set(cond, longUri); Field summary = Class.forName(CLASS).getDeclaredField("summary"); summary.setAccessible(true); summary.set(cond, longString); Field line1 = Class.forName(CLASS).getDeclaredField("line1"); line1.setAccessible(true); line1.set(cond, longString); Field line2 = Class.forName(CLASS).getDeclaredField("line2"); line2.setAccessible(true); line2.set(cond, longString); } catch (NoSuchFieldException e) { fail(e.toString()); } catch (ClassNotFoundException e) { fail(e.toString()); } catch (IllegalAccessException e) { fail(e.toString()); } Parcel parcel = Parcel.obtain(); cond.writeToParcel(parcel, 0); parcel.setDataPosition(0); Condition fromParcel = new Condition(parcel); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); } } Loading
core/java/android/app/AutomaticZenRule.java +1 −1 Original line number Diff line number Diff line Loading @@ -125,7 +125,7 @@ public final class AutomaticZenRule implements Parcelable { name = getTrimmedString(source.readString()); } interruptionFilter = source.readInt(); conditionId = source.readParcelable(null); conditionId = getTrimmedUri(source.readParcelable(null)); owner = getTrimmedComponentName(source.readParcelable(null)); configurationActivity = getTrimmedComponentName(source.readParcelable(null)); creationTime = source.readLong(); Loading
core/java/android/hardware/usb/UsbDeviceConnection.java +64 −7 Original line number Diff line number Diff line Loading @@ -52,6 +52,8 @@ public class UsbDeviceConnection { private final CloseGuard mCloseGuard = CloseGuard.get(); private final Object mLock = new Object(); /** * UsbDevice should only be instantiated by UsbService implementation * @hide Loading @@ -62,6 +64,8 @@ public class UsbDeviceConnection { /* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) { mContext = context.getApplicationContext(); synchronized (mLock) { boolean wasOpened = native_open(name, pfd.getFileDescriptor()); if (wasOpened) { Loading @@ -70,6 +74,14 @@ public class UsbDeviceConnection { return wasOpened; } } /*** * @return If this connection is currently open and usable. */ boolean isOpen() { return mNativeContext != 0; } /** * @return The application context the connection was created for. Loading @@ -80,6 +92,49 @@ public class UsbDeviceConnection { return mContext; } /** * Cancel a request which relates to this connection. * * @return true if the request was successfully cancelled. */ /* package */ boolean cancelRequest(UsbRequest request) { synchronized (mLock) { if (!isOpen()) { return false; } return request.cancelIfOpen(); } } /** * This is meant to be called by UsbRequest's queue() in order to synchronize on * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. */ /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { synchronized (mLock) { if (!isOpen()) { return false; } return request.queueIfConnectionOpen(buffer, length); } } /** * This is meant to be called by UsbRequest's queue() in order to synchronize on * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. */ /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { synchronized (mLock) { if (!isOpen()) { return false; } return request.queueIfConnectionOpen(buffer); } } /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. Loading @@ -87,11 +142,13 @@ public class UsbDeviceConnection { * to retrieve a new instance to reestablish communication with the device. */ public void close() { if (mNativeContext != 0) { synchronized (mLock) { if (isOpen()) { native_close(); mCloseGuard.close(); } } } /** * Returns the native file descriptor for the device, or Loading
core/java/android/hardware/usb/UsbRequest.java +79 −7 Original line number Diff line number Diff line Loading @@ -112,6 +112,7 @@ public class UsbRequest { * Releases all resources related to this request. */ public void close() { synchronized (mLock) { if (mNativeContext != 0) { mEndpoint = null; mConnection = null; Loading @@ -119,6 +120,7 @@ public class UsbRequest { mCloseGuard.close(); } } } @Override protected void finalize() throws Throwable { Loading Loading @@ -190,10 +192,32 @@ public class UsbRequest { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { UsbDeviceConnection connection = mConnection; if (connection == null) { // The expected exception by CTS Verifier - USB Device test throw new NullPointerException("invalid connection"); } // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent // the connection being closed while queueing. return connection.queueRequest(this, buffer, length); } /** * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over * there, to prevent the connection being closed while queueing. */ /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { UsbDeviceConnection connection = mConnection; if (connection == null || !connection.isOpen()) { // The expected exception by CTS Verifier - USB Device test throw new NullPointerException("invalid connection"); } boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } Loading Loading @@ -242,6 +266,28 @@ public class UsbRequest { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { UsbDeviceConnection connection = mConnection; if (connection == null) { // The expected exception by CTS Verifier - USB Device test throw new IllegalStateException("invalid connection"); } // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent // the connection being closed while queueing. return connection.queueRequest(this, buffer); } /** * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over * there, to prevent the connection being closed while queueing. */ /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { UsbDeviceConnection connection = mConnection; if (connection == null || !connection.isOpen()) { // The expected exception by CTS Verifier - USB Device test throw new IllegalStateException("invalid connection"); } // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); Loading @@ -259,7 +305,7 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { if (mConnection.getContext().getApplicationInfo().targetSdkVersion if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, Loading Loading @@ -362,6 +408,32 @@ public class UsbRequest { * @return true if cancelling succeeded */ public boolean cancel() { UsbDeviceConnection connection = mConnection; if (connection == null) { return false; } return connection.cancelRequest(this); } /** * Cancels a pending queue operation (for use when the UsbDeviceConnection associated * with this request is synchronized). This ensures we don't have a race where the * device is closed and then the request is canceled which would lead to a * use-after-free because the cancel operation uses the device connection * information freed in the when UsbDeviceConnection is closed.<br/> * * This method assumes the connected is not closed while this method is executed. * * @return true if cancelling succeeded. */ /* package */ boolean cancelIfOpen() { UsbDeviceConnection connection = mConnection; if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { Log.w(TAG, "Detected attempt to cancel a request on a connection which isn't open"); return false; } return native_cancel(); } Loading
core/java/android/service/notification/Condition.java +34 −4 Original line number Diff line number Diff line Loading @@ -89,6 +89,12 @@ public final class Condition implements Parcelable { public final int flags; public final int icon; /** * The maximum string length for any string contained in this condition. * @hide */ public static final int MAX_STRING_LENGTH = 1000; /** * An object representing the current state of a {@link android.app.AutomaticZenRule}. * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule Loading @@ -103,16 +109,19 @@ public final class Condition implements Parcelable { if (id == null) throw new IllegalArgumentException("id is required"); if (summary == null) throw new IllegalArgumentException("summary is required"); if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); this.id = id; this.summary = summary; this.line1 = line1; this.line2 = line2; this.id = getTrimmedUri(id); this.summary = getTrimmedString(summary); this.line1 = getTrimmedString(line1); this.line2 = getTrimmedString(line2); this.icon = icon; this.state = state; this.flags = flags; } public Condition(Parcel source) { // This constructor passes all fields directly into the constructor that takes all the // fields as arguments; that constructor will trim each of the input strings to // max length if necessary. this((Uri)source.readParcelable(Condition.class.getClassLoader()), source.readString(), source.readString(), Loading Loading @@ -239,4 +248,25 @@ public final class Condition implements Parcelable { return new Condition[size]; } }; /** * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. */ private static String getTrimmedString(String input) { if (input != null && input.length() > MAX_STRING_LENGTH) { return input.substring(0, MAX_STRING_LENGTH); } return input; } /** * Returns a truncated copy of the Uri by trimming the string representation to the maximum * string length. */ private static Uri getTrimmedUri(Uri input) { if (input != null && input.toString().length() > MAX_STRING_LENGTH) { return Uri.parse(getTrimmedString(input.toString())); } return input; } }
core/tests/coretests/src/android/service/notification/ConditionTest.java 0 → 100644 +101 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.service.notification; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import android.net.Uri; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.google.common.base.Strings; import org.junit.Test; import org.junit.runner.RunWith; import java.lang.reflect.Field; @RunWith(AndroidJUnit4.class) @SmallTest public class ConditionTest { private static final String CLASS = "android.service.notification.Condition"; @Test public void testLongFields_inConstructors() { String longString = Strings.repeat("A", 65536); Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); // Confirm strings are truncated via short constructor Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE); assertEquals(Condition.MAX_STRING_LENGTH, cond1.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, cond1.summary.length()); // Confirm strings are truncated via long constructor Condition cond2 = new Condition(longUri, longString, longString, longString, -1, Condition.STATE_TRUE, Condition.FLAG_RELEVANT_ALWAYS); assertEquals(Condition.MAX_STRING_LENGTH, cond2.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.summary.length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.line1.length()); assertEquals(Condition.MAX_STRING_LENGTH, cond2.line2.length()); } @Test public void testLongFields_viaParcel() { // Set fields via reflection to force them to be long, then parcel and unparcel to make sure // it gets truncated upon unparcelling. Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder", Condition.STATE_TRUE); try { String longString = Strings.repeat("A", 65536); Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530)); Field id = Class.forName(CLASS).getDeclaredField("id"); id.setAccessible(true); id.set(cond, longUri); Field summary = Class.forName(CLASS).getDeclaredField("summary"); summary.setAccessible(true); summary.set(cond, longString); Field line1 = Class.forName(CLASS).getDeclaredField("line1"); line1.setAccessible(true); line1.set(cond, longString); Field line2 = Class.forName(CLASS).getDeclaredField("line2"); line2.setAccessible(true); line2.set(cond, longString); } catch (NoSuchFieldException e) { fail(e.toString()); } catch (ClassNotFoundException e) { fail(e.toString()); } catch (IllegalAccessException e) { fail(e.toString()); } Parcel parcel = Parcel.obtain(); cond.writeToParcel(parcel, 0); parcel.setDataPosition(0); Condition fromParcel = new Condition(parcel); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.id.toString().length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.summary.length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length()); assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length()); } }