/* * 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(); } }