diff options
author | Nicholas Sauer <nicksauer@google.com> | 2018-02-09 21:58:42 -0800 |
---|---|---|
committer | Nicholas Sauer <nicksauer@google.com> | 2018-02-12 20:42:19 -0800 |
commit | 2dc004693b725be42de5718f412ff3b9c24d2e9e (patch) | |
tree | 82eec9b94294da6941da8f7ed42ab747134d3f84 | |
parent | f44987591d963a6e15a6412ee158d2c75ef23001 (diff) | |
download | SystemUpdater-2dc004693b725be42de5718f412ff3b9c24d2e9e.tar.gz |
Update the SystemUpdater
- Use update engine to install updates
- More car UX cleanup
- Request permissions to access storage
bug: 72832693
Test: make SystemUpdater; install and test update on device.
Change-Id: Iebca00a9a8c9bff735b63c62d99f51975c2f9264
-rw-r--r-- | AndroidManifest.xml | 10 | ||||
-rw-r--r-- | res/layout/folder_list.xml | 22 | ||||
-rw-r--r-- | res/values/strings.xml | 10 | ||||
-rw-r--r-- | res/values/styles.xml | 4 | ||||
-rw-r--r-- | src/com/android/car/systemupdater/DeviceListFragment.java | 59 | ||||
-rw-r--r-- | src/com/android/car/systemupdater/SystemUpdaterActivity.java | 33 | ||||
-rw-r--r-- | src/com/android/car/systemupdater/UpdateLayoutFragment.java | 198 | ||||
-rw-r--r-- | src/com/android/car/systemupdater/UpdateParser.java | 120 |
8 files changed, 295 insertions, 161 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fb2e14e..e03b9c7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -17,17 +17,11 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.car.systemupdater" > - <uses-sdk android:minSdkVersion="21" /> + package="com.android.car.systemupdater"> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" /> - <uses-permission android:name="android.permission.DELETE_CACHE_FILES" /> <uses-permission android:name="android.permission.REBOOT" /> - <uses-permission android:name="android.permission.RECOVERY" /> - <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <uses-feature android:name="android.hardware.usb.host" /> <application diff --git a/res/layout/folder_list.xml b/res/layout/folder_list.xml index ba3eeff..33982d6 100644 --- a/res/layout/folder_list.xml +++ b/res/layout/folder_list.xml @@ -14,12 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent"> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/current_path" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:textAppearance="@style/ListPath" + android:layout_marginStart="@dimen/car_gutter_size" + android:layout_marginEnd="@dimen/car_margin"/> <androidx.car.widget.PagedListView android:id="@+id/folder_list" @@ -27,4 +35,4 @@ android:layout_height="match_parent" app:showPagedListViewDivider="true" app:gutter="start"/> -</FrameLayout> +</LinearLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index 0aa33b0..d72f65a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -40,10 +40,6 @@ <string name="verify_in_progress">Verifying update…</string> <!-- An error message indicating that verification failed. [CHAR LIMIT=40] --> <string name="verify_failure">Verification Failed. Please select a valid update file.</string> - <!-- A status that indicates that the update is being copied before installation. [CHAR LIMIT=40] --> - <string name="copy_in_progress">Copying to device…</string> - <!-- An error message indicating that copying failed. [CHAR LIMIT=40] --> - <string name="copy_failure">Failed to copy to the device. Check disk space and try again.</string> <!-- A status that indicates that the update is ready to be installed. [CHAR LIMIT=40] --> <string name="install_ready">The update is ready to be installed.</string> <!-- A status that indicates the installation process is running. [CHAR LIMIT=40] --> @@ -52,4 +48,10 @@ <string name="install_success">The update is successful.</string> <!-- A status that indicates that installation failed. [CHAR LIMIT=40] --> <string name="install_failed">System update installation failed.</string> + <!-- A status that indicates the system is about to reboot. [CHAR LIMIT=40] --> + <string name="rebooting">The update is successful. Rebooting now…</string> + <!-- The volumes found on the device. [CHAR LIMIT=40] --> + <string name="volumes">Volumes (%d)</string> + <!-- The path of the current directory. [CHAR LIMIT=20] --> + <string name="path">Path: %s</string> </resources> diff --git a/res/values/styles.xml b/res/values/styles.xml index c8e0f0b..e3a19e6 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -60,4 +60,8 @@ <item name="android:background">?android:selectableItemBackground</item> <item name="android:gravity">center</item> </style> + + <style name="ListPath" parent="CarBody2"> + <item name="android:textColor">@color/car_accent</item> + </style> </resources> diff --git a/src/com/android/car/systemupdater/DeviceListFragment.java b/src/com/android/car/systemupdater/DeviceListFragment.java index a19e652..d32cb7e 100644 --- a/src/com/android/car/systemupdater/DeviceListFragment.java +++ b/src/com/android/car/systemupdater/DeviceListFragment.java @@ -25,31 +25,39 @@ import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import android.widget.Toast; -import android.util.Log; - -import androidx.car.widget.PagedListView; -import androidx.car.widget.ListItem; -import androidx.car.widget.ListItemAdapter; -import androidx.car.widget.ListItemProvider; -import androidx.car.widget.TextListItem; import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; +import androidx.car.widget.ListItem; +import androidx.car.widget.ListItemAdapter; +import androidx.car.widget.ListItemProvider; +import androidx.car.widget.PagedListView; +import androidx.car.widget.TextListItem; + /** -* Display a list of files and directories. -*/ + * Display a list of files and directories. + */ public class DeviceListFragment extends Fragment { private static final String TAG = "DeviceListFragment"; private static final String UPDATE_FILE_SUFFIX = ".zip"; + private static final FileFilter UPDATE_FILE_FILTER = + file -> !file.isHidden() && (file.isDirectory() + || file.getName().toLowerCase().endsWith(UPDATE_FILE_SUFFIX)); + private final Stack<File> mFileStack = new Stack<>(); private StorageManager mStorageManager; @@ -57,6 +65,7 @@ public class DeviceListFragment extends Fragment { private List<File> mListItems; private ListItemAdapter mAdapter; private FileItemProvider mItemProvider; + private TextView mCurrentPathView; private final StorageEventListener mListener = new StorageEventListener() { @Override @@ -91,21 +100,22 @@ public class DeviceListFragment extends Fragment { Toast.makeText(context, R.string.cannot_access_storage, Toast.LENGTH_LONG).show(); return; } - showMountedVolumes(); } - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { mAdapter = new ListItemAdapter(getContext(), mItemProvider); return inflater.inflate(R.layout.folder_list, container, false); - } + } - @Override + @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { PagedListView folderListView = (PagedListView) view.findViewById(R.id.folder_list); folderListView.setMaxPages(PagedListView.ItemCap.UNLIMITED); folderListView.setAdapter(mAdapter); + + mCurrentPathView = (TextView) view.findViewById(R.id.current_path); } @Override @@ -118,6 +128,8 @@ public class DeviceListFragment extends Fragment { actionBar.setDisplayShowTitleEnabled(false); activity.findViewById(R.id.action_bar_icon_container) .setOnClickListener(v -> onBackPressed()); + + showMountedVolumes(); } @Override @@ -145,10 +157,15 @@ public class DeviceListFragment extends Fragment { ArrayList<File> volumes = new ArrayList<>(vols.size()); for (VolumeInfo vol : vols) { File path = vol.getPathForUser(getActivity().getUserId()); - if (vol.getState() == VolumeInfo.STATE_MOUNTED && path != null) { + if (vol.getState() == VolumeInfo.STATE_MOUNTED + && vol.getType() == VolumeInfo.TYPE_PUBLIC + && path != null) { volumes.add(path); } } + + // Otherwise show all of the available volumes. + mCurrentPathView.setText(getString(R.string.volumes, volumes.size())); setFileList(volumes); } @@ -198,11 +215,13 @@ public class DeviceListFragment extends Fragment { return; } + mCurrentPathView.setText(getString(R.string.path, folder.getAbsolutePath())); + // Retrieve the list of files and update the displayed list. new AsyncTask<File, Void, File[]>() { @Override protected File[] doInBackground(File... file) { - return file[0].listFiles(); + return file[0].listFiles(UPDATE_FILE_FILTER); } @Override @@ -210,6 +229,8 @@ public class DeviceListFragment extends Fragment { super.onPostExecute(results); if (results == null) { results = new File[0]; + Toast.makeText(getContext(), R.string.cannot_access_storage, + Toast.LENGTH_LONG).show(); } setFileList(Arrays.asList(results)); } @@ -232,7 +253,7 @@ public class DeviceListFragment extends Fragment { TextListItem item = new TextListItem(mContext); File file = mListItems.get(position); if (file != null) { - item.setTitle(file.getAbsolutePath()); + item.setTitle(file.getName()); item.setOnClickListener(v -> onFileSelected(file)); } else { item.setTitle(getString(R.string.unknown_file)); @@ -242,7 +263,7 @@ public class DeviceListFragment extends Fragment { @Override public int size() { - return mListItems.size(); + return mListItems == null ? 0 : mListItems.size(); } } diff --git a/src/com/android/car/systemupdater/SystemUpdaterActivity.java b/src/com/android/car/systemupdater/SystemUpdaterActivity.java index e5fd9fb..91cbe6b 100644 --- a/src/com/android/car/systemupdater/SystemUpdaterActivity.java +++ b/src/com/android/car/systemupdater/SystemUpdaterActivity.java @@ -15,7 +15,11 @@ */ package com.android.car.systemupdater; +import android.Manifest; +import android.content.pm.PackageManager; import android.os.Bundle; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -27,9 +31,22 @@ import java.io.File; public class SystemUpdaterActivity extends AppCompatActivity implements DeviceListFragment.SystemUpdater { + private static final int STORAGE_PERMISSIONS_REQUEST_CODE = 0; + private static final String[] REQUIRED_STORAGE_PERMISSIONS = new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, REQUIRED_STORAGE_PERMISSIONS, + STORAGE_PERMISSIONS_REQUEST_CODE); + } + setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -43,6 +60,22 @@ public class SystemUpdaterActivity extends AppCompatActivity } @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], + int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (STORAGE_PERMISSIONS_REQUEST_CODE == requestCode) { + if (grantResults.length == 0) { + finish(); + } + for (int grantResult : grantResults) { + if (grantResult != PackageManager.PERMISSION_GRANTED) { + finish(); + } + } + } + } + + @Override public void applyUpdate(File file) { UpdateLayoutFragment fragment = UpdateLayoutFragment.getInstance(file); getSupportFragmentManager().beginTransaction() diff --git a/src/com/android/car/systemupdater/UpdateLayoutFragment.java b/src/com/android/car/systemupdater/UpdateLayoutFragment.java index 70d41a8..699ae19 100644 --- a/src/com/android/car/systemupdater/UpdateLayoutFragment.java +++ b/src/com/android/car/systemupdater/UpdateLayoutFragment.java @@ -15,9 +15,13 @@ */ package com.android.car.systemupdater; +import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import android.os.RecoverySystem; +import android.os.Handler; +import android.os.PowerManager; +import android.os.UpdateEngine; +import android.os.UpdateEngineCallback; import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.v4.app.Fragment; @@ -32,20 +36,17 @@ import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.internal.util.Preconditions; + import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.GeneralSecurityException; /** Display update state and progress. */ public class UpdateLayoutFragment extends Fragment { private static final String TAG = "UpdateLayoutFragment"; - private static final int COPY_BUF_SIZE = 0x10000; // 64k private static final String EXTRA_UPDATE_FILE = "extra_update_file"; - private static final String UPDATE_FILE_NAME = "update.zip"; + private static final int PERCENT_MAX = 100; + private static final String REBOOT_REASON = "reboot-ab-update"; private ProgressBar mProgressBar; private TextView mContentTitle; @@ -53,9 +54,11 @@ public class UpdateLayoutFragment extends Fragment { private TextView mContentDetails; private File mUpdateFile; private Button mSystemUpdateToolbarAction; - private PackageVerifier mPackageVerifier; - private CopyFile mCopyFile; - private InstallUpdate mInstallUpdate; + private PowerManager mPowerManager; + private final UpdateVerifier mPackageVerifier = new UpdateVerifier(); + private final UpdateEngine mUpdateEngine = new UpdateEngine(); + + private final CarUpdateEngineCallback mCarUpdateEngineCallback = new CarUpdateEngineCallback(); /** Create a {@link DeviceListFragment}. */ public static UpdateLayoutFragment getInstance(File file) { @@ -71,11 +74,12 @@ public class UpdateLayoutFragment extends Fragment { super.onCreate(savedInstanceState); mUpdateFile = new File(getArguments().getString(EXTRA_UPDATE_FILE)); + mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); } @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { return inflater.inflate(R.layout.system_update_auto_content, container, false); } @@ -102,31 +106,28 @@ public class UpdateLayoutFragment extends Fragment { mProgressBar = (ProgressBar) activity.findViewById(R.id.progress_bar); mSystemUpdateToolbarAction = activity.findViewById(R.id.system_update_auto_toolbar_action); + mProgressBar.setIndeterminate(true); + mProgressBar.setVisibility(View.VISIBLE); + showStatus(R.string.verify_in_progress); - mPackageVerifier = new PackageVerifier(); mPackageVerifier.execute(mUpdateFile); } @Override public void onStop() { super.onStop(); - if (mCopyFile != null) { - mCopyFile.cancel(true); - } - if (mInstallUpdate != null) { - mInstallUpdate.cancel(true); - } if (mPackageVerifier != null) { mPackageVerifier.cancel(true); } } + /** Update the status information. */ private void showStatus(@StringRes int status) { mContentTitle.setText(status); } /** Show the install now button. */ - private void showInstallNow(File update) { + private void showInstallNow(UpdateParser.ParsedUpdate update) { mContentTitle.setText(R.string.install_ready); mContentInfo.append(getString(R.string.update_file_name, mUpdateFile.getName())); mContentInfo.append(System.getProperty("line.separator")); @@ -138,141 +139,92 @@ public class UpdateLayoutFragment extends Fragment { mSystemUpdateToolbarAction.setVisibility(View.VISIBLE); } - /** Attempt to install the update that is copied to the device. */ - private void installUpdate(File update) { - mInstallUpdate = new InstallUpdate(); - mInstallUpdate.execute(update); + /** Reboot the system. */ + private void rebootNow() { + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, "Rebooting Now."); + } + mPowerManager.reboot(REBOOT_REASON); } - /** Attempt to verify the package. */ - private class PackageVerifier extends AsyncTask<File, Void, File> { + /** Attempt to install the update that is copied to the device. */ + private void installUpdate(UpdateParser.ParsedUpdate parsedUpdate) { + mProgressBar.setIndeterminate(false); + mProgressBar.setVisibility(View.VISIBLE); + mProgressBar.setMax(PERCENT_MAX); + mSystemUpdateToolbarAction.setVisibility(View.GONE); + showStatus(R.string.install_in_progress); + + mUpdateEngine.bind(mCarUpdateEngineCallback, + new Handler(getContext().getMainLooper())); + mUpdateEngine.applyPayload( + parsedUpdate.mUrl, parsedUpdate.mOffset, parsedUpdate.mSize, parsedUpdate.mProps); - @Override - public void onPreExecute() { - mProgressBar.setIndeterminate(true); - mProgressBar.setVisibility(View.VISIBLE); - showStatus(R.string.verify_in_progress); - } + } + + /** Attempt to verify the update and extract information needed for installation. */ + private class UpdateVerifier extends AsyncTask<File, Void, UpdateParser.ParsedUpdate> { @Override - protected File doInBackground(File... files) { + protected UpdateParser.ParsedUpdate doInBackground(File... files) { + Preconditions.checkArgument(files.length > 0, "No file specified"); File file = files[0]; try { - RecoverySystem.verifyPackage(file, null, null); - return file; - } catch (GeneralSecurityException | IOException e) { - Log.e(TAG, String.format("While verifying package: %s", file), e); + return UpdateParser.parse(file); + } catch (IOException e) { + Log.e(TAG, String.format("For file %s", file), e); return null; } } @Override - protected void onPostExecute(File result) { + protected void onPostExecute(UpdateParser.ParsedUpdate result) { mProgressBar.setVisibility(View.GONE); if (result == null) { showStatus(R.string.verify_failure); return; } - - mCopyFile = new CopyFile(); - mCopyFile.execute(result); - } - } - - /** Copy the update file to the data partition so it can be installed. */ - private class CopyFile extends AsyncTask<File, Integer, File> { - private final File mCacheDir; - - CopyFile() { - mCacheDir = getContext().getCacheDir(); - } - - @Override - public void onPreExecute() { - showStatus(R.string.copy_in_progress); - mProgressBar.setIndeterminate(false); - mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.setMax((int)(mUpdateFile.length() / COPY_BUF_SIZE)); - } - - @Override - protected File doInBackground(File... files) { - final File file = files[0]; - if (mCacheDir.getFreeSpace() < file.length()) { - Log.e(TAG, "Not enough cache space!"); - return null; - } - final File dest = new File(mCacheDir, UPDATE_FILE_NAME); - try { - copy(file, dest); - return dest; - } catch (IOException e) { - Log.e(TAG, "Error when copying file to cache", e); - dest.delete(); - return null; - } - } - - @Override - protected void onPostExecute(File result) { - mProgressBar.setVisibility(View.GONE); - if (result == null) { - // Copy failed - showStatus(R.string.copy_failure); + if (!result.isValid()) { + showStatus(R.string.verify_failure); + Log.e(TAG, String.format("Failed verification %s", result)); return; } + if (Log.isLoggable(TAG, Log.INFO)) { + Log.i(TAG, result.toString()); + } showInstallNow(result); } - - protected void onProgressUpdate(Integer... progress) { - mProgressBar.incrementProgressBy(progress[0]); - } - - /** Copy a file from {@code src} to {@code dest}. */ - private void copy(File src, File dst) throws IOException { - try (InputStream in = new FileInputStream(src); - OutputStream out = new FileOutputStream(dst)) { - final byte[] buf = new byte[COPY_BUF_SIZE]; - int len; - int count = 0; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - publishProgress(++count); - } - } - } } - /** Attempt to install the package. */ - private class InstallUpdate extends AsyncTask<File, Void, Boolean> { - - @Override - public void onPreExecute() { - mProgressBar.setIndeterminate(true); - mProgressBar.setVisibility(View.VISIBLE); - mSystemUpdateToolbarAction.setVisibility(View.GONE); - showStatus(R.string.install_in_progress); - } + /** Handles events from the UpdateEngine. */ + public class CarUpdateEngineCallback extends UpdateEngineCallback { @Override - protected Boolean doInBackground(File... files) { - File file = files[0]; - try { - RecoverySystem.installPackage(getContext(), file); - return true; - } catch (IOException e) { - Log.e(TAG, "While installing the update package", e); - return false; + public void onStatusUpdate(int status, float percent) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, String.format("onStatusUpdate %d, Percent %.2f", status, percent)); + } + switch (status) { + case UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT: + rebootNow(); + break; + case UpdateEngine.UpdateStatusConstants.DOWNLOADING: + mProgressBar.setProgress((int) (percent * 100)); + break; + default: + // noop } } @Override - protected void onPostExecute(Boolean result) { + public void onPayloadApplicationComplete(int errorCode) { + Log.w(TAG, String.format("onPayloadApplicationComplete %d", errorCode)); + showStatus(errorCode == UpdateEngine.ErrorCodeConstants.SUCCESS + ? R.string.install_success + : R.string.install_failed); mProgressBar.setVisibility(View.GONE); mSystemUpdateToolbarAction.setVisibility(View.GONE); - - showStatus(result ? R.string.install_success : R.string.install_failed); } } } diff --git a/src/com/android/car/systemupdater/UpdateParser.java b/src/com/android/car/systemupdater/UpdateParser.java new file mode 100644 index 0000000..d48096d --- /dev/null +++ b/src/com/android/car/systemupdater/UpdateParser.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 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 com.android.car.systemupdater; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** Parse an A/B update zip file. */ +class UpdateParser { + + private static final String TAG = "UpdateLayoutFragment"; + private static final String PAYLOAD_BIN_FILE = "payload.bin"; + private static final String PAYLOAD_PROPERTIES = "payload_properties.txt"; + private static final String FILE_URL_PREFIX = "file://"; + private static final int ZIP_FILE_HEADER = 30; + + private UpdateParser() { + } + + /** + * Parse a zip file containing a system update and return a non null ParsedUpdate. + */ + @Nullable + static ParsedUpdate parse(@NonNull File file) throws IOException { + Preconditions.checkNotNull(file); + + long payloadOffset = 0; + long payloadSize = 0; + boolean payloadFound = false; + String[] props = null; + + try (ZipFile zipFile = new ZipFile(file)) { + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + long fileSize = entry.getCompressedSize(); + if (!payloadFound) { + payloadOffset += ZIP_FILE_HEADER + entry.getName().length(); + if (entry.getExtra() != null) { + payloadOffset += entry.getExtra().length; + } + } + + if (entry.isDirectory()) { + continue; + } else if (entry.getName().equals(PAYLOAD_BIN_FILE)) { + payloadSize = fileSize; + payloadFound = true; + } else if (entry.getName().equals(PAYLOAD_PROPERTIES)) { + try (BufferedReader buffer = new BufferedReader( + new InputStreamReader(zipFile.getInputStream(entry)))) { + props = buffer.lines().toArray(String[]::new); + } + } + if (!payloadFound) { + payloadOffset += fileSize; + } + + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, String.format("Entry %s", entry.getName())); + } + } + } + return new ParsedUpdate(file, payloadOffset, payloadSize, props); + } + + /** Information parsed from an update file. */ + static class ParsedUpdate { + final String mUrl; + final long mOffset; + final long mSize; + final String[] mProps; + + ParsedUpdate(File file, long offset, long size, String[] props) { + mUrl = FILE_URL_PREFIX + file.getAbsolutePath(); + mOffset = offset; + mSize = size; + mProps = props; + } + + /** Verify the update information is correct. */ + boolean isValid() { + return mOffset >= 0 && mSize > 0 && mProps != null; + } + + @Override + public String toString() { + return String.format(Locale.getDefault(), + "ParsedUpdate: URL=%s, offset=%d, size=%s, props=%s", + mUrl, mOffset, mSize, Arrays.toString(mProps)); + } + } +} |