content: /*
   * Copyright (C) 2016 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.annotation.WorkerThread;
  import android.app.ActivityManager;
  import android.app.AppGlobals;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ActivityInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.ResolveInfo;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import java.util.concurrent.CountDownLatch;
  
  /**
   * A helper for the ResolverActivity that exposes methods to retrieve, filter and sort its list of
   * resolvers.
   */
  public class ResolverListController {
  
      private final Context mContext;
      private final PackageManager mpm;
      private final int mLaunchedFromUid;
  
      // Needed for sorting resolvers.
      private final Intent mTargetIntent;
      private final String mReferrerPackage;
  
      private static final String TAG = "ResolverListController";
      private static final boolean DEBUG = false;
  
      private AbstractResolverComparator mResolverComparator;
      private boolean isComputed = false;
  
      public ResolverListController(
              Context context,
              PackageManager pm,
              Intent targetIntent,
              String referrerPackage,
              int launchedFromUid) {
          this(context, pm, targetIntent, referrerPackage, launchedFromUid,
                      new ResolverRankerServiceResolverComparator(
                          context, targetIntent, referrerPackage, null));
      }
  
      public ResolverListController(
              Context context,
              PackageManager pm,
              Intent targetIntent,
              String referrerPackage,
              int launchedFromUid,
              AbstractResolverComparator resolverComparator) {
          mContext = context;
          mpm = pm;
          mLaunchedFromUid = launchedFromUid;
          mTargetIntent = targetIntent;
          mReferrerPackage = referrerPackage;
          mResolverComparator = resolverComparator;
      }
  
      @VisibleForTesting
      public ResolveInfo getLastChosen() throws RemoteException {
          return AppGlobals.getPackageManager().getLastChosenActivity(
                  mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                  PackageManager.MATCH_DEFAULT_ONLY);
      }
  
      @VisibleForTesting
      public void setLastChosen(Intent intent, IntentFilter filter, int match)
              throws RemoteException {
          AppGlobals.getPackageManager().setLastChosenActivity(intent,
                  intent.resolveType(mContext.getContentResolver()),
                  PackageManager.MATCH_DEFAULT_ONLY,
                  filter, match, intent.getComponent());
      }
  
      @VisibleForTesting
      public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
              boolean shouldGetResolvedFilter,
              boolean shouldGetActivityMetadata,
              List<Intent> intents) {
          List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
          for (int i = 0, N = intents.size(); i < N; i++) {
              final Intent intent = intents.get(i);
              int flags = PackageManager.MATCH_DEFAULT_ONLY
                      | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
                      | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
              if (intent.isWebIntent()
                          || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
                  flags |= PackageManager.MATCH_INSTANT;
              }
              final List<ResolveInfo> infos = mpm.queryIntentActivities(intent, flags);
              // Remove any activities that are not exported.
              int totalSize = infos.size();
              for (int j = totalSize - 1; j >= 0 ; j--) {
                  ResolveInfo info = infos.get(j);
                  if (info.activityInfo != null && !info.activityInfo.exported) {
                      infos.remove(j);
                  }
              }
              if (infos != null) {
                  if (resolvedComponents == null) {
                      resolvedComponents = new ArrayList<>();
                  }
                  addResolveListDedupe(resolvedComponents, intent, infos);
              }
          }
          return resolvedComponents;
      }
  
      @VisibleForTesting
      public void addResolveListDedupe(List<ResolverActivity.ResolvedComponentInfo> into,
              Intent intent,
              List<ResolveInfo> from) {
          final int fromCount = from.size();
          final int intoCount = into.size();
          for (int i = 0; i < fromCount; i++) {
              final ResolveInfo newInfo = from.get(i);
              boolean found = false;
              // Only loop to the end of into as it was before we started; no dupes in from.
              for (int j = 0; j < intoCount; j++) {
                  final ResolverActivity.ResolvedComponentInfo rci = into.get(j);
                  if (isSameResolvedComponent(newInfo, rci)) {
                      found = true;
                      rci.add(intent, newInfo);
                      break;
                  }
              }
              if (!found) {
                  final ComponentName name = new ComponentName(
                          newInfo.activityInfo.packageName, newInfo.activityInfo.name);
                  final ResolverActivity.ResolvedComponentInfo rci =
                          new ResolverActivity.ResolvedComponentInfo(name, intent, newInfo);
                  rci.setPinned(isComponentPinned(name));
                  into.add(rci);
              }
          }
      }
  
  
      /**
       * Whether this component is pinned by the user. Always false for resolver; overridden in
       * Chooser.
       */
      public boolean isComponentPinned(ComponentName name) {
          return false;
      }
  
  
      // Filter out any activities that the launched uid does not have permission for.
      // To preserve the inputList, optionally will return the original list if any modification has
      // been made.
      @VisibleForTesting
      public ArrayList<ResolverActivity.ResolvedComponentInfo> filterIneligibleActivities(
              List<ResolverActivity.ResolvedComponentInfo> inputList,
              boolean returnCopyOfOriginalListIfModified) {
          ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
          for (int i = inputList.size()-1; i >= 0; i--) {
              ActivityInfo ai = inputList.get(i)
                      .getResolveInfoAt(0).activityInfo;
              int granted = ActivityManager.checkComponentPermission(
                      ai.permission, mLaunchedFromUid,
                      ai.applicationInfo.uid, ai.exported);
  
              if (granted != PackageManager.PERMISSION_GRANTED
                      || isComponentFiltered(ai.getComponentName())) {
                  // Access not allowed! We're about to filter an item,
                  // so modify the unfiltered version if it hasn't already been modified.
                  if (returnCopyOfOriginalListIfModified && listToReturn == null) {
                      listToReturn = new ArrayList<>(inputList);
                  }
                  inputList.remove(i);
              }
          }
          return listToReturn;
      }
  
      // Filter out any low priority items.
      //
      // To preserve the inputList, optionally will return the original list if any modification has
      // been made.
      @VisibleForTesting
      public ArrayList<ResolverActivity.ResolvedComponentInfo> filterLowPriority(
              List<ResolverActivity.ResolvedComponentInfo> inputList,
              boolean returnCopyOfOriginalListIfModified) {
          ArrayList<ResolverActivity.ResolvedComponentInfo> listToReturn = null;
          // Only display the first matches that are either of equal
          // priority or have asked to be default options.
          ResolverActivity.ResolvedComponentInfo rci0 = inputList.get(0);
          ResolveInfo r0 = rci0.getResolveInfoAt(0);
          int N = inputList.size();
          for (int i = 1; i < N; i++) {
              ResolveInfo ri = inputList.get(i).getResolveInfoAt(0);
              if (DEBUG) Log.v(
                      TAG,
                      r0.activityInfo.name + "=" +
                              r0.priority + "/" + r0.isDefault + " vs " +
                              ri.activityInfo.name + "=" +
                              ri.priority + "/" + ri.isDefault);
              if (r0.priority != ri.priority ||
                      r0.isDefault != ri.isDefault) {
                  while (i < N) {
                      if (returnCopyOfOriginalListIfModified && listToReturn == null) {
                          listToReturn = new ArrayList<>(inputList);
                      }
                      inputList.remove(i);
                      N--;
                  }
              }
          }
          return listToReturn;
      }
  
      private class ComputeCallback implements AbstractResolverComparator.AfterCompute {
  
          private CountDownLatch mFinishComputeSignal;
  
          public ComputeCallback(CountDownLatch finishComputeSignal) {
              mFinishComputeSignal = finishComputeSignal;
          }
  
          public void afterCompute () {
              mFinishComputeSignal.countDown();
          }
      }
  
      @VisibleForTesting
      @WorkerThread
      public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
          if (mResolverComparator == null) {
              Log.d(TAG, "Comparator has already been destroyed; skipped.");
              return;
          }
          try {
              long beforeRank = System.currentTimeMillis();
              if (!isComputed) {
                  final CountDownLatch finishComputeSignal = new CountDownLatch(1);
                  ComputeCallback callback = new ComputeCallback(finishComputeSignal);
                  mResolverComparator.setCallBack(callback);
                  mResolverComparator.compute(inputList);
                  finishComputeSignal.await();
                  isComputed = true;
              }
              Collections.sort(inputList, mResolverComparator);
  
              long afterRank = System.currentTimeMillis();
              if (DEBUG) {
                  Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
              }
          } catch (InterruptedException e) {
              Log.e(TAG, "Compute & Sort was interrupted: " + e);
          }
      }
  
  
      private static boolean isSameResolvedComponent(ResolveInfo a,
              ResolverActivity.ResolvedComponentInfo b) {
          final ActivityInfo ai = a.activityInfo;
          return ai.packageName.equals(b.name.getPackageName())
                  && ai.name.equals(b.name.getClassName());
      }
  
      boolean isComponentFiltered(ComponentName componentName) {
          return false;
      }
  
      @VisibleForTesting
      public float getScore(ResolverActivity.DisplayResolveInfo target) {
          return mResolverComparator.getScore(target.getResolvedComponentName());
      }
  
      public void updateModel(ComponentName componentName) {
          mResolverComparator.updateModel(componentName);
      }
  
      public void updateChooserCounts(String packageName, int userId, String action) {
          mResolverComparator.updateChooserCounts(packageName, userId, action);
      }
  
      public void destroy() {
          mResolverComparator.destroy();
      }
  }
  