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

Commit 8b5d279d authored by Joshua Trask's avatar Joshua Trask
Browse files

Create ResolverComparatorModel interface.

Define the API for interacting with comparator model data;
provide implementations for our two current model types; and
(as a first step) re-write our legacy ResolverComparators
to be implemented internally in terms of their new model types.

This is the first CL in a multi-part cleanup of the
AbstractResolverComparator design. This demonstrates that the
role of an AbstractResolverComparator sub-class amounts to
(i.e., Ctrl+F "@Override") some amount of work to prepare model
data; some cleanup; and a set query methods against data that
*really should be* immutable (separated in this CL as the new
ResolverComparatorModel interace). Any remaining responsibilities
of the abstract base class would be better handled (in a subsequent
CL) by an external controller operating on a ResolverComparatorModel
(i.e., preferring composition to inheritance). The async
model-preparation steps should also be separated and cleaned
up (in a later CL).

I believe this to be a pure refactoring with no observable side
effects. While the new design aids in implementing the correct
"immutable snapshot" style, for now I've written the new
ResolverComparatorModel implementations to preserve any possible
quirks in the legacy implementations. Nevertheless, if some
inadvertant behavior change is introduced as a result of this CL,
it's most likely to be a bug-fix where we previously would've mixed
in stale data. A later CL will intentionally pursue those fixes.

Test: atest ResolverActivityTest ChooserActivityTest
Bug: 227486788
Change-Id: If88bf7a5a6394d81c021782d5d9bce7955f1c0e6
parent 25a98404
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -228,12 +228,6 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
     */
    abstract float getScore(ComponentName name);

    /**
     * Returns the list of top K component names which have highest
     * {@link #getScore(ComponentName)}
     */
    abstract List<ComponentName> getTopComponentNames(int topK);

    /** Handles result message sent to mHandler. */
    abstract void handleResultMessage(Message message);

+101 −50
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.internal.app;

import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;

import android.annotation.Nullable;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
@@ -33,12 +34,11 @@ import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be
@@ -58,7 +58,9 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
    private final String mReferrerPackage;
    // If this is non-null (and this is not destroyed), it means APS is disabled and we should fall
    // back to using the ResolverRankerService.
    // TODO: responsibility for this fallback behavior can live outside of the AppPrediction client.
    private ResolverRankerServiceResolverComparator mResolverRankerService;
    private AppPredictionServiceComparatorModel mComparatorModel;

    AppPredictionServiceResolverComparator(
            Context context,
@@ -74,25 +76,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
        mUser = user;
        mReferrerPackage = referrerPackage;
        setChooserActivityLogger(chooserActivityLogger);
        mComparatorModel = buildUpdatedModel();
    }

    @Override
    int compare(ResolveInfo lhs, ResolveInfo rhs) {
        if (mResolverRankerService != null) {
            return mResolverRankerService.compare(lhs, rhs);
        }
        Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
                lhs.activityInfo.name));
        Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
                rhs.activityInfo.name));
        if (lhsRank == null && rhsRank == null) {
            return 0;
        } else if (lhsRank == null) {
            return -1;
        } else if (rhsRank == null) {
            return 1;
        }
        return lhsRank - rhsRank;
        return mComparatorModel.getComparator().compare(lhs, rhs);
    }

    @Override
@@ -121,6 +110,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
                                        mContext, mIntent, mReferrerPackage,
                                        () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
                                        getChooserActivityLogger());
                        mComparatorModel = buildUpdatedModel();
                        mResolverRankerService.compute(targets);
                    } else {
                        Log.i(TAG, "AppPredictionService response received");
@@ -163,6 +153,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
            mTargetRanks.put(componentName, i);
            Log.i(TAG, "handleSortedAppTargets, sortedAppTargets #" + i + ": " + componentName);
        }
        mComparatorModel = buildUpdatedModel();
    }

    private boolean checkAppTargetRankValid(List<AppTarget> sortedAppTargets) {
@@ -176,6 +167,85 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator

    @Override
    float getScore(ComponentName name) {
        return mComparatorModel.getScore(name);
    }

    @Override
    void updateModel(ComponentName componentName) {
        mComparatorModel.notifyOnTargetSelected(componentName);
    }

    @Override
    void destroy() {
        if (mResolverRankerService != null) {
            mResolverRankerService.destroy();
            mResolverRankerService = null;
            mComparatorModel = buildUpdatedModel();
        }
    }

    /**
     * Re-construct an {@code AppPredictionServiceComparatorModel} to replace the current model
     * instance (if any) using the up-to-date {@code AppPredictionServiceResolverComparator} ivar
     * values.
     *
     * TODO: each time we replace the model instance, we're either updating the model to use
     * adjusted data (which is appropriate), or we're providing a (late) value for one of our ivars
     * that wasn't available the last time the model was updated. For those latter cases, we should
     * just avoid creating the model altogether until we have all the prerequisites we'll need. Then
     * we can probably simplify the logic in {@code AppPredictionServiceComparatorModel} since we
     * won't need to handle edge cases when the model data isn't fully prepared.
     * (In some cases, these kinds of "updates" might interleave -- e.g., we might have finished
     * initializing the first time and now want to adjust some data, but still need to wait for
     * changes to propagate to the other ivars before rebuilding the model.)
     */
    private AppPredictionServiceComparatorModel buildUpdatedModel() {
        return new AppPredictionServiceComparatorModel(
                mAppPredictor, mResolverRankerService, mUser, mTargetRanks);
    }

    // TODO: Finish separating behaviors of AbstractResolverComparator, then (probably) make this a
    // standalone class once clients are written in terms of ResolverComparatorModel.
    static class AppPredictionServiceComparatorModel implements ResolverComparatorModel {
        private final AppPredictor mAppPredictor;
        private final ResolverRankerServiceResolverComparator mResolverRankerService;
        private final UserHandle mUser;
        private final Map<ComponentName, Integer> mTargetRanks;  // Treat as immutable.

        AppPredictionServiceComparatorModel(
                AppPredictor appPredictor,
                @Nullable ResolverRankerServiceResolverComparator resolverRankerService,
                UserHandle user,
                Map<ComponentName, Integer> targetRanks) {
            mAppPredictor = appPredictor;
            mResolverRankerService = resolverRankerService;
            mUser = user;
            mTargetRanks = targetRanks;
        }

        @Override
        public Comparator<ResolveInfo> getComparator() {
            return (lhs, rhs) -> {
                if (mResolverRankerService != null) {
                    return mResolverRankerService.compare(lhs, rhs);
                }
                Integer lhsRank = mTargetRanks.get(new ComponentName(lhs.activityInfo.packageName,
                        lhs.activityInfo.name));
                Integer rhsRank = mTargetRanks.get(new ComponentName(rhs.activityInfo.packageName,
                        rhs.activityInfo.name));
                if (lhsRank == null && rhsRank == null) {
                    return 0;
                } else if (lhsRank == null) {
                    return -1;
                } else if (rhsRank == null) {
                    return 1;
                }
                return lhsRank - rhsRank;
            };
        }

        @Override
        public float getScore(ComponentName name) {
            if (mResolverRankerService != null) {
                return mResolverRankerService.getScore(name);
            }
@@ -189,19 +259,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
        }

        @Override
    List<ComponentName> getTopComponentNames(int topK) {
        if (mResolverRankerService != null) {
            return mResolverRankerService.getTopComponentNames(topK);
        }
        return mTargetRanks.entrySet().stream()
                .sorted(Entry.comparingByValue())
                .limit(topK)
                .map(Entry::getKey)
                .collect(Collectors.toList());
    }

    @Override
    void updateModel(ComponentName componentName) {
        public void notifyOnTargetSelected(ComponentName componentName) {
            if (mResolverRankerService != null) {
                mResolverRankerService.updateModel(componentName);
                return;
@@ -214,12 +272,5 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
                            .setClassName(componentName.getClassName()).build(),
                        ACTION_LAUNCH).build());
        }

    @Override
    void destroy() {
        if (mResolverRankerService != null) {
            mResolverRankerService.destroy();
            mResolverRankerService = null;
        }
    }
}
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.app;

import android.content.ComponentName;
import android.content.pm.ResolveInfo;

import java.util.Comparator;
import java.util.List;

/**
 * A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
 *
 * As required by the {@link Comparator} contract, objects returned by {@code getComparator()} must
 * apply a total ordering on its inputs consistent across all calls to {@code Comparator#compare()}.
 * Other query methods and ranking feedback should refer to that same ordering, so implementors are
 * generally advised to "lock in" an immutable snapshot of their model data when this object is
 * initialized (preferring to replace the entire {@code ResolverComparatorModel} instance if the
 * backing data needs to be updated in the future).
 */
interface ResolverComparatorModel {
    /**
     * Get a {@code Comparator} that can be used to sort {@code ResolveInfo} targets according to
     * the model ranking.
     */
    Comparator<ResolveInfo> getComparator();

    /**
     * Get the numerical score, if any, that the model assigns to the component with the specified
     * {@code name}. Scores range from zero to one, with one representing the highest possible
     * likelihood that the user will select that component as the target. Implementations that don't
     * assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
     */
    float getScore(ComponentName name);

    /**
     * Notify the model that the user selected a target. (Models may log this information, use it as
     * a feedback signal for their ranking, etc.) Because the data in this
     * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
     * instance in order to see any changes in the ranking that might result from this feedback.
     */
    void notifyOnTargetSelected(ComponentName componentName);
}
+0 −8
Original line number Diff line number Diff line
@@ -155,14 +155,6 @@ public class ResolverListAdapter extends BaseAdapter {
        return mResolverListController.getScore(componentName);
    }

    /**
     * Returns the list of top K component names which have highest
     * {@link #getScore(DisplayResolveInfo)}
     */
    public List<ComponentName> getTopComponentNames(int topK) {
        return mResolverListController.getTopComponentNames(topK);
    }

    public void updateModel(ComponentName componentName) {
        mResolverListController.updateModel(componentName);
    }
+0 −8
Original line number Diff line number Diff line
@@ -393,14 +393,6 @@ public class ResolverListController {
        return mResolverComparator.getScore(componentName);
    }

    /**
     * Returns the list of top K component names which have highest
     * {@link #getScore(DisplayResolveInfo)}
     */
    public List<ComponentName> getTopComponentNames(int topK) {
        return mResolverComparator.getTopComponentNames(topK);
    }

    public void updateModel(ComponentName componentName) {
        mResolverComparator.updateModel(componentName);
    }
Loading