├── .gitignore ├── LICENSE ├── README.md ├── simple-aidl-objects.jar └── src └── mn └── hart └── android └── simpleaidl ├── AIDLBundleable.java ├── AIDLBundler.aidl ├── AIDLBundler.java ├── AIDLObject.aidl └── AIDLObject.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2012, Kevin Hartman 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of Simple AIDL Objects nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL KEVIN HARTMAN BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple AIDL Objects 2 | =================== 3 | 4 | Simple AIDL Objects offers a versatile solution for AIDL-defined methods that need to accept interfaces or superclasses, allowing design patterns to be used more easily across Android services and their clients. 5 | 6 | by Kevin Hartman 7 | 8 | Disclaimers 9 | =========== 10 | * This was just a fun tangent project that I did to work out how I would have wanted Android's AIDL API to look. I don't maintain it, and I don't recommend using it for anything other than science. 11 | 12 | * This is an interesting approach to enabling AIDL-defined methods to accept interfaces and superclasses as parameters. This is possible without Simple AIDL Objects, but it's a messy solution. For production, you shouldn't use this library. Check out this blog post which describes how to go about doing that. 13 | 14 | Preface 15 | ======= 16 | Android provides an interface definition language that developers can use directly in order to create and communicate with a Service, across processes, in a complex way that cannot be suited by using a simple Messenger. 17 | 18 | Problem 19 | ======= 20 | Methods that can be defined in an AIDL service (ex. a method within IPotatoSaladService.aidl) can, by default, only accept some very basic types as parameters. The full list can be found on the Android developer website. If the developer wishes to have a method accept custom types (ex. Potato), they must: 21 | 22 | * Define a new AIDL interface for said type (ex. Potato.aidl) 23 | * Implement Parcelable in the type's class (implementing all of its required methods, of course) 24 | * Define a static CREATOR field with a Creator object that requires a concrete implementation, which must be set up as well... 25 | 26 | That's a lot of complication. 27 | 28 | Solution 29 | ======== 30 | ##How it Simplifies: 31 | 32 | * No longer do you need to define a .aidl file for the type. 33 | * Just implement AIDLBundleable or extend AIDLObject and define two methods in your new type's class. 34 | 35 | ##How it Handles Inheritance: 36 | 37 | If your type implements AIDLBundleable, all you need to do is instantiate a new AIDLBundler with your type object as a parameter and pass the AIDLBundler to your AIDL service's methods. 38 | 39 | If your type extends AIDLObject, you can pass your type or its inheritors directly to your AIDL service's methods, and this library will take care of the rest for you, allowing you to use inheritance naturally. 40 | 41 | Installation 42 | ============ 43 | Grab simple-aidl-objects.jar from root, and add it to your Android project's build path. 44 | 45 | Usage 46 | ===== 47 | The examples provided for both solutions are strategy design pattern implementations. 48 | 49 | ##Interface Solution 50 | This is an example of how you can use the AIDLBundler solution included within this library to create a new type for use with a Strategy design pattern. 51 | 52 | IMyStrategyService.aidl: 53 | `````java 54 | package mn.hart.example; 55 | import mn.hart.android.simpleaidl.AIDLBundler; 56 | 57 | interface IMyStrategyService { 58 | void setStrategy(in AIDLBundler strategy); 59 | } 60 | ````` 61 | This is the AIDL file associated with your AIDL-using service. All you need to note here is that the method used to set the strategy to use in our service takes an AIDLBundler object. 62 | 63 | MyStrategy.java: 64 | `````java 65 | package mn.hart.example; 66 | import mn.hart.android.simpleaidl.AIDLBundleable; 67 | 68 | public abstract class MyStrategy implements AIDLBundleable { 69 | // Nothing here, because I'm too lazy to make our strategies 70 | // do something. 71 | } 72 | ````` 73 | This is the strategy abstract class that we can use to define some standard functionality for all of our concrete strategies. 74 | 75 | MyConcreteStrategy.java: 76 | `````java 77 | package mn.hart.example; 78 | import android.os.Bundle; 79 | import android.util.Log; 80 | 81 | public class MyConcreteStrategy extends MyStrategy { 82 | private final String INTERVAL_KEY = "interval"; 83 | private long interval; 84 | 85 | /** 86 | * Notice how the constructor accepts a long. That's 87 | * not required by MyStrategy. We could've constructed this 88 | * object with whatever we'd wanted. Point is, subclasses 89 | * of our type can be constructed with variable constructors 90 | * that differ from one another. Without using serialization, 91 | * implementing that was an interesting problem. 92 | */ 93 | public MyConcreteStrategy(long intervalMillis) { 94 | this.interval = intervalMillis; 95 | } 96 | 97 | /** 98 | * Sets this object up on the other side. Should do 99 | * an initialization equivalent to that of the 100 | * constructor. 101 | */ 102 | @Override 103 | public void contructFromInstanceData(Bundle instanceData) { 104 | this.interval = instanceData.getLong(INTERVAL_KEY); 105 | } 106 | 107 | /** 108 | * Stuff this object's information into a Bundle. 109 | * We will get this bundle back on the other side 110 | * and will use it to remake this object. 111 | */ 112 | @Override 113 | public void writeInstanceData(Bundle instanceData) { 114 | instanceData.putLong(INTERVAL_KEY, interval); 115 | } 116 | } 117 | ````` 118 | This is a concrete strategy that happens to take a parameter of type long in its constructor. As noted in the comments within the actual code, constructors take whatever you'd like as parameters. Just like they would if you weren't implementing your strategy pattern over AIDL. 119 | 120 | MyStrategyActivity.java: 121 | `````java 122 | package mn.hart.example; 123 | import mn.hart.android.simpleaidl.AIDLBundler; 124 | 125 | ... 126 | 127 | public void onServiceConnected(ComponentName className, IBinder service) { 128 | IMyStrategyService mIMyStrategyService = IMyStrategyService.Stub.asInterface(service); 129 | 130 | try { 131 | mIMyStrategyService.setStrategy(new AIDLBundler(new MyConcreteStrategy(100L))); 132 | } catch (RemoteException e) { 133 | // Fail 134 | } 135 | } 136 | 137 | ... 138 | 139 | ````` 140 | Notice how the strategy is set using the AIDLBundler above. A particular concrete strategy is passed, but we could've passed any concrete strategy that we'd wanted. 141 | 142 | MyStrategyService.java: 143 | `````java 144 | /** 145 | * A client activity that can use the service remotely. 146 | */ 147 | 148 | package mn.hart.example; 149 | import mn.hart.android.simpleaidl.AIDLBundler; 150 | 151 | ... 152 | 153 | public class MyStrategyService extends Service { 154 | MyStrategy strategy = null; 155 | 156 | @Override 157 | public IBinder onBind(Intent intent) { 158 | return mBinder; 159 | } 160 | 161 | private synchronized void setStrategySafe(MyStrategy myStrategy) { 162 | strategy = myStrategy; 163 | } 164 | 165 | private final IMyStrategyService.Stub mBinder = new IMyStrategyService.Stub() { 166 | public void setStrategy(AIDLBundler aidlBundler) throws RemoteException { 167 | setStrategySafe((MyStrategy) aidlBundler.getBundleable()); 168 | } 169 | }; 170 | } 171 | ````` 172 | Notice how the instance is retrieved from the AIDLBundler. An explicit cast to our interface type is required. 173 | 174 | 175 | ##Abstract Class Solution 176 | This is an example of how you can use the AIDLObject solution included within this library to create a new type for use with a Strategy design pattern. With this option, you're able to use inheritance directly, without the AIDLBundler wrapper. The caveat, however, is that you must extend AIDLObject, rather than just implement AIDLBundleable. 177 | 178 | 179 | IMyStrategyService.aidl: 180 | `````java 181 | package mn.hart.example; 182 | import mn.hart.android.simpleaidl.AIDLObject; 183 | 184 | interface IMyStrategyService { 185 | void setStrategy(in AIDLObject strategy); 186 | } 187 | ````` 188 | This is the AIDL file associated with your AIDL-using service. All you need to note here is that the method used to set the strategy to use in our service takes an AIDLObject object. 189 | 190 | MyStrategy.java: 191 | `````java 192 | package mn.hart.example; 193 | import mn.hart.android.simpleaidl.AIDLObject; 194 | 195 | public abstract class MyStrategy extends AIDLObject { 196 | // Nothing here, because I'm too lazy to make our strategies 197 | // do something. 198 | } 199 | ````` 200 | This is the strategy abstract class that we can use to define some standard functionality for all of our concrete strategies. 201 | 202 | MyConcreteStrategy.java: 203 | `````java 204 | package mn.hart.example; 205 | import android.os.Bundle; 206 | import android.util.Log; 207 | 208 | public class MyConcreteStrategy extends MyStrategy { 209 | private final String INTERVAL_KEY = "interval"; 210 | private long interval; 211 | 212 | /** 213 | * Notice how the constructor accepts a long. That's 214 | * not required by MyStrategy. We could've constructed this 215 | * object with whatever we'd wanted. Point is, subclasses 216 | * of our type can be constructed with variable constructors 217 | * that differ from one another. Without using serialization, 218 | * implementing that was an interesting problem. 219 | */ 220 | public MyConcreteStrategy(long intervalMillis) { 221 | this.interval = intervalMillis; 222 | } 223 | 224 | /** 225 | * Sets this object up on the other side. Should do 226 | * an initialization equivalent to that of the 227 | * constructor. 228 | */ 229 | @Override 230 | public void contructFromInstanceData(Bundle instanceData) { 231 | this.interval = instanceData.getLong(INTERVAL_KEY); 232 | } 233 | 234 | /** 235 | * Stuff this object's information into a Bundle. 236 | * We will get this bundle back on the other side 237 | * and will use it to remake this object. 238 | */ 239 | @Override 240 | public void writeInstanceData(Bundle instanceData) { 241 | instanceData.putLong(INTERVAL_KEY, interval); 242 | } 243 | } 244 | ````` 245 | 246 | MyStrategyActivity.java: 247 | `````java 248 | package mn.hart.example; 249 | 250 | ... 251 | 252 | public void onServiceConnected(ComponentName className, IBinder service) { 253 | IMyStrategyService mIMyStrategyService = IMyStrategyService.Stub.asInterface(service); 254 | 255 | try { 256 | mIMyStrategyService.setStrategy(new MyConcreteStrategy(100L)); 257 | } catch (RemoteException e) { 258 | // Fail 259 | } 260 | } 261 | 262 | ... 263 | 264 | ````` 265 | 266 | MyStrategyService.java: 267 | `````java 268 | /** 269 | * A client activity that can use the service remotely. 270 | */ 271 | 272 | package mn.hart.example; 273 | import mn.hart.android.simpleaidl.AIDLObject; 274 | 275 | ... 276 | 277 | public class MyStrategyService extends Service { 278 | MyStrategy strategy = null; 279 | 280 | @Override 281 | public IBinder onBind(Intent intent) { 282 | return mBinder; 283 | } 284 | 285 | private synchronized void setStrategySafe(MyStrategy myStrategy) { 286 | this.strategy = myStrategy; 287 | } 288 | 289 | private final IMyStrategyService.Stub mBinder = new IMyStrategyService.Stub() { 290 | public void setStrategy(AIDLObject aidlObject) throws RemoteException { 291 | setStrategySafe((MyStrategy) aidlObject); 292 | } 293 | }; 294 | } 295 | ````` 296 | An explicit cast to our interface type is required from AIDLObject. 297 | 298 | Limitations 299 | =========== 300 | 301 | ##Explicit Casting 302 | Using the AIDLBundler and Bundleable interface solution, an explicit cast to your desired type on the Service side is required: 303 | 304 | 305 | `````java 306 | ... 307 | YourType type = (YourType) aidlBundler.getBundleable(); // Where YourType implements AIDLBundleable 308 | ````` 309 | 310 | This is due to an implementation detail of AIDL itself. Specifically, generics were not accounted for in the design of AIDL. 311 | 312 | The explicit casting means that it's possible to pass particular AIDLBundler and AIDLObject objects to service methods that should not be allowed to accept them- which will result in a runtime error. With very mild caution, this should be easily avoidable. 313 | 314 | ##Not so Standard Reflection 315 | Because this library uses some less than standard reflection in order to make your life so simple, there's a possiblity that it may not work with particular versions of Android. It is currently tested only on Android 4.0.3 and 4.1.1. 316 | 317 | ##Current Development State 318 | Simple AIDL Objects is in a useable state, but it was just a fun tangent project. 319 | -------------------------------------------------------------------------------- /simple-aidl-objects.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevinhartman/simple-aidl-objects/6e4c9539c565789c4df5864e97ae0ff0d6eeaf64/simple-aidl-objects.jar -------------------------------------------------------------------------------- /src/mn/hart/android/simpleaidl/AIDLBundleable.java: -------------------------------------------------------------------------------- 1 | package mn.hart.android.simpleaidl; 2 | 3 | import android.os.Bundle; 4 | 5 | /** 6 | * Provides constructs that allow an object to pack and 7 | * recreate itself before and after IPC, respectively. 8 | * into a Bundle and r 9 | * @author Kevin Hartman 10 | * @version 1.0 11 | */ 12 | public interface AIDLBundleable { 13 | 14 | /** 15 | * Set up instance using provided data. 16 | * @param instanceData Bundle created by this object before IPC. 17 | */ 18 | public void contructFromInstanceData(Bundle instanceData); 19 | 20 | /** 21 | * Write instance data prior to shuttling. 22 | * @param instanceData Bundle in which to write instance data. 23 | */ 24 | public void writeInstanceData(Bundle instanceData); 25 | } 26 | -------------------------------------------------------------------------------- /src/mn/hart/android/simpleaidl/AIDLBundler.aidl: -------------------------------------------------------------------------------- 1 | package mn.hart.android.simpleaidl; 2 | 3 | parcelable AIDLBundler; -------------------------------------------------------------------------------- /src/mn/hart/android/simpleaidl/AIDLBundler.java: -------------------------------------------------------------------------------- 1 | package mn.hart.android.simpleaidl; 2 | 3 | import java.lang.reflect.Constructor; 4 | 5 | import android.os.Bundle; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.util.Log; 9 | 10 | /** 11 | * Handles the packing of an AIDLBundleable implementer 12 | * prior to its shuttling across processes, and is responsible 13 | * for orchestrating that implementer's reconstruction on 14 | * the other side. 15 | * @author Kevin Hartman 16 | * @version 1.0 17 | */ 18 | public class AIDLBundler implements Parcelable { 19 | private AIDLBundleable bundleable; 20 | 21 | /** 22 | * Constructs an AIDLBundler to handle the bundling 23 | * and unpacking of an AIDLBundleable implementer 24 | * before and after its interprocess transmission, 25 | * respectively. 26 | * 27 | * NOTE: This constructor is also called when the 28 | * AIDLBundler object itself is recreated on the other 29 | * side. 30 | * @param bundleable The AIDLBundleable to be shuttled. 31 | */ 32 | public AIDLBundler(AIDLBundleable bundleable) { 33 | this.bundleable = bundleable; 34 | } 35 | 36 | /** 37 | * Get the AIDLBundleable that is housed by this 38 | * AIDLBundler. 39 | * 40 | * NOTE: A call to this method from the local side 41 | * will not return the same object as a call to this 42 | * method from the remote side. However, these objects 43 | * should be equivalent if bundling and unpacking is 44 | * done appropriately in the AIDLBundleable implementer 45 | * class. 46 | * @return The AIDLBundleable housed by this AIDLBundler 47 | */ 48 | public AIDLBundleable getBundleable() { 49 | return bundleable; 50 | } 51 | 52 | /** 53 | * Specify special flags for marshaling process 54 | */ 55 | public int describeContents() { 56 | return 0; 57 | } 58 | 59 | /** 60 | * Write the AIDLBundleable's data bundle and save 61 | * that, and the AIDLBundleable's name, in the parcel 62 | * that will be used in reconstruction. 63 | * @param out The parcel in which to save the AIDLBundleable's data 64 | */ 65 | public void writeToParcel(Parcel out, int flags) { 66 | Bundle instanceData = new Bundle(); 67 | bundleable.writeInstanceData(instanceData); 68 | 69 | out.writeString(bundleable.getClass().getName()); 70 | out.writeBundle(instanceData); 71 | 72 | } 73 | 74 | /** 75 | * Creator object that the AIDL generated service class 76 | * will be looking for when it's time to recreate this 77 | * AIDLBundler on the other side. 78 | */ 79 | public static final Creator CREATOR 80 | = new Parcelable.Creator() { 81 | 82 | /** 83 | * Instantiate the desired AIDLBundleable by name and provide 84 | * @param in The AIDLBundleable's data. 85 | * @return An AIDLBundler, holding the desired AIDLBundleable or null if error. 86 | */ 87 | public AIDLBundler createFromParcel(Parcel in) { 88 | String className = in.readString(); 89 | Bundle instanceData = in.readBundle(); 90 | 91 | try { 92 | Constructor implementerConstructor = AndroidMagicConstructorMaker.make(Class.forName(className)); 93 | implementerConstructor.setAccessible(true); 94 | 95 | AIDLBundleable implementer = (AIDLBundleable) implementerConstructor.newInstance(); 96 | implementer.contructFromInstanceData(instanceData); 97 | 98 | AIDLBundler subclasser = new AIDLBundler(implementer); 99 | 100 | return subclasser; 101 | 102 | } catch (Exception e) { 103 | Log.e("AIDLObject.CREATOR.createFromParcel", e.getCause().getMessage()); 104 | } 105 | 106 | return null; 107 | } 108 | 109 | /** 110 | * Required by Parcelable 111 | */ 112 | public AIDLBundler[] newArray(int size) { 113 | return new AIDLBundler[size]; 114 | } 115 | }; 116 | 117 | /** 118 | * Create a new no-args constructor for any class by its name. 119 | * any of its constructors. 120 | * @author Kevin Hartman 121 | * @version 1.0 122 | * 123 | */ 124 | private static class AndroidMagicConstructorMaker { 125 | /** 126 | * 127 | * @param clazz The class for which to create a constructor. 128 | * @return A no-args constructor. 129 | * @throws Exception 130 | */ 131 | @SuppressWarnings("unchecked") 132 | public static Constructor make(Class clazz) throws Exception { 133 | Constructor constr = Constructor.class.getDeclaredConstructor( 134 | Class.class, // Class declaringClass 135 | Class[].class, // Class[] parameterTypes 136 | Class[].class, // Class[] checkedExceptions 137 | int.class); // int slot 138 | constr.setAccessible(true); 139 | 140 | return (Constructor) constr.newInstance(clazz, new Class[0], 141 | new Class[0], 1); 142 | } 143 | } 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/mn/hart/android/simpleaidl/AIDLObject.aidl: -------------------------------------------------------------------------------- 1 | package mn.hart.android.simpleaidl; 2 | 3 | parcelable AIDLObject; -------------------------------------------------------------------------------- /src/mn/hart/android/simpleaidl/AIDLObject.java: -------------------------------------------------------------------------------- 1 | package mn.hart.android.simpleaidl; 2 | 3 | import java.lang.reflect.Constructor; 4 | import android.os.Bundle; 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | import android.util.Log; 8 | 9 | /** 10 | * Packs itself prior to its shuttling across processes, 11 | * and is responsible for orchestrating its own reconstruction 12 | * on the other side. 13 | * @author Kevin Hartman 14 | * @version 1.0 15 | */ 16 | public abstract class AIDLObject implements Parcelable { 17 | 18 | /** 19 | * Specify special flags for marshaling process 20 | */ 21 | public int describeContents() { 22 | return 0; 23 | } 24 | 25 | /** 26 | * Write this object's data bundle and save 27 | * that, and its name, in the parcel 28 | * that will be used in reconstruction. 29 | * @param out The parcel in which to save its data. 30 | */ 31 | public void writeToParcel(Parcel out, int flags) { 32 | Bundle instanceData = new Bundle(); 33 | writeInstanceData(instanceData); 34 | 35 | out.writeString(this.getClass().getName()); 36 | out.writeBundle(instanceData); 37 | 38 | } 39 | 40 | /** 41 | * Creator object that the AIDL generated service class 42 | * will be looking for when it's time to recreate this 43 | * AIDLObject on the other side. 44 | */ 45 | public static final Creator CREATOR 46 | = new Parcelable.Creator() { 47 | 48 | /** 49 | * Instantiate the desired AIDLObject subclass by name and provide 50 | * it with its data bundle. 51 | * @param in The AIDLObject's data. 52 | * @return An AIDLObject, or null if error. 53 | */ 54 | public AIDLObject createFromParcel(Parcel in) { 55 | String className = in.readString(); 56 | Bundle instanceData = in.readBundle(); 57 | 58 | try { 59 | Constructor implementerConstructor = AndroidMagicConstructorMaker.make(Class.forName(className)); 60 | implementerConstructor.setAccessible(true); 61 | AIDLObject implementer = (AIDLObject) implementerConstructor.newInstance(); 62 | 63 | implementer.contructFromInstanceData(instanceData); 64 | return implementer; 65 | 66 | } catch (Exception e) { 67 | Log.e("AIDLObject.CREATOR.createFromParcel", e.getCause().getMessage()); 68 | } 69 | 70 | return null; 71 | } 72 | 73 | /** 74 | * Required by Parcelable 75 | */ 76 | public AIDLObject[] newArray(int size) { 77 | return new AIDLObject[size]; 78 | } 79 | }; 80 | 81 | /** 82 | * Create a new no-args constructor for any class by its name. 83 | * @author Kevin Hartman 84 | * @version 1.0 85 | * 86 | */ 87 | private static class AndroidMagicConstructorMaker { 88 | 89 | @SuppressWarnings("unchecked") 90 | public static Constructor make(Class clazz) throws Exception { 91 | Constructor constr = Constructor.class.getDeclaredConstructor( 92 | Class.class, // Class declaringClass 93 | Class[].class, // Class[] parameterTypes 94 | Class[].class, // Class[] checkedExceptions 95 | int.class); // int slot 96 | constr.setAccessible(true); 97 | 98 | return (Constructor) constr.newInstance(clazz, new Class[0], 99 | new Class[0], 1); 100 | } 101 | } 102 | 103 | /** 104 | * Set up instance using provided data. 105 | * @param instanceData Bundle created by this object before IPC. 106 | */ 107 | protected abstract void contructFromInstanceData(Bundle instanceData); 108 | 109 | /** 110 | * Write instance data prior to shuttling. 111 | * @param instanceData Bundle in which to write instance data. 112 | */ 113 | protected abstract void writeInstanceData(Bundle instanceData); 114 | 115 | 116 | 117 | 118 | } 119 | --------------------------------------------------------------------------------