1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os.image;
18 
19 import android.annotation.NonNull;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SystemService;
22 import android.content.Context;
23 import android.gsi.AvbPublicKey;
24 import android.gsi.GsiProgress;
25 import android.gsi.IGsiService;
26 import android.os.ParcelFileDescriptor;
27 import android.os.RemoteException;
28 import android.util.Pair;
29 
30 /**
31  * The DynamicSystemManager offers a mechanism to use a new system image temporarily. After the
32  * installation, the device can reboot into this image with a new created /data. This image will
33  * last until the next reboot and then the device will go back to the original image. However the
34  * installed image and the new created /data are not deleted but disabled. Thus the application can
35  * either re-enable the installed image by calling {@link #toggle} or use the {@link #remove} to
36  * delete it completely. In other words, there are three device states: no installation, installed
37  * and running. The procedure to install a DynamicSystem starts with a {@link #startInstallation},
38  * followed by a series of {@link #write} and ends with a {@link commit}. Once the installation is
39  * complete, the device state changes from no installation to the installed state and a followed
40  * reboot will change its state to running. Note one instance of DynamicSystem can exist on a given
41  * device thus the {@link #startInstallation} will fail if the device is currently running a
42  * DynamicSystem.
43  *
44  * @hide
45  */
46 @SystemService(Context.DYNAMIC_SYSTEM_SERVICE)
47 public class DynamicSystemManager {
48     private static final String TAG = "DynamicSystemManager";
49 
50     private final IDynamicSystemService mService;
51 
52     /** {@hide} */
DynamicSystemManager(IDynamicSystemService service)53     public DynamicSystemManager(IDynamicSystemService service) {
54         mService = service;
55     }
56 
57     /** The DynamicSystemManager.Session represents a started session for the installation. */
58     public class Session {
Session()59         private Session() {}
60 
61         /**
62          * Set the file descriptor that points to a ashmem which will be used
63          * to fetch data during the submitFromAshmem.
64          *
65          * @param ashmem fd that points to a ashmem
66          * @param size size of the ashmem file
67          */
68         @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
setAshmem(ParcelFileDescriptor ashmem, long size)69         public boolean setAshmem(ParcelFileDescriptor ashmem, long size) {
70             try {
71                 return mService.setAshmem(ashmem, size);
72             } catch (RemoteException e) {
73                 throw new RuntimeException(e.toString());
74             }
75         }
76 
77         /**
78          * Submit bytes to the DSU partition from the ashmem previously set with
79          * setAshmem.
80          *
81          * @param size Number of bytes
82          * @return true on success, false otherwise.
83          */
84         @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
submitFromAshmem(int size)85         public boolean submitFromAshmem(int size) {
86             try {
87                 return mService.submitFromAshmem(size);
88             } catch (RemoteException e) {
89                 throw new RuntimeException(e.toString());
90             }
91         }
92 
93         /**
94          * Retrieve AVB public key from installing partition.
95          *
96          * @param dst           Output the AVB public key.
97          * @return              true on success, false if partition doesn't have a
98          *                      valid VBMeta block to retrieve the AVB key from.
99          */
100         @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
getAvbPublicKey(AvbPublicKey dst)101         public boolean getAvbPublicKey(AvbPublicKey dst) {
102             try {
103                 return mService.getAvbPublicKey(dst);
104             } catch (RemoteException e) {
105                 throw new RuntimeException(e.toString());
106             }
107         }
108 
109         /**
110          * Finish write and make device to boot into the it after reboot.
111          *
112          * @return {@code true} if the call succeeds. {@code false} if there is any native runtime
113          *     error.
114          */
115         @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
commit()116         public boolean commit() {
117             try {
118                 return mService.setEnable(true, true);
119             } catch (RemoteException e) {
120                 throw new RuntimeException(e.toString());
121             }
122         }
123     }
124     /**
125      * Start DynamicSystem installation.
126      *
127      * @return true if the call succeeds
128      */
129     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
startInstallation(String dsuSlot)130     public boolean startInstallation(String dsuSlot) {
131         try {
132             return mService.startInstallation(dsuSlot);
133         } catch (RemoteException e) {
134             throw new RuntimeException(e.toString());
135         }
136     }
137     /**
138      * Start DynamicSystem installation. This call may take an unbounded amount of time. The caller
139      * may use another thread to call the getStartProgress() to get the progress.
140      *
141      * @param name The DSU partition name
142      * @param size Size of the DSU image in bytes
143      * @param readOnly True if the partition is read only, e.g. system.
144      * @return {@code Integer} an IGsiService.INSTALL_* status code. {@link Session} an installation
145      *     session object if successful, otherwise {@code null}.
146      */
147     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
createPartition( String name, long size, boolean readOnly)148     public @NonNull Pair<Integer, Session> createPartition(
149             String name, long size, boolean readOnly) {
150         try {
151             int status = mService.createPartition(name, size, readOnly);
152             if (status == IGsiService.INSTALL_OK) {
153                 return new Pair<>(status, new Session());
154             } else {
155                 return new Pair<>(status, null);
156             }
157         } catch (RemoteException e) {
158             throw new RuntimeException(e.toString());
159         }
160     }
161     /**
162      * Complete the current partition installation.
163      *
164      * @return true if the partition installation completes without error.
165      */
166     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
closePartition()167     public boolean closePartition() {
168         try {
169             return mService.closePartition();
170         } catch (RemoteException e) {
171             throw new RuntimeException(e.toString());
172         }
173     }
174     /**
175      * Finish a previously started installation. Installations without a corresponding
176      * finishInstallation() will be cleaned up during device boot.
177      */
178     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
finishInstallation()179     public boolean finishInstallation() {
180         try {
181             return mService.finishInstallation();
182         } catch (RemoteException e) {
183             throw new RuntimeException(e.toString());
184         }
185     }
186     /**
187      * Query the progress of the current installation operation. This can be called while the
188      * installation is in progress.
189      *
190      * @return GsiProgress GsiProgress { int status; long bytes_processed; long total_bytes; } The
191      *     status field can be IGsiService.STATUS_NO_OPERATION, IGsiService.STATUS_WORKING or
192      *     IGsiService.STATUS_COMPLETE.
193      */
194     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
getInstallationProgress()195     public GsiProgress getInstallationProgress() {
196         try {
197             return mService.getInstallationProgress();
198         } catch (RemoteException e) {
199             throw new RuntimeException(e.toString());
200         }
201     }
202 
203     /**
204      * Abort the installation process. Note this method must be called in a thread other than the
205      * one calling the startInstallation method as the startInstallation method will not return
206      * until it is finished.
207      *
208      * @return {@code true} if the call succeeds. {@code false} if there is no installation
209      *     currently.
210      */
211     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
abort()212     public boolean abort() {
213         try {
214             return mService.abort();
215         } catch (RemoteException e) {
216             throw new RuntimeException(e.toString());
217         }
218     }
219 
220     /** @return {@code true} if the device is running a dynamic system */
221     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
isInUse()222     public boolean isInUse() {
223         try {
224             return mService.isInUse();
225         } catch (RemoteException e) {
226             throw new RuntimeException(e.toString());
227         }
228     }
229 
230     /** @return {@code true} if the device has a dynamic system installed */
231     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
isInstalled()232     public boolean isInstalled() {
233         try {
234             return mService.isInstalled();
235         } catch (RemoteException e) {
236             throw new RuntimeException(e.toString());
237         }
238     }
239 
240     /** @return {@code true} if the device has a dynamic system enabled */
241     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
isEnabled()242     public boolean isEnabled() {
243         try {
244             return mService.isEnabled();
245         } catch (RemoteException e) {
246             throw new RuntimeException(e.toString());
247         }
248     }
249 
250     /**
251      * Remove DynamicSystem installation if present
252      *
253      * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
254      */
255     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
remove()256     public boolean remove() {
257         try {
258             return mService.remove();
259         } catch (RemoteException e) {
260             throw new RuntimeException(e.toString());
261         }
262     }
263 
264     /**
265      * Enable or disable DynamicSystem.
266      * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
267      */
268     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
setEnable(boolean enable, boolean oneShot)269     public boolean setEnable(boolean enable, boolean oneShot) {
270         try {
271             return mService.setEnable(enable, oneShot);
272         } catch (RemoteException e) {
273             throw new RuntimeException(e.toString());
274         }
275     }
276 
277     /**
278      * Returns the suggested scratch partition size for overlayFS.
279      */
280     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
suggestScratchSize()281     public long suggestScratchSize() {
282         try {
283             return mService.suggestScratchSize();
284         } catch (RemoteException e) {
285             throw new RuntimeException(e.toString());
286         }
287     }
288 
289     /**
290      * Returns the active DSU slot
291      */
292     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
getActiveDsuSlot()293     public String getActiveDsuSlot() {
294         try {
295             return mService.getActiveDsuSlot();
296         } catch (RemoteException e) {
297             throw new RuntimeException(e.toString());
298         }
299     }
300 }
301