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

Commit be2c1552 authored by Danning Chen's avatar Danning Chen
Browse files

Create ShareSheetPredictor in People Service.

Change-Id: I93a14fb8172131c8bf8c9cf7d58d99fa13c80aa3
Test: Build and run on a test device and unit tests
Bug: 146522621
parent 25e3a1ad
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ import android.os.RemoteException;
import android.util.Slog;

import com.android.server.people.data.DataManager;
import com.android.server.people.prediction.ConversationPredictor;
import com.android.server.people.prediction.AppTargetPredictor;

import java.util.List;

@@ -34,12 +34,12 @@ class SessionInfo {

    private static final String TAG = "SessionInfo";

    private final ConversationPredictor mConversationPredictor;
    private final AppTargetPredictor mAppTargetPredictor;
    private final RemoteCallbackList<IPredictionCallback> mCallbacks =
            new RemoteCallbackList<>();

    SessionInfo(AppPredictionContext predictionContext, DataManager dataManager) {
        mConversationPredictor = new ConversationPredictor(predictionContext,
        mAppTargetPredictor = AppTargetPredictor.create(predictionContext,
                this::updatePredictions, dataManager);
    }

@@ -51,8 +51,8 @@ class SessionInfo {
        mCallbacks.unregister(callback);
    }

    ConversationPredictor getPredictor() {
        return mConversationPredictor;
    AppTargetPredictor getPredictor() {
        return mAppTargetPredictor;
    }

    void onDestroy() {
+14 −31
Original line number Diff line number Diff line
@@ -59,7 +59,6 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.telephony.SmsApplication;
import com.android.server.LocalServices;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
@@ -199,6 +198,13 @@ public class DataManager {
        }
    }

    /** Gets the {@link PackageData} for the given package and user. */
    @Nullable
    public PackageData getPackage(@NonNull String packageName, @UserIdInt int userId) {
        UserData userData = getUnlockedUserData(userId);
        return userData != null ? userData.getPackageData(packageName) : null;
    }

    /** Gets the {@link ShortcutInfo} for the given shortcut ID. */
    @Nullable
    public ShortcutInfo getShortcut(@NonNull String packageName, @UserIdInt int userId,
@@ -212,20 +218,11 @@ public class DataManager {
    }

    /**
     * Gets the conversation {@link ShareShortcutInfo}s from all packages owned by the calling user
     * that match the specified {@link IntentFilter}.
     * Gets the {@link ShareShortcutInfo}s from all packages owned by the calling user that match
     * the specified {@link IntentFilter}.
     */
    public List<ShareShortcutInfo> getConversationShareTargets(
            @NonNull IntentFilter intentFilter) {
        List<ShareShortcutInfo> shareShortcuts = mShortcutManager.getShareTargets(intentFilter);
        List<ShareShortcutInfo> result = new ArrayList<>();
        for (ShareShortcutInfo shareShortcut : shareShortcuts) {
            ShortcutInfo si = shareShortcut.getShortcutInfo();
            if (getConversationInfo(si.getPackage(), si.getUserId(), si.getId()) != null) {
                result.add(shareShortcut);
            }
        }
        return result;
    public List<ShareShortcutInfo> getShareShortcuts(@NonNull IntentFilter intentFilter) {
        return mShortcutManager.getShareTargets(intentFilter);
    }

    /** Reports the {@link AppTargetEvent} from App Prediction Manager. */
@@ -236,7 +233,7 @@ public class DataManager {
        if (shortcutInfo == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) {
            return;
        }
        PackageData packageData = getPackageData(appTarget.getPackageName(),
        PackageData packageData = getPackage(appTarget.getPackageName(),
                appTarget.getUser().getIdentifier());
        if (packageData == null) {
            return;
@@ -283,20 +280,6 @@ public class DataManager {
        return userData != null && userData.isUnlocked() ? userData : null;
    }

    @Nullable
    private PackageData getPackageData(@NonNull String packageName, int userId) {
        UserData userData = getUnlockedUserData(userId);
        return userData != null ? userData.getPackageData(packageName) : null;
    }

    @Nullable
    private ConversationInfo getConversationInfo(@NonNull String packageName, @UserIdInt int userId,
            @NonNull String shortcutId) {
        PackageData packageData = getPackageData(packageName, userId);
        return packageData != null ? packageData.getConversationStore().getConversation(shortcutId)
                : null;
    }

    private void updateDefaultDialer(@NonNull UserData userData) {
        TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
        String defaultDialer = telecomManager != null
@@ -318,7 +301,7 @@ public class DataManager {
        if (shortcutId == null) {
            return null;
        }
        PackageData packageData = getPackageData(sbn.getPackageName(),
        PackageData packageData = getPackage(sbn.getPackageName(),
                sbn.getUser().getIdentifier());
        if (packageData == null
                || packageData.getConversationStore().getConversation(shortcutId) == null) {
@@ -382,7 +365,7 @@ public class DataManager {
            usageEvents.getNextEvent(e);

            String packageName = e.getPackageName();
            PackageData packageData = getPackageData(packageName, userId);
            PackageData packageData = getPackage(packageName, userId);
            if (packageData == null) {
                continue;
            }
+10 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.people.data;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.LocusId;
import android.text.TextUtils;
@@ -67,6 +68,15 @@ public class PackageData {
        return getEventStore().getPackageEventHistory();
    }

    /**
     * Gets the {@link ConversationInfo} for a given shortcut ID. Returns null if such as {@link
     * ConversationInfo} does not exist.
     */
    @Nullable
    public ConversationInfo getConversationInfo(@NonNull String shortcutId) {
        return getConversationStore().getConversation(shortcutId);
    }

    /**
     * Gets the combined {@link EventHistory} for a given shortcut ID. This returned {@link
     * EventHistory} has events of all types, no matter whether they're annotated with shortcut ID,
+128 −0
Original line number Diff line number Diff line
@@ -18,53 +18,50 @@ package com.android.server.people.prediction;

import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

/**
 * Predictor that predicts the conversations or apps the user is most likely to open.
 * Predictor that predicts the {@link AppTarget} the user is most likely to open.
 */
public class ConversationPredictor {
public class AppTargetPredictor {

    private static final String UI_SURFACE_SHARE = "share";

    /** Creates a {@link AppTargetPredictor} instance based on the prediction context. */
    public static AppTargetPredictor create(@NonNull AppPredictionContext predictionContext,
            @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
            @NonNull DataManager dataManager) {
        if (UI_SURFACE_SHARE.equals(predictionContext.getUiSurface())) {
            return new ShareTargetPredictor(
                    predictionContext, updatePredictionsMethod, dataManager);
        }
        return new AppTargetPredictor(predictionContext, updatePredictionsMethod, dataManager);
    }

    private final AppPredictionContext mPredictionContext;
    private final Consumer<List<AppTarget>> mUpdatePredictionsMethod;
    private final DataManager mDataManager;
    private final ExecutorService mCallbackExecutor;
    @Nullable
    private final IntentFilter mIntentFilter;

    public ConversationPredictor(@NonNull AppPredictionContext predictionContext,
    AppTargetPredictor(@NonNull AppPredictionContext predictionContext,
            @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
            @NonNull DataManager dataManager) {
        mPredictionContext = predictionContext;
        mUpdatePredictionsMethod = updatePredictionsMethod;
        mDataManager = dataManager;
        mCallbackExecutor = Executors.newSingleThreadExecutor();
        if (UI_SURFACE_SHARE.equals(mPredictionContext.getUiSurface())) {
            mIntentFilter = mPredictionContext.getExtras().getParcelable(
                    ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
        } else {
            mIntentFilter = null;
        }
    }

    /**
@@ -72,14 +69,14 @@ public class ConversationPredictor {
     */
    @MainThread
    public void onAppTargetEvent(AppTargetEvent event) {
        mDataManager.reportAppTargetEvent(event, mIntentFilter);
    }

    /**
     * Called by the client app to indicate a particular location has been shown to the user.
     */
    @MainThread
    public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {}
    public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {
    }

    /**
     * Called by the client app to request sorting of the provided targets based on the prediction
@@ -87,7 +84,7 @@ public class ConversationPredictor {
     */
    @MainThread
    public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
        mCallbackExecutor.execute(() -> callback.accept(targets));
        mCallbackExecutor.execute(() -> sortTargets(targets, callback));
    }

    /**
@@ -95,48 +92,37 @@ public class ConversationPredictor {
     */
    @MainThread
    public void onRequestPredictionUpdate() {
        // TODO: Re-route the call to different ranking classes for different surfaces.
        mCallbackExecutor.execute(() -> {
            List<AppTarget> targets = new ArrayList<>();
            if (mIntentFilter != null) {
                List<ShareShortcutInfo> shareShortcuts =
                        mDataManager.getConversationShareTargets(mIntentFilter);
                for (ShareShortcutInfo shareShortcut : shareShortcuts) {
                    ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
                    AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId());
                    String shareTargetClass = shareShortcut.getTargetComponent().getClassName();
                    targets.add(new AppTarget.Builder(appTargetId, shortcutInfo)
                            .setClassName(shareTargetClass)
                            .build());
        mCallbackExecutor.execute(this::predictTargets);
    }

    @VisibleForTesting
    public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
        return mUpdatePredictionsMethod;
    }
            } else {
                List<ConversationData> conversationDataList = new ArrayList<>();
                mDataManager.forAllPackages(packageData ->
                        packageData.forAllConversations(conversationInfo -> {
                            EventHistory eventHistory = packageData.getEventHistory(
                                    conversationInfo.getShortcutId());
                            ConversationData conversationData = new ConversationData(
                                    packageData.getPackageName(), packageData.getUserId(),
                                    conversationInfo, eventHistory);
                            conversationDataList.add(conversationData);
                        }));
                for (ConversationData conversationData : conversationDataList) {
                    String shortcutId = conversationData.getConversationInfo().getShortcutId();
                    ShortcutInfo shortcut = mDataManager.getShortcut(
                            conversationData.getPackageName(), conversationData.getUserId(),
                            shortcutId);
                    if (shortcut != null) {
                        AppTargetId appTargetId = new AppTargetId(shortcut.getId());
                        targets.add(new AppTarget.Builder(appTargetId, shortcut).build());

    /** To be overridden by the subclass to predict the targets. */
    @WorkerThread
    void predictTargets() {
    }

    /**
     * To be overridden by the subclass to sort the provided targets based on the prediction
     * ranking.
     */
    @WorkerThread
    void sortTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
        callback.accept(targets);
    }

    AppPredictionContext getPredictionContext() {
        return mPredictionContext;
    }
            mUpdatePredictionsMethod.accept(targets);
        });

    DataManager getDataManager() {
        return mDataManager;
    }

    @VisibleForTesting
    public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
        return mUpdatePredictionsMethod;
    void updatePredictions(List<AppTarget> targets) {
        mUpdatePredictionsMethod.accept(targets);
    }
}
+139 −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.people.prediction;

import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.server.people.data.ConversationInfo;
import com.android.server.people.data.DataManager;
import com.android.server.people.data.EventHistory;
import com.android.server.people.data.PackageData;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

class ShareTargetPredictor extends AppTargetPredictor {

    private final IntentFilter mIntentFilter;

    ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
            @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
            @NonNull DataManager dataManager) {
        super(predictionContext, updatePredictionsMethod, dataManager);
        mIntentFilter = predictionContext.getExtras().getParcelable(
                ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY);
    }

    @MainThread
    @Override
    public void onAppTargetEvent(AppTargetEvent event) {
        getDataManager().reportAppTargetEvent(event, mIntentFilter);
    }

    @WorkerThread
    @Override
    protected void predictTargets() {
        List<ShareTarget> shareTargets = getShareTargets();
        // TODO: Rank the share targets with the data in ShareTarget.mConversationData.
        List<AppTarget> appTargets = new ArrayList<>();
        for (ShareTarget shareTarget : shareTargets) {

            ShortcutInfo shortcutInfo = shareTarget.getShareShortcutInfo().getShortcutInfo();
            AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId());
            String shareTargetClassName =
                    shareTarget.getShareShortcutInfo().getTargetComponent().getClassName();
            AppTarget appTarget = new AppTarget.Builder(appTargetId, shortcutInfo)
                    .setClassName(shareTargetClassName)
                    .build();
            appTargets.add(appTarget);
            if (appTargets.size() >= getPredictionContext().getPredictedTargetCount()) {
                break;
            }
        }
        updatePredictions(appTargets);
    }

    @VisibleForTesting
    List<ShareTarget> getShareTargets() {
        List<ShareTarget> shareTargets = new ArrayList<>();
        List<ShareShortcutInfo> shareShortcuts =
                getDataManager().getShareShortcuts(mIntentFilter);

        for (ShareShortcutInfo shareShortcut : shareShortcuts) {
            ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
            String packageName = shortcutInfo.getPackage();
            int userId = shortcutInfo.getUserId();
            PackageData packageData = getDataManager().getPackage(packageName, userId);

            ConversationData conversationData = null;
            if (packageData != null) {
                String shortcutId = shortcutInfo.getId();
                ConversationInfo conversationInfo =
                        packageData.getConversationInfo(shortcutId);

                if (conversationInfo != null) {
                    EventHistory eventHistory = packageData.getEventHistory(shortcutId);
                    conversationData = new ConversationData(
                            packageName, userId, conversationInfo, eventHistory);
                }
            }
            shareTargets.add(new ShareTarget(shareShortcut, conversationData));
        }

        return shareTargets;
    }

    @VisibleForTesting
    static class ShareTarget {

        @NonNull
        private final ShareShortcutInfo mShareShortcutInfo;
        @Nullable
        private final ConversationData mConversationData;

        private ShareTarget(@NonNull ShareShortcutInfo shareShortcutInfo,
                @Nullable ConversationData conversationData) {
            mShareShortcutInfo = shareShortcutInfo;
            mConversationData = conversationData;
        }

        @NonNull
        @VisibleForTesting
        ShareShortcutInfo getShareShortcutInfo() {
            return mShareShortcutInfo;
        }

        @Nullable
        @VisibleForTesting
        ConversationData getConversationData() {
            return mConversationData;
        }
    }
}
Loading