Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 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.incremental; |
| 18 | |
| 19 | /** |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 20 | * Set up files and directories used in an installation session. Currently only used by Incremental |
| 21 | * Installation. For Incremental installation, the expected outcome of this function is: 0) All the |
| 22 | * files are in defaultStorage 1) All APK files are in the same directory, bound to mApkStorage, and |
| 23 | * bound to the InstallerSession's stage dir. The files are linked from mApkStorage to |
| 24 | * defaultStorage. 2) All lib files are in the sub directories as their names suggest, and in the |
| 25 | * same parent directory as the APK files. The files are linked from mApkStorage to defaultStorage. |
| 26 | * 3) OBB files are in another directory that is different from APK files and lib files, bound to |
| 27 | * mObbStorage. The files are linked from mObbStorage to defaultStorage. |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 28 | * |
| 29 | * @throws IllegalStateException the session is not an Incremental installation session. |
| 30 | */ |
| 31 | |
Alex Buynytskyy | cd4d387 | 2020-02-08 17:50:50 -0800 | [diff] [blame] | 32 | import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; |
| 33 | |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 34 | import android.annotation.NonNull; |
| 35 | import android.annotation.Nullable; |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 36 | import android.content.Context; |
Alex Buynytskyy | ea14d19 | 2019-12-13 15:42:18 -0800 | [diff] [blame] | 37 | import android.content.pm.DataLoaderParams; |
Alex Buynytskyy | 04f7391 | 2020-02-10 08:34:18 -0800 | [diff] [blame] | 38 | import android.content.pm.IDataLoaderStatusListener; |
Songchun Fan | 6381d61 | 2020-02-26 17:59:41 -0800 | [diff] [blame] | 39 | import android.content.pm.InstallationFileParcel; |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 40 | import android.text.TextUtils; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 41 | |
| 42 | import java.io.File; |
| 43 | import java.io.IOException; |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 44 | import java.util.List; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 45 | |
| 46 | /** |
| 47 | * This class manages storage instances used during a package installation session. |
| 48 | * @hide |
| 49 | */ |
| 50 | public final class IncrementalFileStorages { |
| 51 | private static final String TAG = "IncrementalFileStorages"; |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 52 | |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 53 | private @NonNull final IncrementalManager mIncrementalManager; |
| 54 | private @NonNull final File mStageDir; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 55 | private @Nullable IncrementalStorage mDefaultStorage; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 56 | |
| 57 | /** |
Songchun Fan | 54c6aed | 2020-01-31 16:52:41 -0800 | [diff] [blame] | 58 | * Set up files and directories used in an installation session. Only used by Incremental. |
| 59 | * All the files will be created in defaultStorage. |
| 60 | * TODO(b/133435829): code clean up |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 61 | * |
| 62 | * @throws IllegalStateException the session is not an Incremental installation session. |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 63 | * @throws IOException if fails to setup files or directories. |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 64 | */ |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 65 | public static IncrementalFileStorages initialize(Context context, |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 66 | @NonNull File stageDir, |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 67 | @NonNull DataLoaderParams dataLoaderParams, |
Alex Buynytskyy | 04f7391 | 2020-02-10 08:34:18 -0800 | [diff] [blame] | 68 | @Nullable IDataLoaderStatusListener dataLoaderStatusListener, |
Songchun Fan | 6381d61 | 2020-02-26 17:59:41 -0800 | [diff] [blame] | 69 | List<InstallationFileParcel> addedFiles) throws IOException { |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 70 | // TODO(b/136132412): sanity check if session should not be incremental |
| 71 | IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService( |
| 72 | Context.INCREMENTAL_SERVICE); |
| 73 | if (incrementalManager == null) { |
| 74 | // TODO(b/146080380): add incremental-specific error code |
| 75 | throw new IOException("Failed to obtain incrementalManager."); |
| 76 | } |
| 77 | |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 78 | final IncrementalFileStorages result = |
| 79 | new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams, |
| 80 | dataLoaderStatusListener); |
| 81 | for (InstallationFileParcel file : addedFiles) { |
| 82 | if (file.location == LOCATION_DATA_APP) { |
| 83 | try { |
| 84 | result.addApkFile(file); |
| 85 | } catch (IOException e) { |
| 86 | // TODO(b/146080380): add incremental-specific error code |
| 87 | throw new IOException( |
| 88 | "Failed to add file to IncFS: " + file.name + ", reason: ", e); |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 89 | } |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 90 | } else { |
| 91 | throw new IOException("Unknown file location: " + file.location); |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 92 | } |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 93 | } |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 94 | |
| 95 | if (!result.mDefaultStorage.startLoading()) { |
| 96 | // TODO(b/146080380): add incremental-specific error code |
| 97 | throw new IOException("Failed to start loading data for Incremental installation."); |
| 98 | } |
| 99 | |
| 100 | return result; |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 101 | } |
| 102 | |
| 103 | private IncrementalFileStorages(@NonNull File stageDir, |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 104 | @NonNull IncrementalManager incrementalManager, |
Alex Buynytskyy | 04f7391 | 2020-02-10 08:34:18 -0800 | [diff] [blame] | 105 | @NonNull DataLoaderParams dataLoaderParams, |
| 106 | @Nullable IDataLoaderStatusListener dataLoaderStatusListener) throws IOException { |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 107 | try { |
| 108 | mStageDir = stageDir; |
| 109 | mIncrementalManager = incrementalManager; |
| 110 | if (dataLoaderParams.getComponentName().getPackageName().equals("local")) { |
| 111 | final String incrementalPath = dataLoaderParams.getArguments(); |
| 112 | if (TextUtils.isEmpty(incrementalPath)) { |
| 113 | throw new IOException("Failed to create storage: incrementalPath is empty"); |
| 114 | } |
| 115 | mDefaultStorage = mIncrementalManager.openStorage(incrementalPath); |
| 116 | if (mDefaultStorage == null) { |
| 117 | throw new IOException( |
| 118 | "Couldn't open incremental storage at " + incrementalPath); |
| 119 | } |
| 120 | mDefaultStorage.bind(stageDir.getAbsolutePath()); |
| 121 | } else { |
| 122 | mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(), |
| 123 | dataLoaderParams, |
| 124 | dataLoaderStatusListener, |
| 125 | IncrementalManager.CREATE_MODE_CREATE |
| 126 | | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false); |
| 127 | if (mDefaultStorage == null) { |
| 128 | throw new IOException( |
| 129 | "Couldn't create incremental storage at " + stageDir); |
| 130 | } |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 131 | } |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 132 | } catch (IOException e) { |
| 133 | cleanUp(); |
| 134 | throw e; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 135 | } |
| 136 | } |
| 137 | |
Songchun Fan | 6381d61 | 2020-02-26 17:59:41 -0800 | [diff] [blame] | 138 | private void addApkFile(@NonNull InstallationFileParcel apk) throws IOException { |
| 139 | final String apkName = apk.name; |
Alex Buynytskyy | 04f7391 | 2020-02-10 08:34:18 -0800 | [diff] [blame] | 140 | final File targetFile = new File(mStageDir, apkName); |
Songchun Fan | 54c6aed | 2020-01-31 16:52:41 -0800 | [diff] [blame] | 141 | if (!targetFile.exists()) { |
Songchun Fan | 6381d61 | 2020-02-26 17:59:41 -0800 | [diff] [blame] | 142 | mDefaultStorage.makeFile(apkName, apk.size, null, apk.metadata, apk.signature); |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 143 | } |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Resets the states and unbinds storage instances for an installation session. |
| 148 | * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept |
| 149 | */ |
| 150 | public void cleanUp() { |
Yurii Zubrytskyi | 360bbdc | 2020-04-18 21:03:09 -0700 | [diff] [blame] | 151 | if (mDefaultStorage == null) { |
| 152 | return; |
| 153 | } |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 154 | |
| 155 | try { |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 156 | mDefaultStorage.unBind(mStageDir.getAbsolutePath()); |
| 157 | } catch (IOException ignored) { |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 158 | } |
Alex Buynytskyy | c5682f5 | 2020-01-30 13:17:05 -0800 | [diff] [blame] | 159 | mDefaultStorage = null; |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 160 | } |
Songchun Fan | 4e75869 | 2019-11-29 15:43:27 -0800 | [diff] [blame] | 161 | } |