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

Commit 8f4f0d7e authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Cache ServiceInfo objects."

parents c769dda5 30188be6
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -101,10 +101,15 @@ public abstract class JobScheduler {
     * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
     * this API if calls are made too frequently in a short amount of time.
     *
     * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
     * job.
     *
     * @param job The job you wish scheduled. See
     * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
     * you can schedule.
     * @return the result of the schedule request.
     * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
     * disabled.
     */
    public abstract @Result int schedule(@NonNull JobInfo job);

@@ -137,11 +142,21 @@ public abstract class JobScheduler {
     * work you are enqueue, since currently this will always be treated as a different JobInfo,
     * even if the ClipData contents are exactly the same.</p>
     *
     * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
     * rescheduling the same job and the job didn't execute, especially on platform versions before
     * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
     * this API if calls are made too frequently in a short amount of time.
     *
     * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
     * job.
     *
     * @param job The job you wish to enqueue work for. See
     * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs
     * you can schedule.
     * @param work New work to enqueue.  This will be available later when the job starts running.
     * @return the result of the enqueue request.
     * @throws IllegalArgumentException if the specified {@link JobService} doesn't exist or is
     * disabled.
     */
    public abstract @Result int enqueue(@NonNull JobInfo job, @NonNull JobWorkItem work);

+5 −13
Original line number Diff line number Diff line
@@ -87,11 +87,11 @@ import com.android.server.AppStateTrackerImpl;
import com.android.server.DeviceIdleInternal;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.SystemService.TargetUser;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
import com.android.server.job.controllers.BatteryController;
import com.android.server.job.controllers.ComponentController;
import com.android.server.job.controllers.ConnectivityController;
import com.android.server.job.controllers.ContentObserverController;
import com.android.server.job.controllers.DeviceIdleJobsController;
@@ -1484,6 +1484,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(mDeviceIdleJobsController);
        mQuotaController = new QuotaController(this);
        mControllers.add(mQuotaController);
        mControllers.add(new ComponentController(this));

        mRestrictiveControllers = new ArrayList<>();
        mRestrictiveControllers.add(mBatteryController);
@@ -2300,21 +2301,12 @@ public class JobSchedulerService extends com.android.server.SystemService
            return false;
        }

        // The expensive check: validate that the defined package+service is
        // still present & viable.
        // Validate that the defined package+service is still present & viable.
        return isComponentUsable(job);
    }

    private boolean isComponentUsable(@NonNull JobStatus job) {
        final ServiceInfo service;
        try {
            // TODO: cache result until we're notified that something in the package changed.
            service = AppGlobals.getPackageManager().getServiceInfo(
                    job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    job.getUserId());
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
        final ServiceInfo service = job.serviceInfo;

        if (service == null) {
            if (DEBUG) {
@@ -3104,7 +3096,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                try {
                    componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
                            js.getServiceComponent(),
                            PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                            PackageManager.MATCH_DIRECT_BOOT_AUTO,
                            js.getUserId()) != null);
                } catch (RemoteException e) {
                }
+192 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.job.controllers;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;

import com.android.server.job.JobSchedulerService;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Controller that tracks changes in the service component's enabled state.
 */
public class ComponentController extends StateController {
    private static final String TAG = "JobScheduler.Component";
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (action == null) {
                Slog.wtf(TAG, "Intent action was null");
                return;
            }
            switch (action) {
                case Intent.ACTION_PACKAGE_CHANGED:
                    final Uri uri = intent.getData();
                    final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
                    final String[] changedComponents = intent.getStringArrayExtra(
                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
                    if (pkg != null && changedComponents != null && changedComponents.length > 0) {
                        updateComponentStateForPackage(pkg);
                    }
                    break;
                case Intent.ACTION_USER_UNLOCKED:
                case Intent.ACTION_USER_STOPPED:
                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                    updateComponentStateForUser(userId);
                    break;
            }
        }
    };

    private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
            new ComponentStateUpdateFunctor();

    public ComponentController(JobSchedulerService service) {
        super(service);

        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        mContext.registerReceiverAsUser(
                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
        final IntentFilter userFilter = new IntentFilter();
        userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
        userFilter.addAction(Intent.ACTION_USER_STOPPED);
        mContext.registerReceiverAsUser(
                mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);

    }

    @Override
    public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
        updateComponentEnabledStateLocked(jobStatus, null);
    }

    @Override
    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate) {

    }

    @Nullable
    private ServiceInfo getServiceInfo(JobStatus jobStatus,
            @Nullable SparseArrayMap<ComponentName, ServiceInfo> cache) {
        final ComponentName cn = jobStatus.getServiceComponent();
        ServiceInfo si = null;
        if (cache != null) {
            si = cache.get(jobStatus.getUserId(), cn);
        }
        if (si == null) {
            try {
                si = AppGlobals.getPackageManager().getServiceInfo(
                        cn, PackageManager.MATCH_DIRECT_BOOT_AUTO, jobStatus.getUserId());
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
            if (cache != null) {
                cache.add(jobStatus.getUserId(), cn, si);
            }
        }
        return si;
    }

    private boolean updateComponentEnabledStateLocked(JobStatus jobStatus,
            @Nullable SparseArrayMap<ComponentName, ServiceInfo> cache) {
        final ServiceInfo service = getServiceInfo(jobStatus, cache);

        if (DEBUG && service == null) {
            Slog.v(TAG, jobStatus.toShortString() + " component not present");
        }
        final ServiceInfo ogService = jobStatus.serviceInfo;
        jobStatus.serviceInfo = service;
        return !Objects.equals(ogService, service);
    }

    private void updateComponentStateForPackage(final String pkg) {
        updateComponentStates(
                jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
    }

    private void updateComponentStateForUser(final int userId) {
        updateComponentStates(jobStatus -> {
            // Using user ID instead of source user ID because the service will run under the
            // user ID, not source user ID.
            return jobStatus.getUserId() == userId;
        });
    }

    private void updateComponentStates(@NonNull Predicate<JobStatus> filter) {
        synchronized (mLock) {
            mComponentStateUpdateFunctor.reset();
            mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
            if (mComponentStateUpdateFunctor.mChanged) {
                mStateChangedListener.onControllerStateChanged();
            }
        }
    }

    final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
        boolean mChanged;
        final SparseArrayMap<ComponentName, ServiceInfo> mTempCache = new SparseArrayMap<>();

        @Override
        public void accept(JobStatus jobStatus) {
            mChanged |= updateComponentEnabledStateLocked(jobStatus, mTempCache);
        }

        private void reset() {
            mChanged = false;
            mTempCache.clear();
        }
    }

    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {

    }

    @Override
    public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
            Predicate<JobStatus> predicate) {

    }
}
+7 −2
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.app.job.JobInfo;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
@@ -296,6 +297,7 @@ public final class JobStatus {
    public ArraySet<Uri> changedUris;
    public ArraySet<String> changedAuthorities;
    public Network network;
    public ServiceInfo serviceInfo;

    public int lastEvaluatedPriority;

@@ -1284,8 +1286,8 @@ public final class JobStatus {
        // run if its constraints are satisfied).
        // DeviceNotDozing implicit constraint must be satisfied
        // NotRestrictedInBackground implicit constraint must be satisfied
        return mReadyNotDozing && mReadyNotRestrictedInBg && (mReadyDeadlineSatisfied
                || isConstraintsSatisfied(satisfiedConstraints));
        return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
                && (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
    }

    /** All constraints besides implicit and deadline. */
@@ -1767,6 +1769,9 @@ public final class JobStatus {
        pw.print(prefix);
        pw.print("  readyDynamicSatisfied: ");
        pw.println(mReadyDynamicSatisfied);
        pw.print(prefix);
        pw.print("  readyComponentEnabled: ");
        pw.println(serviceInfo != null);

        if (changedAuthorities != null) {
            pw.print(prefix); pw.println("Changed authorities:");
+4 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
@@ -685,6 +686,8 @@ public class JobStatusTest {
    }

    private static JobStatus createJobStatus(JobInfo job) {
        return JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
        JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
        jobStatus.serviceInfo = mock(ServiceInfo.class);
        return jobStatus;
    }
}
Loading