/* * Copyright (C) 2023 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.app.appsearch.safeparcel; import android.os.Parcel; import android.os.Parcelable; /** * A SafeParcelable is a special {@link Parcelable} interface that marshalls its fields in a * protobuf-like manner into a {@link Parcel}. The marshalling encodes a unique id for each field * along with the size in bytes of the field. By doing this, older versions of a SafeParcelable can * skip over unknown fields, which enables backwards compatibility. Because SafeParcelable extends a * Parcelable, it is NOT safe for persistence. * *
To prevent the need to manually write code to marshall fields like in a Parcelable, a * SafeParcelable implementing class is annotated with several annotations so that a generated * "creator" class has the metadata it needs to automatically generate the boiler plate marshalling * and unmarshalling code. * *
The main annotations are the following: * *
Because a SafeParcelable extends Parcelable, you must have a public static final member named * CREATOR and override writeToParcel() and describeContents(). Here's a typical example. * *
* @Class(creator="MySafeParcelableCreator", validate=true) * public class MySafeParcelable implements SafeParcelable { * public static final Parcelable.Creator<MySafeParcelable> CREATOR = * new MySafeParcelableCreator(); * * @Field(id=1) * public final String myString; * * @Field(id=2, getter="getInteger") * private final int myInteger; * * @Constructor * MySafeParcelable( * @Param(id=1) String string, * @Param(id=2) int integer) { * myString = string; * myInteger = integer; * ) * * // Example public constructor (not used by MySafeParcelableCreator) * public MySafeParcelable(String string, int integer) { * myString = string; * myInteger = integer; * } * * // This is only needed if validate=true in @Class annotation. * public void validateContents() { * // Add validation here. * } * * // This getter is needed because myInteger is private, and the generated creator class * // MySafeParcelableCreator can't access private member fields. * int getInteger() { * return myInteger; * } * * // This is necessary because SafeParcelable extends Parcelable. * // {@link AbstractSafeParcelable} implements this for you. * @Override * public int describeContents() { * return MySafeParcelableCreator.CONTENT_DESCRIPTION; * } * * // This is necessary because SafeParcelable extends Parcelable. * @Override * public void writeToParcel(Parcel out, int flags) { * // This invokes the generated MySafeParcelableCreator class's marshalling to a Parcel. * // In the event you need custom logic when writing to a Parcel, that logic can be * // inserted here. * MySafeParcelableCreator.writeToParcel(this, out, flags); * } * } ** * @hide */ // Include the SafeParcel source code directly in AppSearch until it gets officially open-sourced. public interface SafeParcelable extends Parcelable { /** @hide */ // Note: the field name and value are accessed using reflection for backwards compatibility, and // must not be changed. String NULL = "SAFE_PARCELABLE_NULL_STRING"; /** * This annotates your class and specifies the name of the generated "creator" class for * marshalling/unmarshalling a SafeParcelable to/from a {@link Parcel}. The "creator" class is * generated in the same package as the SafeParcelable class. You can also set "validate" to * true, which will cause the "creator" to invoke the method validateContents() on your class * after constructing an instance. */ @SuppressWarnings("JavaLangClash") @interface Class { /** * Simple name of the generated "creator" class generated in the same package as the * SafeParceable. */ String creator(); /** Whether the generated "creator" class is final. */ boolean creatorIsFinal() default true; /** * When set to true, invokes the validateContents() method in this SafeParcelable object * after constructing a new instance. */ boolean validate() default false; /** * When set to true, it will not write type default values to the Parcel. * *
boolean: false byte/char/short/int/long: 0 float: 0.0f double: 0.0 Objects/arrays: * null * *
Cannot be used with Field(defaultValue) */ boolean doNotParcelTypeDefaultValues() default false; } /** Use this annotation on members that you wish to be marshalled in the SafeParcelable. */ @interface Field { /** * Valid values for id are between 1 and 65535. This field id is marshalled into a Parcel . * To maintain backwards compatibility, never reuse old id's. It is okay to no longer use * old id's and add new ones in subsequent versions of a SafeParcelable. */ int id(); /** * This specifies the name of the getter method for retrieving the value of this field. This * must be specified for fields that do not have at least package visibility because the * "creator" class will be unable to access the value when attempting to marshall this * field. The getter method should take no parameters and return the type of this field * (unless overridden by the "type" attribute below). */ String getter() default NULL; /** * For advanced uses, this specifies the type for the field when marshalling and * unmarshalling by the "creator" class to be something different than the declared type of * the member variable. This is useful if you want to incorporate an object that is not * SafeParcelable (or a system Parcelable object). Be sure to enter the fully qualified name * for the class (i.e., android.os.Bundle and not Bundle). For example, * *
* @Class(creator="MyAdvancedCreator") * public class MyAdvancedSafeParcelable implements SafeParcelable { * public static final Parcelable.Creator<MyAdvancedSafeParcelable> CREATOR = * new MyAdvancedCreator(); * * @Field(id=1, getter="getObjectAsBundle", type="android.os.Bundle") * private final MyCustomObject myObject; * * @Constructor * MyAdvancedSafeParcelable( * @Param(id=1) Bundle objectAsBundle) { * myObject = myConvertFromBundleToObject(objectAsBundle); * } * * Bundle getObjectAsBundle() { * // The code here can convert your custom object to one that can be parcelled. * return myConvertFromObjectToBundle(myObject); * } * * ... * } **/ String type() default NULL; /** * This can be used to specify the default value for primitive types (e.g., boolean, int, * long), primitive type object wrappers (e.g., Boolean, Integer, Long) and String in the * case a value for a field was not explicitly set in the marshalled Parcel. This performs * compile-time checks for the type of the field and inserts the appropriate quotes or * double quotes around strings and chars or removes them completely for booleans and * numbers. To insert a generic string for initializing field, use {@link * #defaultValueUnchecked()}. You can specify at most one of {@link #defaultValue()} or * {@link #defaultValueUnchecked()}. For example, * *
* @Field(id=2, defaultValue="true") * boolean myBoolean; * * @Field(id=3, defaultValue="13") * Integer myInteger; * * @Field(id=4, defaultValue="foo") * String myString; **/ String defaultValue() default NULL; /** * This can be used to specify the default value for any object and the string value is * literally added to the generated creator class code unchecked. You can specify at most * one of {@link #defaultValue()} or {@link #defaultValueUnchecked()}. You must fully * qualify any classes you reference within the string. For example, * *
* @Field(id=2, defaultValueUnchecked="new android.os.Bundle()") * Bundle myBundle; **/ String defaultValueUnchecked() default NULL; } /** * There may be exactly one member annotated with VersionField, which represents the version of * this safe parcelable. The attributes are the same as those of {@link Field}. Note you can use * any type you want for your version field, although most people use int's. */ @interface VersionField { int id(); String getter() default NULL; String type() default NULL; } /** * Use this to indicate the member field that holds whether a field was set or not. The member * field type currently supported is a HashSet<Integer> which is the set of safe * parcelable field id's that have been explicitly set. * *
This annotation should also be used to annotate one of the parameters to the constructor * annotated with @Constructor. Note that this annotation should either be present on * exactly one member field and one constructor parameter or left out completely. */ @interface Indicator { String getter() default NULL; } /** * Use this to indicate the constructor that the creator should use. The constructor annotated * with this must be package or public visibility, so that the generated "creator" class can * invoke this. */ @interface Constructor {} /** * Use this on each parameter passed in to the Constructor to indicate to which field id each * formal parameter corresponds. */ @interface Param { int id(); } /** * Use this on a parameter passed in to the Constructor to indicate that a removed field should * be read on construction. If the field is not present when read, the default value will be * used instead. */ @interface RemovedParam { int id(); String defaultValue() default NULL; String defaultValueUnchecked() default NULL; } /** * Use this to mark tombstones for removed {@link Field Fields} or {@link VersionField * VersionFields}. */ @interface Reserved { int[] value(); } }