| /* |
| * Copyright (C) 2021 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.compat.overrides; |
| |
| import static android.content.Intent.ACTION_PACKAGE_ADDED; |
| import static android.content.Intent.ACTION_PACKAGE_CHANGED; |
| import static android.content.Intent.ACTION_PACKAGE_REMOVED; |
| import static android.content.pm.PackageManager.MATCH_ANY_USER; |
| import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES; |
| |
| import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS; |
| import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES; |
| |
| import static java.util.Collections.emptyMap; |
| import static java.util.Collections.emptySet; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.compat.PackageOverride; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.provider.DeviceConfig; |
| import android.provider.DeviceConfig.Properties; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.compat.CompatibilityOverrideConfig; |
| import com.android.internal.compat.CompatibilityOverridesByPackageConfig; |
| import com.android.internal.compat.CompatibilityOverridesToRemoveByPackageConfig; |
| import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; |
| import com.android.internal.compat.IPlatformCompat; |
| import com.android.server.SystemService; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Service for applying per-app compat overrides delivered via Device Config. |
| * |
| * <p>The service listens both on changes to supported Device Config namespaces and on package |
| * added/changed/removed events, and applies overrides accordingly. |
| * |
| * @hide |
| */ |
| public final class AppCompatOverridesService { |
| private static final String TAG = "AppCompatOverridesService"; |
| |
| private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList( |
| NAMESPACE_APP_COMPAT_OVERRIDES); |
| |
| private final Context mContext; |
| private final PackageManager mPackageManager; |
| private final IPlatformCompat mPlatformCompat; |
| private final List<String> mSupportedNamespaces; |
| private final AppCompatOverridesParser mOverridesParser; |
| private final PackageReceiver mPackageReceiver; |
| private final List<DeviceConfigListener> mDeviceConfigListeners; |
| |
| private AppCompatOverridesService(Context context) { |
| this(context, IPlatformCompat.Stub.asInterface( |
| ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)), SUPPORTED_NAMESPACES); |
| } |
| |
| @VisibleForTesting |
| AppCompatOverridesService(Context context, IPlatformCompat platformCompat, |
| List<String> supportedNamespaces) { |
| mContext = context; |
| mPackageManager = mContext.getPackageManager(); |
| mPlatformCompat = platformCompat; |
| mSupportedNamespaces = supportedNamespaces; |
| mOverridesParser = new AppCompatOverridesParser(mPackageManager); |
| mPackageReceiver = new PackageReceiver(mContext); |
| mDeviceConfigListeners = new ArrayList<>(); |
| for (String namespace : mSupportedNamespaces) { |
| mDeviceConfigListeners.add(new DeviceConfigListener(mContext, namespace)); |
| } |
| } |
| |
| @Override |
| public void finalize() { |
| unregisterDeviceConfigListeners(); |
| unregisterPackageReceiver(); |
| } |
| |
| @VisibleForTesting |
| void registerDeviceConfigListeners() { |
| for (DeviceConfigListener listener : mDeviceConfigListeners) { |
| listener.register(); |
| } |
| } |
| |
| private void unregisterDeviceConfigListeners() { |
| for (DeviceConfigListener listener : mDeviceConfigListeners) { |
| listener.unregister(); |
| } |
| } |
| |
| @VisibleForTesting |
| void registerPackageReceiver() { |
| mPackageReceiver.register(); |
| } |
| |
| private void unregisterPackageReceiver() { |
| mPackageReceiver.unregister(); |
| } |
| |
| /** |
| * Same as {@link #applyOverrides(Properties, Set, Map)} except all properties of the given |
| * {@code namespace} are fetched via {@link DeviceConfig#getProperties}. |
| */ |
| private void applyAllOverrides(String namespace, Set<Long> ownedChangeIds, |
| Map<String, Set<Long>> packageToChangeIdsToSkip) { |
| applyOverrides(DeviceConfig.getProperties(namespace), ownedChangeIds, |
| packageToChangeIdsToSkip); |
| } |
| |
| /** |
| * Iterates all package override flags in the given {@code properties}, and for each flag whose |
| * package is installed on the device, parses its value and adds the overrides in it with |
| * respect to the package's current installed version. |
| * |
| * <p>In addition, for each package, removes any override that wasn't just added, whose change |
| * ID is in {@code ownedChangeIds} but not in the respective set in {@code |
| * packageToChangeIdsToSkip}. |
| */ |
| private void applyOverrides(Properties properties, Set<Long> ownedChangeIds, |
| Map<String, Set<Long>> packageToChangeIdsToSkip) { |
| Set<String> packageNames = new ArraySet<>(properties.getKeyset()); |
| packageNames.remove(FLAG_OWNED_CHANGE_IDS); |
| packageNames.remove(FLAG_REMOVE_OVERRIDES); |
| Map<String, CompatibilityOverrideConfig> packageNameToOverridesToAdd = new ArrayMap<>(); |
| Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove = |
| new ArrayMap<>(); |
| for (String packageName : packageNames) { |
| Set<Long> changeIdsToSkip = packageToChangeIdsToSkip.getOrDefault(packageName, |
| emptySet()); |
| |
| Map<Long, PackageOverride> overridesToAdd = emptyMap(); |
| Long versionCode = getVersionCodeOrNull(packageName); |
| if (versionCode != null) { |
| // Only if package installed add overrides, otherwise just remove. |
| overridesToAdd = mOverridesParser.parsePackageOverrides( |
| properties.getString(packageName, /* defaultValue= */ ""), packageName, |
| versionCode, changeIdsToSkip); |
| } |
| if (!overridesToAdd.isEmpty()) { |
| packageNameToOverridesToAdd.put(packageName, |
| new CompatibilityOverrideConfig(overridesToAdd)); |
| } |
| |
| Set<Long> overridesToRemove = new ArraySet<>(); |
| for (Long changeId : ownedChangeIds) { |
| if (!overridesToAdd.containsKey(changeId) && !changeIdsToSkip.contains(changeId)) { |
| overridesToRemove.add(changeId); |
| } |
| } |
| if (!overridesToRemove.isEmpty()) { |
| packageNameToOverridesToRemove.put(packageName, |
| new CompatibilityOverridesToRemoveConfig(overridesToRemove)); |
| } |
| } |
| |
| putAllPackageOverrides(packageNameToOverridesToAdd); |
| removeAllPackageOverrides(packageNameToOverridesToRemove); |
| } |
| |
| /** |
| * Adds all overrides in all supported namespaces for the given {@code packageName}. |
| */ |
| private void addAllPackageOverrides(String packageName) { |
| Long versionCode = getVersionCodeOrNull(packageName); |
| if (versionCode == null) { |
| return; |
| } |
| |
| for (String namespace : mSupportedNamespaces) { |
| // We apply overrides for each namespace separately so that if there is a failure for |
| // one namespace, the other namespaces won't be affected. |
| Set<Long> ownedChangeIds = getOwnedChangeIds(namespace); |
| putPackageOverrides(packageName, mOverridesParser.parsePackageOverrides( |
| DeviceConfig.getString(namespace, packageName, /* defaultValue= */""), |
| packageName, versionCode, |
| getOverridesToRemove(namespace, ownedChangeIds).getOrDefault(packageName, |
| emptySet()))); |
| } |
| } |
| |
| /** |
| * Removes all owned overrides in all supported namespaces for the given {@code packageName}. |
| * |
| * <p>If a certain namespace doesn't have a package override flag for the given {@code |
| * packageName}, that namespace is skipped.</p> |
| */ |
| private void removeAllPackageOverrides(String packageName) { |
| for (String namespace : mSupportedNamespaces) { |
| if (DeviceConfig.getString(namespace, packageName, /* defaultValue= */ "").isEmpty()) { |
| // No overrides for this package in this namespace. |
| continue; |
| } |
| // We remove overrides for each namespace separately so that if there is a failure for |
| // one namespace, the other namespaces won't be affected. |
| removePackageOverrides(packageName, getOwnedChangeIds(namespace)); |
| } |
| } |
| |
| /** |
| * Calls {@link IPlatformCompat#removeAllOverridesOnReleaseBuilds} on {@code |
| * packageNameToOverridesToRemove}. |
| */ |
| private void removeOverrides(Map<String, Set<Long>> packageNameToOverridesToRemove) { |
| Map<String, CompatibilityOverridesToRemoveConfig> packageNameToConfig = |
| new ArrayMap<>(); |
| for (Map.Entry<String, Set<Long>> packageNameAndChangeIds : |
| packageNameToOverridesToRemove.entrySet()) { |
| packageNameToConfig.put(packageNameAndChangeIds.getKey(), |
| new CompatibilityOverridesToRemoveConfig(packageNameAndChangeIds.getValue())); |
| } |
| removeAllPackageOverrides(packageNameToConfig); |
| } |
| |
| /** |
| * Fetches the value of {@link AppCompatOverridesParser#FLAG_REMOVE_OVERRIDES} for the given |
| * {@code namespace} and parses it into a map from package name to a set of change IDs to |
| * remove for that package. |
| */ |
| private Map<String, Set<Long>> getOverridesToRemove(String namespace, |
| Set<Long> ownedChangeIds) { |
| return mOverridesParser.parseRemoveOverrides( |
| DeviceConfig.getString(namespace, FLAG_REMOVE_OVERRIDES, /* defaultValue= */ ""), |
| ownedChangeIds); |
| } |
| |
| /** |
| * Fetches the value of {@link AppCompatOverridesParser#FLAG_OWNED_CHANGE_IDS} for the given |
| * {@code namespace} and parses it into a set of change IDs. |
| */ |
| private static Set<Long> getOwnedChangeIds(String namespace) { |
| return AppCompatOverridesParser.parseOwnedChangeIds( |
| DeviceConfig.getString(namespace, FLAG_OWNED_CHANGE_IDS, /* defaultValue= */ "")); |
| } |
| |
| private void putAllPackageOverrides( |
| Map<String, CompatibilityOverrideConfig> packageNameToOverrides) { |
| if (packageNameToOverrides.isEmpty()) { |
| return; |
| } |
| CompatibilityOverridesByPackageConfig config = new CompatibilityOverridesByPackageConfig( |
| packageNameToOverrides); |
| try { |
| mPlatformCompat.putAllOverridesOnReleaseBuilds(config); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to call IPlatformCompat#putAllOverridesOnReleaseBuilds", e); |
| } |
| } |
| |
| private void putPackageOverrides(String packageName, |
| Map<Long, PackageOverride> overridesToAdd) { |
| if (overridesToAdd.isEmpty()) { |
| return; |
| } |
| CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overridesToAdd); |
| try { |
| mPlatformCompat.putOverridesOnReleaseBuilds(config, packageName); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e); |
| } |
| } |
| |
| private void removeAllPackageOverrides( |
| Map<String, CompatibilityOverridesToRemoveConfig> packageNameToOverridesToRemove) { |
| if (packageNameToOverridesToRemove.isEmpty()) { |
| return; |
| } |
| CompatibilityOverridesToRemoveByPackageConfig config = |
| new CompatibilityOverridesToRemoveByPackageConfig(packageNameToOverridesToRemove); |
| try { |
| mPlatformCompat.removeAllOverridesOnReleaseBuilds(config); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to call IPlatformCompat#removeAllOverridesOnReleaseBuilds", e); |
| } |
| } |
| |
| private void removePackageOverrides(String packageName, Set<Long> overridesToRemove) { |
| if (overridesToRemove.isEmpty()) { |
| return; |
| } |
| CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig( |
| overridesToRemove); |
| try { |
| mPlatformCompat.removeOverridesOnReleaseBuilds(config, packageName); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Failed to call IPlatformCompat#removeOverridesOnReleaseBuilds", e); |
| } |
| } |
| |
| private boolean isInstalledForAnyUser(String packageName) { |
| return getVersionCodeOrNull(packageName) != null; |
| } |
| |
| @Nullable |
| private Long getVersionCodeOrNull(String packageName) { |
| try { |
| ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName, |
| MATCH_ANY_USER); |
| return applicationInfo.longVersionCode; |
| } catch (PackageManager.NameNotFoundException e) { |
| // Package isn't installed for any user. |
| return null; |
| } |
| } |
| |
| /** |
| * SystemService lifecycle for AppCompatOverridesService. |
| * |
| * @hide |
| */ |
| public static final class Lifecycle extends SystemService { |
| private AppCompatOverridesService mService; |
| |
| public Lifecycle(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public void onStart() { |
| mService = new AppCompatOverridesService(getContext()); |
| mService.registerDeviceConfigListeners(); |
| mService.registerPackageReceiver(); |
| } |
| } |
| |
| /** |
| * A {@link DeviceConfig.OnPropertiesChangedListener} that listens on changes to a given |
| * namespace and adds/removes overrides according to the changed flags. |
| */ |
| private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { |
| private final Context mContext; |
| private final String mNamespace; |
| |
| private DeviceConfigListener(Context context, String namespace) { |
| mContext = context; |
| mNamespace = namespace; |
| } |
| |
| private void register() { |
| DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(), |
| this); |
| } |
| |
| private void unregister() { |
| DeviceConfig.removeOnPropertiesChangedListener(this); |
| } |
| |
| @Override |
| public void onPropertiesChanged(Properties properties) { |
| boolean removeOverridesFlagChanged = properties.getKeyset().contains( |
| FLAG_REMOVE_OVERRIDES); |
| boolean ownedChangedIdsFlagChanged = properties.getKeyset().contains( |
| FLAG_OWNED_CHANGE_IDS); |
| |
| Set<Long> ownedChangeIds = getOwnedChangeIds(mNamespace); |
| Map<String, Set<Long>> overridesToRemove = getOverridesToRemove(mNamespace, |
| ownedChangeIds); |
| if (removeOverridesFlagChanged || ownedChangedIdsFlagChanged) { |
| // In both cases it's possible that overrides that weren't removed before should |
| // now be removed. |
| removeOverrides(overridesToRemove); |
| } |
| |
| if (removeOverridesFlagChanged) { |
| // We need to re-apply all overrides in the namespace since the remove overrides |
| // flag might have blocked some of them from being applied before. |
| applyAllOverrides(mNamespace, ownedChangeIds, overridesToRemove); |
| } else { |
| applyOverrides(properties, ownedChangeIds, overridesToRemove); |
| } |
| } |
| } |
| |
| /** |
| * A {@link BroadcastReceiver} that listens on package added/changed/removed events and |
| * adds/removes overrides according to the corresponding Device Config flags. |
| */ |
| private final class PackageReceiver extends BroadcastReceiver { |
| private final Context mContext; |
| private final IntentFilter mIntentFilter; |
| |
| private PackageReceiver(Context context) { |
| mContext = context; |
| mIntentFilter = new IntentFilter(); |
| mIntentFilter.addAction(ACTION_PACKAGE_ADDED); |
| mIntentFilter.addAction(ACTION_PACKAGE_CHANGED); |
| mIntentFilter.addAction(ACTION_PACKAGE_REMOVED); |
| mIntentFilter.addDataScheme("package"); |
| } |
| |
| private void register() { |
| mContext.registerReceiverForAllUsers(this, mIntentFilter, /* broadcastPermission= */ |
| null, /* scheduler= */ null); |
| } |
| |
| private void unregister() { |
| mContext.unregisterReceiver(this); |
| } |
| |
| @Override |
| public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { |
| Uri data = intent.getData(); |
| if (data == null) { |
| Slog.w(TAG, "Failed to get package name in package receiver"); |
| return; |
| } |
| String packageName = data.getSchemeSpecificPart(); |
| String action = intent.getAction(); |
| if (action == null) { |
| Slog.w(TAG, "Failed to get action in package receiver"); |
| return; |
| } |
| switch (action) { |
| case ACTION_PACKAGE_ADDED: |
| case ACTION_PACKAGE_CHANGED: |
| addAllPackageOverrides(packageName); |
| break; |
| case ACTION_PACKAGE_REMOVED: |
| if (!isInstalledForAnyUser(packageName)) { |
| removeAllPackageOverrides(packageName); |
| } |
| break; |
| default: |
| Slog.w(TAG, "Unsupported action in package receiver: " + action); |
| break; |
| } |
| } |
| } |
| } |