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

Commit 278af8f4 authored by Nikita Ioffe's avatar Nikita Ioffe
Browse files

Make ApexManager an abstract class

And a two implementations:
* ApexManagerImpl - in case device supports updatable APEX.
* ApexManagerNoOp - in case device does not.

This is a more future-proof way of handling both new flow that supports
APEX and legacy one that does not.

Test: atest CtsStagedInstallHostTestCases
Bug: 131697251
Change-Id: If27fcdb95d4ec60834bd5334b68f53d4235a5d61
parent c255afdd
Loading
Loading
Loading
Loading
+415 −293
Original line number Diff line number Diff line
@@ -11,7 +11,7 @@
 * 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.s
 * limitations under the License.
 */

package com.android.server.pm;
@@ -31,10 +31,8 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.sysprop.ApexProperties;
import android.util.Slog;

@@ -55,8 +53,173 @@ import java.util.stream.Collectors;
 * ApexManager class handles communications with the apex service to perform operation and queries,
 * as well as providing caching to avoid unnecessary calls to the service.
 */
class ApexManager {
    static final String TAG = "ApexManager";
abstract class ApexManager {

    private static final String TAG = "ApexManager";

    static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
    static final int MATCH_FACTORY_PACKAGE = 1 << 1;

    /**
     * Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
     * on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
     * {@code true}.
     */
    static ApexManager create(Context systemContext) {
        if (ApexProperties.updatable().orElse(false)) {
            try {
                return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface(
                        ServiceManager.getServiceOrThrow("apexservice")));
            } catch (ServiceManager.ServiceNotFoundException e) {
                throw new IllegalStateException("Required service apexservice not available");
            }
        } else {
            return new ApexManagerNoOp();
        }
    }

    abstract void systemReady();

    /**
     * Retrieves information about an APEX package.
     *
     * @param packageName the package name to look for. Note that this is the package name reported
     *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
     *                    differ from the one reported in the APEX manifest (i.e.
     *                    apex_manifest.json).
     * @param flags the type of package to return. This may match to active packages
     *              and factory (pre-installed) packages.
     * @return a PackageInfo object with the information about the package, or null if the package
     *         is not found.
     */
    @Nullable
    abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags);

    /**
     * Retrieves information about all active APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         active package.
     */
    abstract List<PackageInfo> getActivePackages();

    /**
     * Retrieves information about all active pre-installed APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         active pre-installed package.
     */
    abstract List<PackageInfo> getFactoryPackages();

    /**
     * Retrieves information about all inactive APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         inactive package.
     */
    abstract List<PackageInfo> getInactivePackages();

    /**
     * Checks if {@code packageName} is an apex package.
     *
     * @param packageName package to check.
     * @return {@code true} if {@code packageName} is an apex package.
     */
    abstract boolean isApexPackage(String packageName);

    /**
     * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
     * track the different states of a session.
     *
     * @param sessionId the identifier of the session.
     * @return an ApexSessionInfo object, or null if the session is not known.
     */
    @Nullable
    abstract ApexSessionInfo getStagedSessionInfo(int sessionId);

    /**
     * Submit a staged session to apex service. This causes the apex service to perform some initial
     * verification and accept or reject the session. Submitting a session successfully is not
     * enough for it to be activated at the next boot, the caller needs to call
     * {@link #markStagedSessionReady(int)}.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
     * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
     *                        an array of identifiers of all the child sessions. Otherwise it should
     *                        be an empty array.
     * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
     *                     and will be filled with a list of {@link ApexInfo} objects, each of which
     *                     contains metadata about one of the packages being submitted as part of
     *                     the session.
     * @return whether the submission of the session was successful.
     */
    abstract boolean submitStagedSession(
            int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList);

    /**
     * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
     * applied at next reboot.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
     * @return true upon success, false if the session is unknown.
     */
    abstract boolean markStagedSessionReady(int sessionId);

    /**
     * Marks a staged session as successful.
     *
     * <p>Only activated session can be marked as successful.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
     *                  successful.
     */
    abstract void markStagedSessionSuccessful(int sessionId);

    /**
     * Whether the current device supports the management of APEX packages.
     *
     * @return true if APEX packages can be managed on this device, false otherwise.
     */
    abstract boolean isApexSupported();

    /**
     * Abandons the (only) active session previously submitted.
     *
     * @return {@code true} upon success, {@code false} if any remote exception occurs
     */
    abstract boolean abortActiveSession();

    /**
     * Uninstalls given {@code apexPackage}.
     *
     * <p>NOTE. Device must be rebooted in order for uninstall to take effect.
     *
     * @param apexPackagePath package to uninstall.
     * @return {@code true} upon successful uninstall, {@code false} otherwise.
     */
    abstract boolean uninstallApex(String apexPackagePath);

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
     * @param pw the {@link PrintWriter} object to send information to.
     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
     *                    information about that specific package will be dumped.
     */
    abstract void dump(PrintWriter pw, @Nullable String packageName);

    @IntDef(
            flag = true,
            prefix = { "MATCH_"},
            value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
    @Retention(RetentionPolicy.SOURCE)
    @interface PackageInfoFlags{}

    /**
     * An implementation of {@link ApexManager} that should be used in case device supports updating
     * APEX packages.
     */
    private static class ApexManagerImpl extends ApexManager {
        private final IApexService mApexService;
        private final Context mContext;
        private final Object mLock = new Object();
@@ -70,35 +233,37 @@ class ApexManager {
        @GuardedBy("mLock")
        private List<PackageInfo> mAllPackagesCache;

    ApexManager(Context context) {
        ApexManagerImpl(Context context, IApexService apexService) {
            mContext = context;
        if (!isApexSupported()) {
            mApexService = null;
            return;
        }
        try {
            mApexService = IApexService.Stub.asInterface(
                ServiceManager.getServiceOrThrow("apexservice"));
        } catch (ServiceNotFoundException e) {
            throw new IllegalStateException("Required service apexservice not available");
            mApexService = apexService;
        }

        /**
         * Whether an APEX package is active or not.
         *
         * @param packageInfo the package to check
         * @return {@code true} if this package is active, {@code false} otherwise.
         */
        private static boolean isActive(PackageInfo packageInfo) {
            return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
        }

    static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
    static final int MATCH_FACTORY_PACKAGE = 1 << 1;
    @IntDef(
            flag = true,
            prefix = { "MATCH_"},
            value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
    @Retention(RetentionPolicy.SOURCE)
    @interface PackageInfoFlags{}
        /**
         * Whether the APEX package is pre-installed or not.
         *
         * @param packageInfo the package to check
         * @return {@code true} if this package is pre-installed, {@code false} otherwise.
         */
        private static boolean isFactory(PackageInfo packageInfo) {
            return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
        }

        @Override
        void systemReady() {
        if (!isApexSupported()) return;
            mContext.registerReceiver(new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                onBootCompleted();
                    populateAllPackagesCacheIfNeeded();
                    mContext.unregisterReceiver(this);
                }
            }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
@@ -141,7 +306,7 @@ class ApexManager {
                                }
                                factoryPackagesSet.add(pkg.packageName);
                            }
                    } catch (PackageParserException pe) {
                        } catch (PackageParser.PackageParserException pe) {
                            throw new IllegalStateException("Unable to parse: " + ai, pe);
                        }
                    }
@@ -152,20 +317,8 @@ class ApexManager {
            }
        }

    /**
     * Retrieves information about an APEX package.
     *
     * @param packageName the package name to look for. Note that this is the package name reported
     *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
     *                    differ from the one reported in the APEX manifest (i.e.
     *                    apex_manifest.json).
     * @param flags the type of package to return. This may match to active packages
     *              and factory (pre-installed) packages.
     * @return a PackageInfo object with the information about the package, or null if the package
     *         is not found.
     */
        @Override
        @Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
        if (!isApexSupported()) return null;
            populateAllPackagesCacheIfNeeded();
            boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
            boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
@@ -181,14 +334,8 @@ class ApexManager {
            return null;
        }

    /**
     * Retrieves information about all active APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         active package.
     */
        @Override
        List<PackageInfo> getActivePackages() {
        if (!isApexSupported()) return Collections.emptyList();
            populateAllPackagesCacheIfNeeded();
            return mAllPackagesCache
                    .stream()
@@ -196,14 +343,8 @@ class ApexManager {
                    .collect(Collectors.toList());
        }

    /**
     * Retrieves information about all active pre-installed APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         active pre-installed package.
     */
        @Override
        List<PackageInfo> getFactoryPackages() {
        if (!isApexSupported()) return Collections.emptyList();
            populateAllPackagesCacheIfNeeded();
            return mAllPackagesCache
                    .stream()
@@ -211,14 +352,8 @@ class ApexManager {
                    .collect(Collectors.toList());
        }

    /**
     * Retrieves information about all inactive APEX packages.
     *
     * @return a List of PackageInfo object, each one containing information about a different
     *         inactive package.
     */
        @Override
        List<PackageInfo> getInactivePackages() {
        if (!isApexSupported()) return Collections.emptyList();
            populateAllPackagesCacheIfNeeded();
            return mAllPackagesCache
                    .stream()
@@ -226,12 +361,7 @@ class ApexManager {
                    .collect(Collectors.toList());
        }

    /**
     * Checks if {@code packageName} is an apex package.
     *
     * @param packageName package to check.
     * @return {@code true} if {@code packageName} is an apex package.
     */
        @Override
        boolean isApexPackage(String packageName) {
            if (!isApexSupported()) return false;
            populateAllPackagesCacheIfNeeded();
@@ -243,15 +373,8 @@ class ApexManager {
            return false;
        }

    /**
     * Retrieves information about an apexd staged session i.e. the internal state used by apexd to
     * track the different states of a session.
     *
     * @param sessionId the identifier of the session.
     * @return an ApexSessionInfo object, or null if the session is not known.
     */
        @Override
        @Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
        if (!isApexSupported()) return null;
            try {
                ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
                if (apexSessionInfo.isUnknown) {
@@ -264,25 +387,9 @@ class ApexManager {
            }
        }

    /**
     * Submit a staged session to apex service. This causes the apex service to perform some initial
     * verification and accept or reject the session. Submitting a session successfully is not
     * enough for it to be activated at the next boot, the caller needs to call
     * {@link #markStagedSessionReady(int)}.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being submitted.
     * @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
     *                        an array of identifiers of all the child sessions. Otherwise it should
     *                        be an empty array.
     * @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
     *                     and will be filled with a list of {@link ApexInfo} objects, each of which
     *                     contains metadata about one of the packages being submitted as part of
     *                     the session.
     * @return whether the submission of the session was successful.
     */
        @Override
        boolean submitStagedSession(
                int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
        if (!isApexSupported()) return false;
            try {
                return mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
            } catch (RemoteException re) {
@@ -291,15 +398,8 @@ class ApexManager {
            }
        }

    /**
     * Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
     * applied at next reboot.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
     * @return true upon success, false if the session is unknown.
     */
        @Override
        boolean markStagedSessionReady(int sessionId) {
        if (!isApexSupported()) return false;
            try {
                return mApexService.markStagedSessionReady(sessionId);
            } catch (RemoteException re) {
@@ -308,44 +408,27 @@ class ApexManager {
            }
        }

    /**
     * Marks a staged session as successful.
     *
     * <p>Only activated session can be marked as successful.
     *
     * @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
     *                  successful.
     */
        @Override
        void markStagedSessionSuccessful(int sessionId) {
        if (!isApexSupported()) return;
            try {
                mApexService.markStagedSessionSuccessful(sessionId);
            } catch (RemoteException re) {
                Slog.e(TAG, "Unable to contact apexservice", re);
                throw new RuntimeException(re);
            } catch (Exception e) {
            // It is fine to just log an exception in this case. APEXd will be able to recover in
            // case markStagedSessionSuccessful fails.
                // It is fine to just log an exception in this case. APEXd will be able to recover
                // in case markStagedSessionSuccessful fails.
                Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
            }
        }

    /**
     * Whether the current device supports the management of APEX packages.
     *
     * @return true if APEX packages can be managed on this device, false otherwise.
     */
        @Override
        boolean isApexSupported() {
        return ApexProperties.updatable().orElse(false);
            return true;
        }

    /**
     * Abandons the (only) active session previously submitted.
     *
     * @return {@code true} upon success, {@code false} if any remote exception occurs
     */
        @Override
        boolean abortActiveSession() {
        if (!isApexSupported()) return false;
            try {
                mApexService.abortActiveSession();
                return true;
@@ -355,16 +438,8 @@ class ApexManager {
            }
        }

    /**
     * Uninstalls given {@code apexPackage}.
     *
     * <p>NOTE. Device must be rebooted in order for uninstall to take effect.
     *
     * @param apexPackagePath package to uninstall.
     * @return {@code true} upon successful uninstall, {@code false} otherwise.
     */
        @Override
        boolean uninstallApex(String apexPackagePath) {
        if (!isApexSupported()) return false;
            try {
                mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
                return true;
@@ -373,31 +448,11 @@ class ApexManager {
            }
        }

    /**
     * Whether an APEX package is active or not.
     *
     * @param packageInfo the package to check
     * @return {@code true} if this package is active, {@code false} otherwise.
     */
    private static boolean isActive(PackageInfo packageInfo) {
        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
    }

    /**
     * Whether the APEX package is pre-installed or not.
     *
     * @param packageInfo the package to check
     * @return {@code true} if this package is pre-installed, {@code false} otherwise.
     */
    private static boolean isFactory(PackageInfo packageInfo) {
        return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    }

        /**
         * Dump information about the packages contained in a particular cache
         * @param packagesCache the cache to print information about.
     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
     *                    information about that specific package will be dumped.
         * @param packageName a {@link String} containing a package name, or {@code null}. If set,
         *                    only information about that specific package will be dumped.
         * @param ipw the {@link IndentingPrintWriter} object to send information to.
         */
        void dumpFromPackagesCache(
@@ -422,15 +477,8 @@ class ApexManager {
            ipw.println();
        }

    /**
     * Dumps various state information to the provided {@link PrintWriter} object.
     *
     * @param pw the {@link PrintWriter} object to send information to.
     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
     *                    information about that specific package will be dumped.
     */
        @Override
        void dump(PrintWriter pw, @Nullable String packageName) {
        if (!isApexSupported()) return;
            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
            try {
                populateAllPackagesCacheIfNeeded();
@@ -474,9 +522,83 @@ class ApexManager {
                ipw.println("Couldn't communicate with apexd.");
            }
        }
    }

    public void onBootCompleted() {
        if (!isApexSupported()) return;
        populateAllPackagesCacheIfNeeded();
    /**
     * An implementation of {@link ApexManager} that should be used in case device does not support
     * updating APEX packages.
     */
    private static final class ApexManagerNoOp extends ApexManager {

        @Override
        void systemReady() {
            // No-op
        }

        @Override
        PackageInfo getPackageInfo(String packageName, int flags) {
            return null;
        }

        @Override
        List<PackageInfo> getActivePackages() {
            return Collections.emptyList();
        }

        @Override
        List<PackageInfo> getFactoryPackages() {
            return Collections.emptyList();
        }

        @Override
        List<PackageInfo> getInactivePackages() {
            return Collections.emptyList();
        }

        @Override
        boolean isApexPackage(String packageName) {
            return false;
        }

        @Override
        ApexSessionInfo getStagedSessionInfo(int sessionId) {
            throw new UnsupportedOperationException();
        }

        @Override
        boolean submitStagedSession(int sessionId, int[] childSessionIds,
                ApexInfoList apexInfoList) {
            throw new UnsupportedOperationException();
        }

        @Override
        boolean markStagedSessionReady(int sessionId) {
            throw new UnsupportedOperationException();
        }

        @Override
        void markStagedSessionSuccessful(int sessionId) {
            throw new UnsupportedOperationException();
        }

        @Override
        boolean isApexSupported() {
            return false;
        }

        @Override
        boolean abortActiveSession() {
            throw new UnsupportedOperationException();
        }

        @Override
        boolean uninstallApex(String apexPackagePath) {
            throw new UnsupportedOperationException();
        }

        @Override
        void dump(PrintWriter pw, String packageName) {
            // No-op
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -2489,7 +2489,7 @@ public class PackageManagerService extends IPackageManager.Stub
        mProtectedPackages = new ProtectedPackages(mContext);
        mApexManager = new ApexManager(context);
        mApexManager = ApexManager.create(context);
        // CHECKSTYLE:OFF IndentationCheck
        synchronized (mInstallLock) {
        // writer