blob: 4a7258958f196dfe908f9abc052be609cd10bfb9 [file] [log] [blame]
/*
* Copyright (C) 2022 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.adservices.service.common;
import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_STATE;
import static android.adservices.common.AdServicesPermissions.ACCESS_ADSERVICES_STATE_COMPAT;
import static android.adservices.common.AdServicesPermissions.MODIFY_ADSERVICES_STATE;
import static android.adservices.common.AdServicesPermissions.MODIFY_ADSERVICES_STATE_COMPAT;
import static android.adservices.common.AdServicesStatusUtils.STATUS_INTERNAL_ERROR;
import static android.adservices.common.AdServicesStatusUtils.STATUS_SUCCESS;
import static android.adservices.common.AdServicesStatusUtils.STATUS_UNAUTHORIZED;
import static com.android.adservices.data.common.AdservicesEntryPointConstant.ADSERVICES_ENTRY_POINT_STATUS_DISABLE;
import static com.android.adservices.data.common.AdservicesEntryPointConstant.ADSERVICES_ENTRY_POINT_STATUS_ENABLE;
import static com.android.adservices.data.common.AdservicesEntryPointConstant.KEY_ADSERVICES_ENTRY_POINT_STATUS;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__AD_SERVICES_ENTRY_POINT_FAILURE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE;
import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX;
import static com.android.adservices.service.ui.constants.DebugMessages.BACK_COMPAT_FEATURE_ENABLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.ENABLE_AD_SERVICES_API_CALLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.ENABLE_AD_SERVICES_API_DISABLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.ENABLE_AD_SERVICES_API_ENABLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.IS_AD_SERVICES_ENABLED_API_CALLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.SET_AD_SERVICES_ENABLED_API_CALLED_MESSAGE;
import static com.android.adservices.service.ui.constants.DebugMessages.UNAUTHORIZED_CALLER_MESSAGE;
import android.adservices.common.AdServicesStates;
import android.adservices.common.EnableAdServicesResponse;
import android.adservices.common.IAdServicesCommonCallback;
import android.adservices.common.IAdServicesCommonService;
import android.adservices.common.IEnableAdServicesCallback;
import android.adservices.common.IsAdServicesEnabledResult;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.RemoteException;
import androidx.annotation.RequiresApi;
import com.android.adservices.LogUtil;
import com.android.adservices.concurrency.AdServicesExecutors;
import com.android.adservices.errorlogging.ErrorLogUtil;
import com.android.adservices.service.Flags;
import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
import com.android.adservices.service.consent.ConsentManager;
import com.android.adservices.service.consent.DeviceRegionProvider;
import com.android.adservices.service.ui.UxEngine;
import com.android.adservices.service.ui.data.UxStatesManager;
import java.util.concurrent.Executor;
/**
* Implementation of {@link IAdServicesCommonService}.
*
* @hide
*/
// TODO(b/269798827): Enable for R.
@RequiresApi(Build.VERSION_CODES.S)
public class AdServicesCommonServiceImpl extends IAdServicesCommonService.Stub {
private static final Executor sBackgroundExecutor = AdServicesExecutors.getBackgroundExecutor();
public final String ADSERVICES_STATUS_SHARED_PREFERENCE = "AdserviceStatusSharedPreference";
private final Context mContext;
private final UxEngine mUxEngine;
private final UxStatesManager mUxStatesManager;
private final Flags mFlags;
public AdServicesCommonServiceImpl(
Context context, Flags flags, UxEngine uxEngine, UxStatesManager uxStatesManager) {
mContext = context;
mFlags = flags;
mUxEngine = uxEngine;
mUxStatesManager = uxStatesManager;
}
@Override
@RequiresPermission(anyOf = {ACCESS_ADSERVICES_STATE, ACCESS_ADSERVICES_STATE_COMPAT})
public void isAdServicesEnabled(@NonNull IAdServicesCommonCallback callback) {
LogUtil.d(IS_AD_SERVICES_ENABLED_API_CALLED_MESSAGE);
boolean hasAccessAdServicesStatePermission =
PermissionHelper.hasAccessAdServicesStatePermission(mContext);
sBackgroundExecutor.execute(
() -> {
try {
if (!hasAccessAdServicesStatePermission) {
LogUtil.e(UNAUTHORIZED_CALLER_MESSAGE);
callback.onFailure(STATUS_UNAUTHORIZED);
return;
}
boolean isAdServicesEnabled = mFlags.getAdServicesEnabled();
if (mFlags.isBackCompatActivityFeatureEnabled()) {
LogUtil.d(BACK_COMPAT_FEATURE_ENABLED_MESSAGE);
isAdServicesEnabled &=
PackageManagerCompatUtils.isAdServicesActivityEnabled(mContext);
}
// TO-DO (b/286664178): remove the block after API is fully ramped up.
if (mFlags.getEnableAdServicesSystemApi()
&& ConsentManager.getInstance(mContext).getUx() != null) {
LogUtil.d(ENABLE_AD_SERVICES_API_ENABLED_MESSAGE);
// PS entry point should be hidden from unenrolled users.
isAdServicesEnabled &= mUxStatesManager.isEnrolledUser(mContext);
} else {
LogUtil.d(ENABLE_AD_SERVICES_API_DISABLED_MESSAGE);
// Reconsent is already handled by the enableAdServices API.
reconsentIfNeededForEU();
}
LogUtil.d("isAdServiceseEnabled: " + isAdServicesEnabled);
callback.onResult(
new IsAdServicesEnabledResult.Builder()
.setAdServicesEnabled(isAdServicesEnabled)
.build());
} catch (Exception e) {
try {
callback.onFailure(STATUS_INTERNAL_ERROR);
} catch (RemoteException re) {
LogUtil.e(re, "Unable to send result to the callback");
ErrorLogUtil.e(
re,
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__API_CALLBACK_ERROR,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX);
}
}
});
}
/**
* Set the adservices entry point Status from UI side, and also check adid zero-out status, and
* Schedule notification if both adservices entry point enabled and adid not opt-out and
* Adservice Is enabled
*/
@Override
@RequiresPermission(anyOf = {MODIFY_ADSERVICES_STATE, MODIFY_ADSERVICES_STATE_COMPAT})
public void setAdServicesEnabled(boolean adServicesEntryPointEnabled, boolean adIdEnabled) {
LogUtil.d(SET_AD_SERVICES_ENABLED_API_CALLED_MESSAGE);
boolean hasModifyAdServicesStatePermission =
PermissionHelper.hasModifyAdServicesStatePermission(mContext);
sBackgroundExecutor.execute(
() -> {
try {
if (!hasModifyAdServicesStatePermission) {
// TODO(b/242578032): handle the security exception in a better way
LogUtil.d(UNAUTHORIZED_CALLER_MESSAGE);
return;
}
SharedPreferences preferences =
mContext.getSharedPreferences(
ADSERVICES_STATUS_SHARED_PREFERENCE, Context.MODE_PRIVATE);
int adServiceEntryPointStatusInt =
adServicesEntryPointEnabled
? ADSERVICES_ENTRY_POINT_STATUS_ENABLE
: ADSERVICES_ENTRY_POINT_STATUS_DISABLE;
SharedPreferences.Editor editor = preferences.edit();
editor.putInt(
KEY_ADSERVICES_ENTRY_POINT_STATUS, adServiceEntryPointStatusInt);
if (!editor.commit()) {
LogUtil.e("saving to the sharedpreference failed");
ErrorLogUtil.e(
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__SHARED_PREF_UPDATE_FAILURE,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX,
this.getClass().getSimpleName(),
new Object() {}.getClass().getEnclosingMethod().getName());
}
LogUtil.d(
"adid status is "
+ adIdEnabled
+ ", adservice status is "
+ mFlags.getAdServicesEnabled());
LogUtil.d("entry point: " + adServicesEntryPointEnabled);
ConsentManager consentManager = ConsentManager.getInstance(mContext);
consentManager.setAdIdEnabled(adIdEnabled);
if (mFlags.getAdServicesEnabled() && adServicesEntryPointEnabled) {
// Check if it is reconsent for ROW.
if (reconsentIfNeededForROW()) {
LogUtil.d("Reconsent for ROW.");
ConsentNotificationJobService.schedule(mContext, adIdEnabled, true);
} else if (getFirstConsentStatus()) {
ConsentNotificationJobService.schedule(
mContext, adIdEnabled, false);
}
if (ConsentManager.getInstance(mContext).getConsent().isGiven()) {
PackageChangedReceiver.enableReceiver(mContext, mFlags);
BackgroundJobsManager.scheduleAllBackgroundJobs(mContext);
}
}
} catch (Exception e) {
LogUtil.e(
"unable to save the adservices entry point status of "
+ e.getMessage());
ErrorLogUtil.e(
AD_SERVICES_ERROR_REPORTED__ERROR_CODE__AD_SERVICES_ENTRY_POINT_FAILURE,
AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__UX,
this.getClass().getSimpleName(),
this.getClass().getEnclosingMethod().getName());
}
});
}
/** Init the AdServices Status Service. */
public void init() {}
/** Check EU device and reconsent logic and schedule the notification if needed. */
public void reconsentIfNeededForEU() {
boolean adserviceEnabled = mFlags.getAdServicesEnabled();
if (adserviceEnabled
&& mFlags.getGaUxFeatureEnabled()
&& DeviceRegionProvider.isEuDevice(mContext, mFlags)) {
// Check if GA UX was notice before
ConsentManager consentManager = ConsentManager.getInstance(mContext);
if (!consentManager.wasGaUxNotificationDisplayed()) {
// Check Beta notification displayed and user opt-in, we will re-consent
SharedPreferences preferences =
mContext.getSharedPreferences(
ADSERVICES_STATUS_SHARED_PREFERENCE, Context.MODE_PRIVATE);
// Check the setAdServicesEnabled was called before
if (preferences.contains(KEY_ADSERVICES_ENTRY_POINT_STATUS)
&& consentManager.getConsent().isGiven()) {
ConsentNotificationJobService.schedule(mContext, false, true);
}
}
}
}
/** Check if user is first time consent */
public boolean getFirstConsentStatus() {
ConsentManager consentManager = ConsentManager.getInstance(mContext);
return (!consentManager.wasGaUxNotificationDisplayed()
&& !consentManager.wasNotificationDisplayed())
|| mFlags.getConsentNotificationDebugMode();
}
/** Check ROW device and see if it fit reconsent */
public boolean reconsentIfNeededForROW() {
ConsentManager consentManager = ConsentManager.getInstance(mContext);
return mFlags.getGaUxFeatureEnabled()
&& !DeviceRegionProvider.isEuDevice(mContext, mFlags)
&& !consentManager.wasGaUxNotificationDisplayed()
&& consentManager.wasNotificationDisplayed()
&& consentManager.getConsent().isGiven();
}
@Override
@RequiresPermission(anyOf = {MODIFY_ADSERVICES_STATE, MODIFY_ADSERVICES_STATE_COMPAT})
public void enableAdServices(
@NonNull AdServicesStates adServicesStates,
@NonNull IEnableAdServicesCallback callback) {
LogUtil.d(ENABLE_AD_SERVICES_API_CALLED_MESSAGE);
boolean authorizedCaller = PermissionHelper.hasModifyAdServicesStatePermission(mContext);
sBackgroundExecutor.execute(
() -> {
try {
if (!authorizedCaller) {
callback.onFailure(STATUS_UNAUTHORIZED);
LogUtil.d(UNAUTHORIZED_CALLER_MESSAGE);
return;
}
// TO-DO (b/286664178): remove the block after API is fully ramped up.
if (!mFlags.getEnableAdServicesSystemApi()) {
callback.onResult(
new EnableAdServicesResponse.Builder()
.setStatusCode(STATUS_SUCCESS)
.setApiEnabled(false)
.setSuccess(false)
.build());
LogUtil.d("enableAdServices(): API is disabled.");
return;
}
mUxEngine.start(adServicesStates);
LogUtil.d("enableAdServices(): UxEngine started.");
callback.onResult(
new EnableAdServicesResponse.Builder()
.setStatusCode(STATUS_SUCCESS)
.setApiEnabled(true)
.setSuccess(true)
.build());
} catch (Exception e) {
LogUtil.e("enableAdServices() failed to complete: " + e.getMessage());
}
});
}
}