Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1b8522ca authored by Sumedh Sen's avatar Sumedh Sen
Browse files

Introduce a helper class for staging a session

This class will write the bytes from the supplied APK into an
OutputStream

Bug: 182205982
Test: builds successfully
Test: No CTS Tests. Flag to use new app is turned off by default

Change-Id: Ib0a805bd67481e6d497ffd1c6b3384233407b7f3
parent 215e074b
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -197,6 +197,13 @@ public class InstallRepository {
        }
    }

    public interface SessionStageListener {

        void onStagingSuccess(SessionInfo info);

        void onStagingFailure();
    }

    public static class CallerInfo {

        private final String mPackageName;
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.packageinstaller.v2.model;

import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;

import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {

    private static final String TAG = SessionStager.class.getSimpleName();
    private final Context mContext;
    private final Uri mUri;
    private final int mStagedSessionId;
    private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
    private final SessionStageListener mListener;

    SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
        mContext = context;
        mUri = uri;
        mStagedSessionId = stagedSessionId;
        mListener = listener;
    }

    @Override
    protected PackageInstaller.SessionInfo doInBackground(Void... params) {
        PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
        try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
            InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
            session.setStagingProgress(0);

            if (in == null) {
                return null;
            }
            final long sizeBytes = getContentSizeBytes();
            mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);

            long totalRead = 0;
            try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
                byte[] buffer = new byte[1024 * 1024];
                while (true) {
                    int numRead = in.read(buffer);

                    if (numRead == -1) {
                        session.fsync(out);
                        break;
                    }

                    if (isCancelled()) {
                        break;
                    }

                    out.write(buffer, 0, numRead);
                    if (sizeBytes > 0) {
                        totalRead += numRead;
                        float fraction = ((float) totalRead / (float) sizeBytes);
                        session.setStagingProgress(fraction);
                        publishProgress((int) (fraction * 100.0));
                    }
                }
            }
            return pi.getSessionInfo(mStagedSessionId);
        } catch (IOException | SecurityException | IllegalStateException
                 | IllegalArgumentException e) {
            Log.w(TAG, "Error staging apk from content URI", e);
            return null;
        }
    }

    private long getContentSizeBytes() {
        try (AssetFileDescriptor afd = mContext.getContentResolver()
            .openAssetFileDescriptor(mUri, "r")) {
            return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
        } catch (IOException e) {
            Log.w(TAG, "Failed to open asset file descriptor", e);
            return UNKNOWN_LENGTH;
        }
    }

    public MutableLiveData<Integer> getProgress() {
        return mProgressLiveData;
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        if (progress != null && progress.length > 0) {
            mProgressLiveData.setValue(progress[0]);
        }
    }

    @Override
    protected void onPostExecute(SessionInfo sessionInfo) {
        if (sessionInfo == null || !sessionInfo.isActive()
            || sessionInfo.getResolvedBaseApkPath() == null) {
            Log.w(TAG, "Session info is invalid: " + sessionInfo);
            mListener.onStagingFailure();
            return;
        }
        mListener.onStagingSuccess(sessionInfo);
    }
}