Add new getHistoricalSessions API to PMInternal

Sessions were written to strings to be stored as a historical session to
avoid keeping references on Bitmaps and other heavy-weight objects.
Since referenes to these heavy-weight object are all in SessionParams,
we just write the SessionParams to a string into a new
PackageInstallerHistoricalSession object.

Bug: 292091934
Test: manual install and check the dump
Change-Id: I839d67dff735a8e45ff206a0c9b08a1e72353042
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 557e4ac..838aae8 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1409,4 +1409,10 @@
      */
     public abstract boolean isPackageQuarantined(@NonNull String packageName,
             @UserIdInt int userId);
+
+    /**
+     * Return a list of all historical install sessions for the given user.
+     */
+    public abstract ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(
+            int userId);
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
new file mode 100644
index 0000000..d40a715
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageInstallerHistoricalSession.java
@@ -0,0 +1,203 @@
+/*
+ * 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.server.pm;
+
+import android.content.pm.PackageInstaller.PreapprovalDetails;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageInstaller.SessionParams;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+
+/**
+ * A historical session object that stores minimal session info.
+ */
+public final class PackageInstallerHistoricalSession {
+    public final int sessionId;
+    public final int userId;
+    private final String mParams;
+    private final long mCreatedMillis;
+
+    private final File mStageDir;
+    private final String mStageCid;
+
+    private final long mUpdatedMillis;
+
+    private final long mCommittedMillis;
+
+    private final int mOriginalInstallerUid;
+
+    private final String mOriginalInstallerPackageName;
+
+    private final int mInstallerUid;
+
+    private final InstallSource mInstallSource;
+
+    private final float mClientProgress;
+
+    private final float mProgress;
+    private final boolean mSealed;
+
+    private final boolean mPreapprovalRequested;
+    private final boolean mCommitted;
+
+    private final boolean mStageDirInUse;
+
+    private final boolean mPermissionsManuallyAccepted;
+
+    private final int mFinalStatus;
+    private final String mFinalMessage;
+
+    private final int mFds;
+    private final int mBridges;
+
+    private final String mPreapprovalDetails;
+    private final int mParentSessionId;
+    private final boolean mDestroyed;
+    private final int[] mChildSessionIds;
+    private final boolean mSessionApplied;
+    private final boolean mSessionReady;
+    private final boolean mSessionFailed;
+    private final int mSessionErrorCode;
+    private final String mSessionErrorMessage;
+
+    PackageInstallerHistoricalSession(int sessionId, int userId, int originalInstallerUid,
+            String originalInstallerPackageName, InstallSource installSource, int installerUid,
+            long createdMillis, long updatedMillis, long committedMillis, File stageDir,
+            String stageCid, float clientProgress, float progress, boolean committed,
+            boolean preapprovalRequested, boolean sealed, boolean permissionsManuallyAccepted,
+            boolean stageDirInUse, boolean destroyed, int fds, int bridges, int finalStatus,
+            String finalMessage, SessionParams params, int parentSessionId,
+            int[] childSessionIds, boolean sessionApplied, boolean sessionFailed,
+            boolean sessionReady, int sessionErrorCode, String sessionErrorMessage,
+            PreapprovalDetails preapprovalDetails) {
+        this.sessionId = sessionId;
+        this.userId = userId;
+        this.mOriginalInstallerUid = originalInstallerUid;
+        this.mOriginalInstallerPackageName = originalInstallerPackageName;
+        this.mInstallSource = installSource;
+        this.mInstallerUid = installerUid;
+        this.mCreatedMillis = createdMillis;
+        this.mUpdatedMillis = updatedMillis;
+        this.mCommittedMillis = committedMillis;
+        this.mStageDir = stageDir;
+        this.mStageCid = stageCid;
+        this.mClientProgress = clientProgress;
+        this.mProgress = progress;
+        this.mCommitted = committed;
+        this.mPreapprovalRequested = preapprovalRequested;
+        this.mSealed = sealed;
+        this.mPermissionsManuallyAccepted = permissionsManuallyAccepted;
+        this.mStageDirInUse = stageDirInUse;
+        this.mDestroyed = destroyed;
+        this.mFds = fds;
+        this.mBridges = bridges;
+        this.mFinalStatus = finalStatus;
+        this.mFinalMessage = finalMessage;
+
+        CharArrayWriter writer = new CharArrayWriter();
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
+        params.dump(pw);
+        this.mParams = writer.toString();
+
+        this.mParentSessionId = parentSessionId;
+        this.mChildSessionIds = childSessionIds;
+        this.mSessionApplied = sessionApplied;
+        this.mSessionFailed = sessionFailed;
+        this.mSessionReady = sessionReady;
+        this.mSessionErrorCode = sessionErrorCode;
+        this.mSessionErrorMessage = sessionErrorMessage;
+        if (preapprovalDetails != null) {
+            this.mPreapprovalDetails = preapprovalDetails.toString();
+        } else {
+            this.mPreapprovalDetails = null;
+        }
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Session " + sessionId + ":");
+        pw.increaseIndent();
+
+        pw.printPair("userId", userId);
+        pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+        pw.printPair("mOriginalInstallerPackageName", mOriginalInstallerPackageName);
+        pw.printPair("installerPackageName", mInstallSource.mInstallerPackageName);
+        pw.printPair("installInitiatingPackageName", mInstallSource.mInitiatingPackageName);
+        pw.printPair("installOriginatingPackageName", mInstallSource.mOriginatingPackageName);
+        pw.printPair("mInstallerUid", mInstallerUid);
+        pw.printPair("createdMillis", mCreatedMillis);
+        pw.printPair("updatedMillis", mUpdatedMillis);
+        pw.printPair("committedMillis", mCommittedMillis);
+        pw.printPair("stageDir", mStageDir);
+        pw.printPair("stageCid", mStageCid);
+        pw.println();
+
+        pw.print(mParams);
+
+        pw.printPair("mClientProgress", mClientProgress);
+        pw.printPair("mProgress", mProgress);
+        pw.printPair("mCommitted", mCommitted);
+        pw.printPair("mPreapprovalRequested", mPreapprovalRequested);
+        pw.printPair("mSealed", mSealed);
+        pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
+        pw.printPair("mStageDirInUse", mStageDirInUse);
+        pw.printPair("mDestroyed", mDestroyed);
+        pw.printPair("mFds", mFds);
+        pw.printPair("mBridges", mBridges);
+        pw.printPair("mFinalStatus", mFinalStatus);
+        pw.printPair("mFinalMessage", mFinalMessage);
+        pw.printPair("mParentSessionId", mParentSessionId);
+        pw.printPair("mChildSessionIds", mChildSessionIds);
+        pw.printPair("mSessionApplied", mSessionApplied);
+        pw.printPair("mSessionFailed", mSessionFailed);
+        pw.printPair("mSessionReady", mSessionReady);
+        pw.printPair("mSessionErrorCode", mSessionErrorCode);
+        pw.printPair("mSessionErrorMessage", mSessionErrorMessage);
+        pw.printPair("mPreapprovalDetails", mPreapprovalDetails);
+        pw.println();
+
+        pw.decreaseIndent();
+    }
+
+    /**
+     * Generates a {@link SessionInfo} object.
+     */
+    public SessionInfo generateInfo() {
+        final SessionInfo info = new SessionInfo();
+        info.sessionId = sessionId;
+        info.userId = userId;
+        info.installerPackageName = mInstallSource.mInstallerPackageName;
+        info.installerAttributionTag = mInstallSource.mInstallerAttributionTag;
+        info.progress = mProgress;
+        info.sealed = mSealed;
+        info.isCommitted = mCommitted;
+        info.isPreapprovalRequested = mPreapprovalRequested;
+
+        info.parentSessionId = mParentSessionId;
+        info.childSessionIds = mChildSessionIds;
+        info.isSessionApplied = mSessionApplied;
+        info.isSessionReady = mSessionReady;
+        info.isSessionFailed = mSessionFailed;
+        info.setSessionErrorCode(mSessionErrorCode, mSessionErrorMessage);
+        info.createdMillis = mCreatedMillis;
+        info.updatedMillis = mUpdatedMillis;
+        info.installerUid = mInstallerUid;
+        return info;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 10cd51a..e360256 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,7 +111,6 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.CharArrayWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -230,7 +229,7 @@
 
     /** Historical sessions kept around for debugging purposes */
     @GuardedBy("mSessions")
-    private final List<String> mHistoricalSessions = new ArrayList<>();
+    private final List<PackageInstallerHistoricalSession> mHistoricalSessions = new ArrayList<>();
 
     @GuardedBy("mSessions")
     private final SparseIntArray mHistoricalSessionsByInstaller = new SparseIntArray();
@@ -570,14 +569,11 @@
 
     @GuardedBy("mSessions")
     private void addHistoricalSessionLocked(PackageInstallerSession session) {
-        CharArrayWriter writer = new CharArrayWriter();
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
-        session.dump(pw);
         if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
             Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
             mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
         }
-        mHistoricalSessions.add(writer.toString());
+        mHistoricalSessions.add(session.createHistoricalSession());
 
         int installerUid = session.getInstallerUid();
         // Increment the number of sessions by this installerUid.
@@ -1223,6 +1219,24 @@
         return new ParceledListSlice<>(result);
     }
 
+    ParceledListSlice<SessionInfo> getHistoricalSessions(int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final Computer snapshot = mPm.snapshotComputer();
+        snapshot.enforceCrossUserPermission(callingUid, userId, true, false, "getAllSessions");
+
+        final List<SessionInfo> result = new ArrayList<>();
+        synchronized (mSessions) {
+            for (int i = 0; i < mHistoricalSessions.size(); i++) {
+                final PackageInstallerHistoricalSession session = mHistoricalSessions.get(i);
+                if (userId == UserHandle.USER_ALL || session.userId == userId) {
+                    result.add(session.generateInfo());
+                }
+            }
+        }
+        result.removeIf(info -> shouldFilterSession(snapshot, callingUid, info));
+        return new ParceledListSlice<>(result);
+    }
+
     @Override
     public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                 IntentSender statusReceiver, int userId) {
@@ -1837,7 +1851,7 @@
             pw.increaseIndent();
             N = mHistoricalSessions.size();
             for (int i = 0; i < N; i++) {
-                pw.print(mHistoricalSessions.get(i));
+                mHistoricalSessions.get(i).dump(pw);
                 pw.println();
             }
             pw.println();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 6270655..b6079a6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1148,6 +1148,25 @@
         }
     }
 
+    PackageInstallerHistoricalSession createHistoricalSession() {
+        final float progress;
+        final float clientProgress;
+        synchronized (mProgressLock) {
+            progress = mProgress;
+            clientProgress = mClientProgress;
+        }
+        synchronized (mLock) {
+            return new PackageInstallerHistoricalSession(sessionId, userId, mOriginalInstallerUid,
+                    mOriginalInstallerPackageName, mInstallSource, mInstallerUid, createdMillis,
+                    updatedMillis, committedMillis, stageDir, stageCid, clientProgress, progress,
+                    isCommitted(), isPreapprovalRequested(), mSealed, mPermissionsManuallyAccepted,
+                    mStageDirInUse, mDestroyed, mFds.size(), mBridges.size(), mFinalStatus,
+                    mFinalMessage, params, mParentSessionId, getChildSessionIdsLocked(),
+                    mSessionApplied, mSessionFailed, mSessionReady, mSessionErrorCode,
+                    mSessionErrorMessage, mPreapprovalDetails);
+        }
+    }
+
     /**
      * Returns {@code true} if the {@link SessionInfo} object should be produced with potentially
      * sensitive data scrubbed from its fields.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c63d8be..914f6d0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6933,6 +6933,11 @@
             return mDistractingPackageHelper.getDistractingPackageRestrictionsAsUser(snapshot,
                     packageNames, userId, callingUid);
         }
+
+        @Override
+        public ParceledListSlice<PackageInstaller.SessionInfo> getHistoricalSessions(int userId) {
+            return mInstallerService.getHistoricalSessions(userId);
+        }
     }
 
     private void setEnabledOverlayPackages(@UserIdInt int userId,