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

Commit 49a4a1df authored by Mårten Kongstad's avatar Mårten Kongstad Committed by Adam Lesinski
Browse files

Teach apps to refresh AppInfos without restarting

Teach running applications to refresh already loaded ApplicationInfo
objects without resorting to restarting the application process.

Activities will be scheduled for restart via the regular life-cycle.
This is similar to a configuration change but since ApplicationInfo
changes are too low-level we don't permit apps to opt out.

This change is intended to be used with runtime resource overlays and
split APKs.

Test: Manual - command to update application via ActivityManagerShellCommand
Change-Id: Ice10a1691cced90eee95e3278fd784b8a9206d87
parent 4391fae3
Loading
Loading
Loading
Loading
+76 −18
Original line number Diff line number Diff line
@@ -244,7 +244,7 @@ public final class ActivityThread {
    boolean mSomeActivitiesChanged = false;
    boolean mUpdatingSystemConfig = false;

    // These can be accessed by multiple threads; mPackages is the lock.
    // These can be accessed by multiple threads; mResourcesManager is the lock.
    // XXX For now we keep around information about all packages we have
    // seen, not removing entries from this map.
    // NOTE: The activity and window managers need to call in to
@@ -253,12 +253,13 @@ public final class ActivityThread {
    // holds their own lock.  Thus you MUST NEVER call back into the activity manager
    // or window manager or anything that depends on them while holding this lock.
    // These LoadedApk are only valid for the userId that we're running as.
    final ArrayMap<String, WeakReference<LoadedApk>> mPackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
    final ArrayList<ActivityClientRecord> mRelaunchingActivities
            = new ArrayList<ActivityClientRecord>();
    @GuardedBy("mResourcesManager")
    final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
    @GuardedBy("mResourcesManager")
    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
    @GuardedBy("mResourcesManager")
    final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>();
    @GuardedBy("mResourcesManager")
    Configuration mPendingConfiguration = null;
    // Because we merge activity relaunch operations we can't depend on the ordering provided by
    // the handler messages. We need to introduce secondary ordering mechanism, which will allow
@@ -903,6 +904,10 @@ public final class ActivityThread {
            sendMessage(H.CONFIGURATION_CHANGED, config);
        }

        public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
            sendMessage(H.APPLICATION_INFO_CHANGED, ai);
        }

        public void updateTimeZone() {
            TimeZone.setDefault(null);
        }
@@ -1447,6 +1452,7 @@ public final class ActivityThread {
        public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
        public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
        public static final int ATTACH_AGENT = 155;
        public static final int APPLICATION_INFO_CHANGED = 156;

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
@@ -1504,6 +1510,7 @@ public final class ActivityThread {
                    case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
                    case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
                    case ATTACH_AGENT: return "ATTACH_AGENT";
                    case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
                }
            }
            return Integer.toString(code);
@@ -1635,8 +1642,11 @@ public final class ActivityThread {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
                    mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
                    mUpdatingSystemConfig = true;
                    try {
                        handleConfigurationChanged((Configuration) msg.obj, null);
                    } finally {
                        mUpdatingSystemConfig = false;
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case CLEAN_UP_CONTEXT:
@@ -1762,6 +1772,14 @@ public final class ActivityThread {
                case ATTACH_AGENT:
                    handleAttachAgent((String) msg.obj);
                    break;
                case APPLICATION_INFO_CHANGED:
                    mUpdatingSystemConfig = true;
                    try {
                        handleApplicationInfoChanged((ApplicationInfo) msg.obj);
                    } finally {
                        mUpdatingSystemConfig = false;
                    }
                    break;
            }
            Object obj = msg.obj;
            if (obj instanceof SomeArgs) {
@@ -3499,10 +3517,11 @@ public final class ActivityThread {
                }
                r.activity.performResume();

                synchronized (mResourcesManager) {
                    // If there is a pending local relaunch that was requested when the activity was
                    // paused, it will put the activity into paused state when it finally happens.
                // Since the activity resumed before being relaunched, we don't want that to happen,
                // so we need to clear the request to relaunch paused.
                    // Since the activity resumed before being relaunched, we don't want that to
                    // happen, so we need to clear the request to relaunch paused.
                    for (int i = mRelaunchingActivities.size() - 1; i >= 0; i--) {
                        final ActivityClientRecord relaunching = mRelaunchingActivities.get(i);
                        if (relaunching.token == r.token
@@ -3510,6 +3529,7 @@ public final class ActivityThread {
                            relaunching.startsNotResumed = false;
                        }
                    }
                }

                EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED, UserHandle.myUserId(),
                        r.activity.getComponentName().getClassName(), reason);
@@ -4897,6 +4917,44 @@ public final class ActivityThread {
        }
    }

    void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
        synchronized (mResourcesManager) {
            // Update all affected loaded packages with new package information
            WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
            LoadedApk apk = ref != null ? ref.get() : null;
            if (apk != null) {
                apk.updateApplicationInfo(ai, null);
            }

            ref = mResourcePackages.get(ai.packageName);
            apk = ref != null ? ref.get() : null;
            if (apk != null) {
                apk.updateApplicationInfo(ai, null);
            }

            // Update all affected Resources objects to use new ResourcesImpl
            mResourcesManager.applyNewResourceDirsLocked(ai.sourceDir, ai.resourceDirs);
        }

        ApplicationPackageManager.configurationChanged();

        // Trigger a regular Configuration change event, only with a different assetsSeq number
        // so that we actually call through to all components.
        Configuration newConfig = new Configuration();
        newConfig.unset();
        newConfig.assetsSeq = mConfiguration.assetsSeq + 1;
        handleConfigurationChanged(newConfig, null);

        // Schedule all activities to reload
        for (final Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
            final Activity activity = entry.getValue().activity;
            if (!activity.mFinished) {
                requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false,
                        false);
            }
        }
    }

    static void freeTextLayoutCachesIfNeeded(int configDiff) {
        if (configDiff != 0) {
            // Ask text layout engine to free its caches if there is a locale change
+2 −0
Original line number Diff line number Diff line
@@ -602,6 +602,8 @@ interface IActivityManager {
     */
    ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);

    void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);

    // WARNING: when these transactions are updated, check if they are any callers on the native
    // side. If so, make sure they are using the correct transaction ids and arguments.
    // If a transaction which will also be used on the native side is being inserted, add it
+1 −0
Original line number Diff line number Diff line
@@ -152,4 +152,5 @@ oneway interface IApplicationThread {
            IVoiceInteractor voiceInteractor);
    void handleTrustStorageUpdate();
    void attachAgent(String path);
    void scheduleApplicationInfoChanged(in ApplicationInfo ai);
}
+67 −26
Original line number Diff line number Diff line
@@ -916,6 +916,45 @@ public class ResourcesManager {
                }
            }

            redirectResourcesToNewImplLocked(updatedResourceKeys);
        }
    }

    // TODO(adamlesinski): Make this accept more than just overlay directories.
    final void applyNewResourceDirsLocked(@NonNull final String baseCodePath,
            @NonNull final String[] newResourceDirs) {
        try {
            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                    "ResourcesManager#applyNewResourceDirsLocked");

            final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
            final int implCount = mResourceImpls.size();
            for (int i = 0; i < implCount; i++) {
                final ResourcesKey key = mResourceImpls.keyAt(i);
                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
                if (impl != null && key.mResDir != null && key.mResDir.equals(baseCodePath)) {
                    updatedResourceKeys.put(impl, new ResourcesKey(
                            key.mResDir,
                            key.mSplitResDirs,
                            newResourceDirs,
                            key.mLibDirs,
                            key.mDisplayId,
                            key.mOverrideConfiguration,
                            key.mCompatInfo));
                }
            }

            invalidatePath("/");

            redirectResourcesToNewImplLocked(updatedResourceKeys);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }
    }

    private void redirectResourcesToNewImplLocked(
            @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
        // Bail early if there is no work to do.
        if (updatedResourceKeys.isEmpty()) {
            return;
@@ -924,13 +963,14 @@ public class ResourcesManager {
        // Update any references to ResourcesImpl that require reloading.
        final int resourcesCount = mResourceReferences.size();
        for (int i = 0; i < resourcesCount; i++) {
                final Resources r = mResourceReferences.get(i).get();
            final WeakReference<Resources> ref = mResourceReferences.get(i);
            final Resources r = ref != null ? ref.get() : null;
            if (r != null) {
                final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
                if (key != null) {
                    final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
                    if (impl == null) {
                            throw new Resources.NotFoundException("failed to load " + libAsset);
                        throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
                    }
                    r.setImpl(impl);
                }
@@ -941,13 +981,15 @@ public class ResourcesManager {
        for (ActivityResources activityResources : mActivityResourceReferences.values()) {
            final int resCount = activityResources.activityResources.size();
            for (int i = 0; i < resCount; i++) {
                    final Resources r = activityResources.activityResources.get(i).get();
                final WeakReference<Resources> ref = activityResources.activityResources.get(i);
                final Resources r = ref != null ? ref.get() : null;
                if (r != null) {
                    final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
                    if (key != null) {
                        final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
                        if (impl == null) {
                                throw new Resources.NotFoundException("failed to load " + libAsset);
                            throw new Resources.NotFoundException(
                                    "failed to redirect ResourcesImpl");
                        }
                        r.setImpl(impl);
                    }
@@ -956,4 +998,3 @@ public class ResourcesManager {
        }
    }
}
}
+8 −0
Original line number Diff line number Diff line
@@ -669,6 +669,14 @@ public class ActivityInfo extends ComponentInfo
     * {@link android.R.attr#configChanges} attribute.
     */
    public static final int CONFIG_LAYOUT_DIRECTION = 0x2000;
    /**
     * Bit in {@link #configChanges} that indicates that the activity
     * can itself handle asset path changes.  Set from the {@link android.R.attr#configChanges}
     * attribute. This is not a core resource configuration, but a higher-level value, so its
     * constant starts at the high bits.
     * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs.
     */
    public static final int CONFIG_ASSETS_PATHS = 0x80000000;
    /**
     * Bit in {@link #configChanges} that indicates that the activity
     * can itself handle changes to the font scaling factor.  Set from the
Loading