Home | History | Annotate | Download | only in eligibility
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.intelligence.suggestions.eligibility;
     18 
     19 import static android.content.Intent.EXTRA_COMPONENT_NAME;
     20 
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.ProviderInfo;
     25 import android.content.pm.ResolveInfo;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.support.annotation.NonNull;
     29 import android.support.annotation.VisibleForTesting;
     30 import android.text.TextUtils;
     31 import android.util.Log;
     32 
     33 import com.android.settings.intelligence.R;
     34 import com.android.settings.intelligence.suggestions.model.CandidateSuggestion;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 import java.util.concurrent.Callable;
     39 import java.util.concurrent.ExecutionException;
     40 import java.util.concurrent.ExecutorService;
     41 import java.util.concurrent.Executors;
     42 import java.util.concurrent.FutureTask;
     43 import java.util.concurrent.TimeUnit;
     44 import java.util.concurrent.TimeoutException;
     45 
     46 /**
     47  * Filters candidate list to only valid ones.
     48  */
     49 public class CandidateSuggestionFilter {
     50 
     51     private static final String TAG = "CandidateSuggestionFilter";
     52 
     53     private static CandidateSuggestionFilter sChecker;
     54     private static ExecutorService sExecutorService;
     55 
     56     public static CandidateSuggestionFilter getInstance() {
     57         if (sChecker == null) {
     58             sChecker = new CandidateSuggestionFilter();
     59             sExecutorService = Executors.newCachedThreadPool();
     60         }
     61         return sChecker;
     62     }
     63 
     64     @NonNull
     65     public synchronized List<CandidateSuggestion> filterCandidates(Context context,
     66             List<CandidateSuggestion> candidates) {
     67         final long startTime = System.currentTimeMillis();
     68         final List<CandidateFilterTask> checkTasks = new ArrayList<>();
     69         final List<CandidateSuggestion> incompleteCandidates = new ArrayList<>();
     70         if (candidates == null) {
     71             return incompleteCandidates;
     72         }
     73         // Put a check task into ExecutorService for each candidate.
     74         for (CandidateSuggestion candidate : candidates) {
     75             final CandidateFilterTask task = new CandidateFilterTask(context, candidate);
     76             sExecutorService.execute(task);
     77             checkTasks.add(task);
     78         }
     79         for (CandidateFilterTask task : checkTasks) {
     80             try {
     81                 long checkTaskTimeOutValue =
     82                         context.getResources().getInteger(R.integer.check_task_timeout_ms);
     83                 final CandidateSuggestion candidate = task.get(checkTaskTimeOutValue,
     84                         TimeUnit.MILLISECONDS);
     85                 if (candidate != null) {
     86                     incompleteCandidates.add(candidate);
     87                 }
     88             } catch (TimeoutException | InterruptedException | ExecutionException e) {
     89                 Log.w(TAG, "Error checking completion state for " + task.getId());
     90             }
     91         }
     92         final long endTime = System.currentTimeMillis();
     93         Log.d(TAG, "filterCandidates duration: " + (endTime - startTime));
     94         return incompleteCandidates;
     95     }
     96 
     97     /**
     98      * {@link FutureTask} that filters status for a suggestion candidate.
     99      * <p/>
    100      * If the candidate status is valid, {@link #get()} will return the candidate itself.
    101      * Otherwise it returns null.
    102      */
    103     static class CandidateFilterTask extends FutureTask<CandidateSuggestion> {
    104 
    105         private static final String EXTRA_CANDIDATE_ID = "candidate_id";
    106         private static final String RESULT_IS_COMPLETE = "candidate_is_complete";
    107 
    108         private final String mId;
    109 
    110         public CandidateFilterTask(Context context, CandidateSuggestion candidate) {
    111             super(new GetSuggestionStatusCallable(context, candidate));
    112             mId = candidate.getId();
    113         }
    114 
    115         public String getId() {
    116             return mId;
    117         }
    118 
    119         @VisibleForTesting
    120         static class GetSuggestionStatusCallable implements Callable<CandidateSuggestion> {
    121             @VisibleForTesting
    122             static final String CONTENT_PROVIDER_INTENT_ACTION =
    123                     "com.android.settings.action.SUGGESTION_STATE_PROVIDER";
    124             private static final String METHOD_GET_SUGGESTION_STATE = "getSuggestionState";
    125 
    126             private final Context mContext;
    127             private final CandidateSuggestion mCandidate;
    128 
    129             public GetSuggestionStatusCallable(Context context, CandidateSuggestion candidate) {
    130                 mContext = context.getApplicationContext();
    131                 mCandidate = candidate;
    132             }
    133 
    134             @Override
    135             public CandidateSuggestion call() throws Exception {
    136                 // First find if candidate has any state provider.
    137                 final String packageName = mCandidate.getComponent().getPackageName();
    138                 final Intent probe = new Intent(CONTENT_PROVIDER_INTENT_ACTION)
    139                         .setPackage(packageName);
    140                 final List<ResolveInfo> providers = mContext.getPackageManager()
    141                         .queryIntentContentProviders(probe, 0 /* flags */);
    142                 if (providers == null || providers.isEmpty()) {
    143                     // No provider, let it go through
    144                     return mCandidate;
    145                 }
    146                 final ProviderInfo providerInfo = providers.get(0).providerInfo;
    147                 if (providerInfo == null || TextUtils.isEmpty(providerInfo.authority)) {
    148                     // Bad provider - don't let candidate pass through.
    149                     return null;
    150                 }
    151                 // Query candidate state (isComplete)
    152                 final Uri uri = new Uri.Builder()
    153                         .scheme(ContentResolver.SCHEME_CONTENT)
    154                         .authority(providerInfo.authority)
    155                         .build();
    156                 final Bundle result = mContext.getContentResolver().call(
    157                         uri, METHOD_GET_SUGGESTION_STATE, null /* args */,
    158                         buildGetSuggestionStateExtras(mCandidate));
    159                 final boolean isComplete = result.getBoolean(RESULT_IS_COMPLETE, false);
    160                 Log.d(TAG, "Suggestion state result " + result);
    161                 return isComplete ? null : mCandidate;
    162             }
    163 
    164             @VisibleForTesting
    165             static Bundle buildGetSuggestionStateExtras(CandidateSuggestion candidate) {
    166                 final Bundle args = new Bundle();
    167                 final String id = candidate.getId();
    168                 args.putString(EXTRA_CANDIDATE_ID, id);
    169                 args.putParcelable(EXTRA_COMPONENT_NAME, candidate.getComponent());
    170                 return args;
    171             }
    172         }
    173     }
    174 }
    175