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.service.dataloader; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SystemApi; 23 import android.app.Service; 24 import android.content.Intent; 25 import android.content.pm.DataLoaderParams; 26 import android.content.pm.DataLoaderParamsParcel; 27 import android.content.pm.FileSystemControlParcel; 28 import android.content.pm.IDataLoader; 29 import android.content.pm.IDataLoaderStatusListener; 30 import android.content.pm.InstallationFile; 31 import android.content.pm.InstallationFileParcel; 32 import android.os.IBinder; 33 import android.os.ParcelFileDescriptor; 34 import android.util.ExceptionUtils; 35 import android.util.Slog; 36 37 import libcore.io.IoUtils; 38 39 import java.io.IOException; 40 import java.util.Collection; 41 42 /** 43 * The base class for implementing a data loader service. 44 * <p> 45 * After calling commit() on the install session, the DataLoaderService is started and bound to 46 * provide the actual data bytes for the streaming session. 47 * The service will automatically be rebound until the streaming session has enough data to 48 * proceed with the installation. 49 * 50 * @see android.content.pm.DataLoaderParams 51 * @see android.content.pm.PackageInstaller.SessionParams#setDataLoaderParams 52 * 53 * @hide 54 */ 55 @SystemApi 56 public abstract class DataLoaderService extends Service { 57 private static final String TAG = "DataLoaderService"; 58 private final DataLoaderBinderService mBinder = new DataLoaderBinderService(); 59 60 /** 61 * DataLoader interface. Each instance corresponds to a single installation session. 62 * @hide 63 */ 64 @SystemApi 65 public interface DataLoader { 66 /** 67 * A virtual constructor. 68 * 69 * @param dataLoaderParams parameters set in the installation session 70 * {@link android.content.pm.PackageInstaller.SessionParams#setDataLoaderParams} 71 * @param connector Wrapper providing access to the installation image. 72 * @return true if initialization of a DataLoader was successful. False will notify the 73 * Installer {@link android.content.pm.PackageInstaller#STATUS_PENDING_STREAMING} and 74 * interrupt the session commit. The Installer is supposed to make sure DataLoader can 75 * proceed and then commit the session 76 * {@link android.content.pm.PackageInstaller.Session#commit}. 77 */ onCreate(@onNull DataLoaderParams dataLoaderParams, @NonNull FileSystemConnector connector)78 boolean onCreate(@NonNull DataLoaderParams dataLoaderParams, 79 @NonNull FileSystemConnector connector); 80 81 /** 82 * Prepare installation image. After this method succeeds installer will validate the files 83 * and continue installation. 84 * The method should block until the files are prepared for installation. 85 * This can take up to session lifetime (~day). If the session lifetime is exceeded then 86 * any attempts to write new data will fail. 87 * 88 * Example implementation: 89 * <code> 90 * String localPath = "/data/local/tmp/base.apk"; 91 * session.addFile(LOCATION_DATA_APP, "base", 123456, localPath.getBytes(UTF_8), null); 92 * ... 93 * // onPrepareImage 94 * for (InstallationFile file : addedFiles) { 95 * String localPath = new String(file.getMetadata(), UTF_8); 96 * File source = new File(localPath); 97 * ParcelFileDescriptor fd = ParcelFileDescriptor.open(source, MODE_READ_ONLY); 98 * try { 99 * mConnector.writeData(file.getName(), 0, fd.getStatSize(), fd); 100 * } finally { 101 * IoUtils.closeQuietly(fd); 102 * } 103 * } 104 * </code> 105 * It is recommended to stream data into installation session directly from source, e.g. 106 * cloud data storage, to save local disk space. 107 * 108 * @param addedFiles list of files created in this installation session 109 * {@link android.content.pm.PackageInstaller.Session#addFile} 110 * @param removedFiles list of files removed in this installation session 111 * {@link android.content.pm.PackageInstaller.Session#removeFile} 112 * @return false if unable to create and populate all addedFiles. Installation will fail. 113 */ onPrepareImage(@onNull Collection<InstallationFile> addedFiles, @NonNull Collection<String> removedFiles)114 boolean onPrepareImage(@NonNull Collection<InstallationFile> addedFiles, 115 @NonNull Collection<String> removedFiles); 116 } 117 118 /** 119 * DataLoader factory method. 120 * An installation session uses it to create an instance of DataLoader. 121 * @hide 122 */ 123 @SystemApi onCreateDataLoader(@onNull DataLoaderParams dataLoaderParams)124 public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { 125 return null; 126 } 127 128 /** 129 * @hide 130 */ onBind(@onNull Intent intent)131 public final @NonNull IBinder onBind(@NonNull Intent intent) { 132 return (IBinder) mBinder; 133 } 134 135 private class DataLoaderBinderService extends IDataLoader.Stub { 136 @Override create(int id, @NonNull DataLoaderParamsParcel params, @NonNull FileSystemControlParcel control, @NonNull IDataLoaderStatusListener listener)137 public void create(int id, @NonNull DataLoaderParamsParcel params, 138 @NonNull FileSystemControlParcel control, 139 @NonNull IDataLoaderStatusListener listener) 140 throws RuntimeException { 141 try { 142 nativeCreateDataLoader(id, control, params, listener); 143 } catch (Exception ex) { 144 Slog.e(TAG, "Failed to create native loader for " + id, ex); 145 destroy(id); 146 throw new RuntimeException(ex); 147 } finally { 148 if (control.incremental != null) { 149 IoUtils.closeQuietly(control.incremental.cmd); 150 IoUtils.closeQuietly(control.incremental.pendingReads); 151 IoUtils.closeQuietly(control.incremental.log); 152 IoUtils.closeQuietly(control.incremental.blocksWritten); 153 } 154 } 155 } 156 157 @Override start(int id)158 public void start(int id) { 159 if (!nativeStartDataLoader(id)) { 160 Slog.e(TAG, "Failed to start loader: " + id); 161 } 162 } 163 164 @Override stop(int id)165 public void stop(int id) { 166 if (!nativeStopDataLoader(id)) { 167 Slog.w(TAG, "Failed to stop loader: " + id); 168 } 169 } 170 171 @Override destroy(int id)172 public void destroy(int id) { 173 if (!nativeDestroyDataLoader(id)) { 174 Slog.w(TAG, "Failed to destroy loader: " + id); 175 } 176 } 177 178 @Override prepareImage(int id, InstallationFileParcel[] addedFiles, String[] removedFiles)179 public void prepareImage(int id, InstallationFileParcel[] addedFiles, 180 String[] removedFiles) { 181 if (!nativePrepareImage(id, addedFiles, removedFiles)) { 182 Slog.w(TAG, "Failed to prepare image for data loader: " + id); 183 } 184 } 185 } 186 187 /** 188 * Provides access to the installation image. 189 * 190 * @hide 191 */ 192 @SystemApi 193 public static final class FileSystemConnector { 194 /** 195 * Create a wrapper for a native instance. 196 * 197 * @hide 198 */ FileSystemConnector(long nativeInstance)199 FileSystemConnector(long nativeInstance) { 200 mNativeInstance = nativeInstance; 201 } 202 203 /** 204 * Write data to an installation file from an arbitrary FD. 205 * 206 * @param name name of file previously added to the installation session 207 * {@link InstallationFile#getName()}. 208 * @param offsetBytes offset into the file to begin writing at, or 0 to start at the 209 * beginning of the file. 210 * @param lengthBytes total size of the file being written, used to preallocate the 211 * underlying disk space, or -1 if unknown. The system may clear various 212 * caches as needed to allocate this space. 213 * @param incomingFd FD to read bytes from. 214 * @throws IOException if trouble opening the file for writing, such as lack of disk space 215 * or unavailable media. 216 */ 217 @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) writeData(@onNull String name, long offsetBytes, long lengthBytes, @NonNull ParcelFileDescriptor incomingFd)218 public void writeData(@NonNull String name, long offsetBytes, long lengthBytes, 219 @NonNull ParcelFileDescriptor incomingFd) throws IOException { 220 try { 221 nativeWriteData(mNativeInstance, name, offsetBytes, lengthBytes, incomingFd); 222 } catch (RuntimeException e) { 223 ExceptionUtils.maybeUnwrapIOException(e); 224 throw e; 225 } 226 } 227 228 private final long mNativeInstance; 229 } 230 231 /* Native methods */ nativeCreateDataLoader(int storageId, @NonNull FileSystemControlParcel control, @NonNull DataLoaderParamsParcel params, IDataLoaderStatusListener listener)232 private native boolean nativeCreateDataLoader(int storageId, 233 @NonNull FileSystemControlParcel control, 234 @NonNull DataLoaderParamsParcel params, 235 IDataLoaderStatusListener listener); 236 nativeStartDataLoader(int storageId)237 private native boolean nativeStartDataLoader(int storageId); 238 nativeStopDataLoader(int storageId)239 private native boolean nativeStopDataLoader(int storageId); 240 nativeDestroyDataLoader(int storageId)241 private native boolean nativeDestroyDataLoader(int storageId); 242 nativePrepareImage(int storageId, InstallationFileParcel[] addedFiles, String[] removedFiles)243 private native boolean nativePrepareImage(int storageId, 244 InstallationFileParcel[] addedFiles, String[] removedFiles); 245 nativeWriteData(long nativeInstance, String name, long offsetBytes, long lengthBytes, ParcelFileDescriptor incomingFd)246 private static native void nativeWriteData(long nativeInstance, String name, long offsetBytes, 247 long lengthBytes, ParcelFileDescriptor incomingFd); 248 249 } 250