| /* |
| * Copyright (C) 2019 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; |
| |
| import static android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD; |
| import static android.content.pm.PackageManager.MATCH_ANY_USER; |
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; |
| |
| import static com.android.internal.compat.OverrideAllowedState.ALLOWED; |
| import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION; |
| import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK; |
| import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; |
| import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; |
| import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE; |
| import static com.android.internal.compat.OverrideAllowedState.PLATFORM_TOO_OLD; |
| |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.database.ContentObserver; |
| import android.os.Handler; |
| import android.provider.Settings; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.compat.AndroidBuildClassifier; |
| import com.android.internal.compat.IOverrideValidator; |
| import com.android.internal.compat.OverrideAllowedState; |
| |
| /** |
| * Implementation of the policy for allowing compat change overrides. |
| */ |
| public class OverrideValidatorImpl extends IOverrideValidator.Stub { |
| |
| private AndroidBuildClassifier mAndroidBuildClassifier; |
| private Context mContext; |
| private CompatConfig mCompatConfig; |
| private boolean mForceNonDebuggableFinalBuild; |
| |
| private class SettingsObserver extends ContentObserver { |
| SettingsObserver() { |
| super(new Handler()); |
| } |
| @Override |
| public void onChange(boolean selfChange) { |
| mForceNonDebuggableFinalBuild = Settings.Global.getInt( |
| mContext.getContentResolver(), |
| Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT, |
| 0) == 1; |
| } |
| } |
| |
| @VisibleForTesting |
| OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier, |
| Context context, CompatConfig config) { |
| mAndroidBuildClassifier = androidBuildClassifier; |
| mContext = context; |
| mCompatConfig = config; |
| mForceNonDebuggableFinalBuild = false; |
| } |
| |
| /** |
| * Check the allowed state for the given changeId and packageName on a recheck. |
| * |
| * <p>Recheck happens when the given app is getting updated. In this case we cannot do a |
| * permission check on the caller, so we're using the fact that the override was present as |
| * proof that the original caller was allowed to set this override. |
| */ |
| OverrideAllowedState getOverrideAllowedStateForRecheck(long changeId, |
| @NonNull String packageName) { |
| return getOverrideAllowedStateInternal(changeId, packageName, true); |
| } |
| |
| @Override |
| public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) { |
| return getOverrideAllowedStateInternal(changeId, packageName, false); |
| } |
| |
| private OverrideAllowedState getOverrideAllowedStateInternal(long changeId, String packageName, |
| boolean isRecheck) { |
| if (mCompatConfig.isLoggingOnly(changeId)) { |
| return new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1); |
| } |
| |
| boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild() |
| && !mForceNonDebuggableFinalBuild; |
| boolean finalBuild = mAndroidBuildClassifier.isFinalBuild() |
| || mForceNonDebuggableFinalBuild; |
| int maxTargetSdk = mCompatConfig.maxTargetSdkForChangeIdOptIn(changeId); |
| boolean disabled = mCompatConfig.isDisabled(changeId); |
| |
| // Allow any override for userdebug or eng builds. |
| if (debuggableBuild) { |
| return new OverrideAllowedState(ALLOWED, -1, -1); |
| } |
| if (maxTargetSdk >= mAndroidBuildClassifier.platformTargetSdk()) { |
| return new OverrideAllowedState(PLATFORM_TOO_OLD, -1, maxTargetSdk); |
| } |
| PackageManager packageManager = mContext.getPackageManager(); |
| if (packageManager == null) { |
| throw new IllegalStateException("No PackageManager!"); |
| } |
| ApplicationInfo applicationInfo; |
| try { |
| applicationInfo = packageManager.getApplicationInfo(packageName, MATCH_ANY_USER); |
| } catch (NameNotFoundException e) { |
| return new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1); |
| } |
| // If the change is annotated as @Overridable, apps with the specific permission can |
| // set the override even on production builds. When rechecking the override, e.g. during an |
| // app update we can bypass this check, as it wouldn't have been here in the first place. |
| if (mCompatConfig.isOverridable(changeId) |
| && (isRecheck |
| || mContext.checkCallingOrSelfPermission( |
| OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) |
| == PERMISSION_GRANTED)) { |
| return new OverrideAllowedState(ALLOWED, -1, -1); |
| } |
| int appTargetSdk = applicationInfo.targetSdkVersion; |
| // Only allow overriding debuggable apps. |
| if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { |
| return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1); |
| } |
| // Allow overriding any change for debuggable apps on non-final builds. |
| if (!finalBuild) { |
| return new OverrideAllowedState(ALLOWED, appTargetSdk, maxTargetSdk); |
| } |
| // Do not allow overriding default enabled changes on user builds |
| if (maxTargetSdk == -1 && !disabled) { |
| return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, maxTargetSdk); |
| } |
| // Only allow to opt-in for a targetSdk gated change. |
| if (disabled || appTargetSdk <= maxTargetSdk) { |
| return new OverrideAllowedState(ALLOWED, appTargetSdk, maxTargetSdk); |
| } |
| return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, maxTargetSdk); |
| } |
| |
| void registerContentObserver() { |
| mContext.getContentResolver().registerContentObserver( |
| Settings.Global.getUriFor( |
| Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT), |
| false, |
| new SettingsObserver()); |
| } |
| |
| void forceNonDebuggableFinalForTest(boolean value) { |
| mForceNonDebuggableFinalBuild = value; |
| } |
| } |