All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.ambientcontext.AmbientContextManagerPerUserService Maven / Gradle / Ivy

/*
 * 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.ambientcontext;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.service.ambientcontext.AmbientContextDetectionResult;
import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.infra.AbstractPerUserSystemService;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * Per-user manager service for {@link AmbientContextEvent}s.
 */
final class AmbientContextManagerPerUserService extends
        AbstractPerUserSystemService {
    private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName();

    @Nullable
    @VisibleForTesting
    RemoteAmbientContextDetectionService mRemoteService;

    private ComponentName mComponentName;

    AmbientContextManagerPerUserService(
            @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
        super(master, lock, userId);
    }

    void destroyLocked() {
        Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
        if (mRemoteService != null) {
            synchronized (mLock) {
                mRemoteService.unbind();
                mRemoteService = null;
            }
        }
    }

    @GuardedBy("mLock")
    private void ensureRemoteServiceInitiated() {
        if (mRemoteService == null) {
            mRemoteService = new RemoteAmbientContextDetectionService(
                    getContext(), mComponentName, getUserId());
        }
    }

    /**
     * get the currently bound component name.
     */
    @VisibleForTesting
    ComponentName getComponentName() {
        return mComponentName;
    }


    /**
     * Resolves and sets up the service if it had not been done yet. Returns true if the service
     * is available.
     */
    @GuardedBy("mLock")
    @VisibleForTesting
    boolean setUpServiceIfNeeded() {
        if (mComponentName == null) {
            mComponentName = updateServiceInfoLocked();
        }
        if (mComponentName == null) {
            return false;
        }

        ServiceInfo serviceInfo;
        try {
            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
                    mComponentName, 0, mUserId);
        } catch (RemoteException e) {
            Slog.w(TAG, "RemoteException while setting up service");
            return false;
        }
        return serviceInfo != null;
    }

    @Override
    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
            throws PackageManager.NameNotFoundException {
        ServiceInfo serviceInfo;
        try {
            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
                    0, mUserId);
            if (serviceInfo != null) {
                final String permission = serviceInfo.permission;
                if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals(
                        permission)) {
                    throw new SecurityException(String.format(
                            "Service %s requires %s permission. Found %s permission",
                            serviceInfo.getComponentName(),
                            Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE,
                            serviceInfo.permission));
                }
            }
        } catch (RemoteException e) {
            throw new PackageManager.NameNotFoundException(
                    "Could not get service for " + serviceComponent);
        }
        return serviceInfo;
    }

    @Override
    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
        synchronized (super.mLock) {
            super.dumpLocked(prefix, pw);
        }
        if (mRemoteService != null) {
            mRemoteService.dump("", new IndentingPrintWriter(pw, "  "));
        }
    }

    /**
     * Handles client registering as an observer. Only one registration is supported per app
     * package. A new registration from the same package will overwrite the previous registration.
     */
    public void onRegisterObserver(AmbientContextEventRequest request,
            PendingIntent pendingIntent, RemoteCallback clientStatusCallback) {
        synchronized (mLock) {
            if (!setUpServiceIfNeeded()) {
                Slog.w(TAG, "Detection service is not available at this moment.");
                sendStatusCallback(
                        clientStatusCallback,
                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                return;
            }

            // Register package and add to existing ClientRequests cache
            startDetection(request, pendingIntent.getCreatorPackage(),
                    createDetectionResultRemoteCallback(), clientStatusCallback);
            mMaster.newClientAdded(mUserId, request, pendingIntent, clientStatusCallback);
        }
    }

    /**
     * Returns a RemoteCallback that handles the status from the detection service, and
     * sends results to the client callback.
     */
    private RemoteCallback getServerStatusCallback(RemoteCallback clientStatusCallback) {
        return new RemoteCallback(result -> {
            AmbientContextDetectionServiceStatus serviceStatus =
                    (AmbientContextDetectionServiceStatus) result.get(
                            AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
            final long token = Binder.clearCallingIdentity();
            try {
                String packageName = serviceStatus.getPackageName();
                Bundle bundle = new Bundle();
                bundle.putInt(
                        AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
                        serviceStatus.getStatusCode());
                clientStatusCallback.sendResult(bundle);
                int statusCode = serviceStatus.getStatusCode();
                Slog.i(TAG, "Got detection status of " + statusCode
                        + " for " + packageName);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        });
    }

    @VisibleForTesting
    void startDetection(AmbientContextEventRequest request, String callingPackage,
            RemoteCallback detectionResultCallback, RemoteCallback clientStatusCallback) {
        Slog.d(TAG, "Requested detection of " + request.getEventTypes());
        synchronized (mLock) {
            if (setUpServiceIfNeeded()) {
                ensureRemoteServiceInitiated();
                mRemoteService.startDetection(request, callingPackage, detectionResultCallback,
                        getServerStatusCallback(clientStatusCallback));
            } else {
                Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
                sendStatusToCallback(clientStatusCallback,
                        AmbientContextManager.STATUS_NOT_SUPPORTED);
            }
        }
    }

    /**
     * Sends an intent with a status code and empty events.
     */
    void sendStatusCallback(RemoteCallback statusCallback, int statusCode) {
        Bundle bundle = new Bundle();
        bundle.putInt(
                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
                statusCode);
        statusCallback.sendResult(bundle);
    }

    /**
     * Unregisters the client from all previously registered events by removing from the
     * mExistingRequests map, and unregister events from the service if those events are not
     * requested by other apps.
     */
    public void onUnregisterObserver(String callingPackage) {
        synchronized (mLock) {
            stopDetection(callingPackage);
            mMaster.clientRemoved(mUserId, callingPackage);
        }
    }

    public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
            RemoteCallback statusCallback) {
        Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
                + " for " + callingPackage);
        synchronized (mLock) {
            if (!setUpServiceIfNeeded()) {
                Slog.w(TAG, "Detection service is not available at this moment.");
                sendStatusToCallback(statusCallback,
                        AmbientContextManager.STATUS_NOT_SUPPORTED);
                return;
            }
            ensureRemoteServiceInitiated();
            mRemoteService.queryServiceStatus(
                    eventTypes,
                    callingPackage,
                    getServerStatusCallback(statusCallback));
        }
    }

    public void onStartConsentActivity(int[] eventTypes, String callingPackage) {
        Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes)
                + " for " + callingPackage);

        // Look up the recent task from the callingPackage
        ActivityManager.RecentTaskInfo task;
        ParceledListSlice recentTasks;
        int userId = getUserId();
        try {
            recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
                    /*flags*/ 0, userId);
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to query recent tasks!");
            return;
        }

        if ((recentTasks == null) || recentTasks.getList().isEmpty()) {
            Slog.e(TAG, "Recent task list is empty!");
            return;
        }

        task = recentTasks.getList().get(0);
        if (!callingPackage.equals(task.topActivityInfo.packageName)) {
            Slog.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
                    + " doesn't match with client package name: " + callingPackage);
            return;
        }

        // Start activity as the same task from the callingPackage
        ComponentName consentComponent = getConsentComponent();
        if (consentComponent == null) {
            Slog.e(TAG, "Consent component not found!");
            return;
        }

        Slog.d(TAG, "Starting consent activity for " + callingPackage);
        Intent intent = new Intent();
        final long identity = Binder.clearCallingIdentity();
        try {
            Context context = getContext();
            String packageNameExtraKey = context.getResources().getString(
                    com.android.internal.R.string.config_ambientContextPackageNameExtraKey);
            String eventArrayExtraKey = context.getResources().getString(
                    com.android.internal.R.string.config_ambientContextEventArrayExtraKey);

            // Create consent activity intent with the calling package name and requested events
            intent.setComponent(consentComponent);
            if (packageNameExtraKey != null) {
                intent.putExtra(packageNameExtraKey, callingPackage);
            } else {
                Slog.d(TAG, "Missing packageNameExtraKey for consent activity");
            }
            if (eventArrayExtraKey != null) {
                intent.putExtra(eventArrayExtraKey, eventTypes);
            } else {
                Slog.d(TAG, "Missing eventArrayExtraKey for consent activity");
            }

            // Set parent to the calling app's task
            ActivityOptions options = ActivityOptions.makeBasic();
            options.setLaunchTaskId(task.taskId);
            context.startActivityAsUser(intent, options.toBundle(), context.getUser());
        } catch (ActivityNotFoundException e) {
            Slog.e(TAG, "unable to start consent activity");
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * Returns the consent activity component from config lookup.
     */
    private ComponentName getConsentComponent() {
        Context context = getContext();
        String consentComponent = context.getResources().getString(
                    com.android.internal.R.string.config_defaultAmbientContextConsentComponent);
        if (TextUtils.isEmpty(consentComponent)) {
            return null;
        }
        Slog.i(TAG, "Consent component name: " + consentComponent);
        return ComponentName.unflattenFromString(consentComponent);
    }

    /**
     * Sends the result response with the specified status to the callback.
     */
    void sendStatusToCallback(RemoteCallback callback,
                    @AmbientContextManager.StatusCode int status) {
        Bundle bundle = new Bundle();
        bundle.putInt(
                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
                status);
        callback.sendResult(bundle);
    }

    @VisibleForTesting
    void stopDetection(String packageName) {
        Slog.d(TAG, "Stop detection for " + packageName);
        synchronized (mLock) {
            if (mComponentName != null) {
                ensureRemoteServiceInitiated();
                mRemoteService.stopDetection(packageName);
            }
        }
    }

    /**
     * Sends out the Intent to the client after the event is detected.
     *
     * @param pendingIntent Client's PendingIntent for callback
     * @param result result from the detection service
     */
    private void sendDetectionResultIntent(PendingIntent pendingIntent,
            AmbientContextDetectionResult result) {
        Intent intent = new Intent();
        intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
                new ArrayList(result.getEvents()));
        // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing
        // the PendingIntent as a backdoor to do this.
        BroadcastOptions options = BroadcastOptions.makeBasic();
        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
        try {
            pendingIntent.send(getContext(), 0, intent, null, null, null,
                    options.toBundle());
            Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
                    + result);
        } catch (PendingIntent.CanceledException e) {
            Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent);
        }
    }

    @NonNull
    RemoteCallback createDetectionResultRemoteCallback() {
        return new RemoteCallback(result -> {
            AmbientContextDetectionResult detectionResult =
                    (AmbientContextDetectionResult) result.get(
                            AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY);
            String packageName = detectionResult.getPackageName();
            PendingIntent pendingIntent = mMaster.getPendingIntent(mUserId, packageName);
            if (pendingIntent == null) {
                return;
            }

            final long token = Binder.clearCallingIdentity();
            try {
                sendDetectionResultIntent(pendingIntent, detectionResult);
                Slog.i(TAG, "Got detection result of " + detectionResult.getEvents()
                        + " for " + packageName);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy