content: /*
   * Copyright 2018 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 static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
  
  import android.app.prediction.AppPredictor;
  import android.app.prediction.AppTarget;
  import android.app.prediction.AppTargetEvent;
  import android.app.prediction.AppTargetId;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.ResolveInfo;
  import android.os.Message;
  import android.os.UserHandle;
  import android.util.Log;
  
  import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.Executors;
  
  /**
   * Uses an {@link AppPredictor} to sort Resolver targets. If the AppPredictionService appears to be
   * disabled by returning an empty sorted target list, {@link AppPredictionServiceResolverComparator}
   * will fallback to using a {@link ResolverRankerServiceResolverComparator}.
   */
  class AppPredictionServiceResolverComparator extends AbstractResolverComparator {
  
      private static final String TAG = "APSResolverComparator";
      private static final boolean DEBUG = false;
  
      private final AppPredictor mAppPredictor;
      private final Context mContext;
      private final Map<ComponentName, Integer> mTargetRanks = new HashMap<>();
      private final UserHandle mUser;
      private final Intent mIntent;
      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.
      private ResolverRankerServiceResolverComparator mResolverRankerService;
  
      AppPredictionServiceResolverComparator(
                  Context context,
                  Intent intent,
                  String referrerPackage,
                  AppPredictor appPredictor,
                  UserHandle user) {
          super(context, intent);
          mContext = context;
          mIntent = intent;
          mAppPredictor = appPredictor;
          mUser = user;
          mReferrerPackage = referrerPackage;
      }
  
      @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;
      }
  
      @Override
      void doCompute(List<ResolvedComponentInfo> targets) {
          if (targets.isEmpty()) {
              mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT);
              return;
          }
          List<AppTarget> appTargets = new ArrayList<>();
          for (ResolvedComponentInfo target : targets) {
              appTargets.add(
                      new AppTarget.Builder(
                          new AppTargetId(target.name.flattenToString()),
                              target.name.getPackageName(),
                              mUser)
                      .setClassName(target.name.getClassName())
                      .build());
          }
          mAppPredictor.sortTargets(appTargets, Executors.newSingleThreadExecutor(),
                  sortedAppTargets -> {
                      if (sortedAppTargets.isEmpty()) {
                          if (DEBUG) {
                              Log.d(TAG, "AppPredictionService disabled. Using resolver.");
                          }
                          // APS for chooser is disabled. Fallback to resolver.
                          mResolverRankerService =
                                  new ResolverRankerServiceResolverComparator(
                                      mContext, mIntent, mReferrerPackage,
                                          () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT));
                          mResolverRankerService.compute(targets);
                      } else {
                          if (DEBUG) {
                              Log.d(TAG, "AppPredictionService response received");
                          }
                          Message msg =
                              Message.obtain(mHandler, RANKER_SERVICE_RESULT, sortedAppTargets);
                          msg.sendToTarget();
                      }
                  }
          );
      }
  
      @Override
      void handleResultMessage(Message msg) {
          // Null value is okay if we have defaulted to the ResolverRankerService.
          if (msg.what == RANKER_SERVICE_RESULT && msg.obj != null) {
              final List<AppTarget> sortedAppTargets = (List<AppTarget>) msg.obj;
              for (int i = 0; i < sortedAppTargets.size(); i++) {
                  mTargetRanks.put(new ComponentName(sortedAppTargets.get(i).getPackageName(),
                          sortedAppTargets.get(i).getClassName()), i);
              }
          } else if (msg.obj == null && mResolverRankerService == null) {
              Log.e(TAG, "Unexpected null result");
          }
      }
  
      @Override
      float getScore(ComponentName name) {
          if (mResolverRankerService != null) {
              return mResolverRankerService.getScore(name);
          }
          Integer rank = mTargetRanks.get(name);
          if (rank == null) {
              Log.w(TAG, "Score requested for unknown component.");
              return 0f;
          }
          int consecutiveSumOfRanks = (mTargetRanks.size() - 1) * (mTargetRanks.size()) / 2;
          return 1.0f - (((float) rank) / consecutiveSumOfRanks);
      }
  
      @Override
      void updateModel(ComponentName componentName) {
          if (mResolverRankerService != null) {
              mResolverRankerService.updateModel(componentName);
              return;
          }
          mAppPredictor.notifyAppTargetEvent(
                  new AppTargetEvent.Builder(
                      new AppTarget.Builder(
                          new AppTargetId(componentName.toString()),
                          componentName.getPackageName(), mUser)
                          .setClassName(componentName.getClassName()).build(),
                      ACTION_LAUNCH).build());
      }
  
      @Override
      void destroy() {
          if (mResolverRankerService != null) {
              mResolverRankerService.destroy();
              mResolverRankerService = null;
          }
      }
  }
  