Home | History | Annotate | Download | only in shortcuts
      1 /*
      2  * Copyright (C) 2016 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.dialer.shortcuts;
     18 
     19 import android.Manifest;
     20 import android.annotation.TargetApi;
     21 import android.content.Context;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.ShortcutManager;
     24 import android.database.Cursor;
     25 import android.net.Uri;
     26 import android.os.AsyncTask;
     27 import android.os.Build;
     28 import android.os.Build.VERSION_CODES;
     29 import android.provider.ContactsContract.Contacts;
     30 import android.provider.ContactsContract.PhoneLookup;
     31 import android.support.annotation.MainThread;
     32 import android.support.annotation.NonNull;
     33 import android.support.annotation.Nullable;
     34 import android.support.annotation.WorkerThread;
     35 import android.support.v4.content.ContextCompat;
     36 import android.text.TextUtils;
     37 import com.android.dialer.common.Assert;
     38 import com.android.dialer.common.LogUtil;
     39 import com.android.dialer.common.concurrent.AsyncTaskExecutor;
     40 import com.android.dialer.common.concurrent.AsyncTaskExecutors;
     41 
     42 /**
     43  * Reports outgoing calls as shortcut usage.
     44  *
     45  * <p>Note that all outgoing calls are considered shortcut usage, no matter where they are initiated
     46  * from (i.e. from anywhere in the dialer app, or even from other apps).
     47  *
     48  * <p>This allows launcher applications to provide users with shortcut suggestions, even if the user
     49  * isn't already using shortcuts.
     50  */
     51 @TargetApi(VERSION_CODES.N_MR1) // Shortcuts introduced in N_MR1
     52 public class ShortcutUsageReporter {
     53 
     54   private static final AsyncTaskExecutor EXECUTOR = AsyncTaskExecutors.createThreadPoolExecutor();
     55 
     56   /**
     57    * Called when an outgoing call is added to the call list in order to report outgoing calls as
     58    * shortcut usage. This should be called exactly once for each outgoing call.
     59    *
     60    * <p>Asynchronously queries the contacts database for the contact's lookup key which corresponds
     61    * to the provided phone number, and uses that to report shortcut usage.
     62    *
     63    * @param context used to access ShortcutManager system service
     64    * @param phoneNumber the phone number being called
     65    */
     66   @MainThread
     67   public static void onOutgoingCallAdded(@NonNull Context context, @Nullable String phoneNumber) {
     68     Assert.isMainThread();
     69     Assert.isNotNull(context);
     70 
     71     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1 || TextUtils.isEmpty(phoneNumber)) {
     72       return;
     73     }
     74 
     75     EXECUTOR.submit(Task.ID, new Task(context), phoneNumber);
     76   }
     77 
     78   private static final class Task extends AsyncTask<String, Void, Void> {
     79     private static final String ID = "ShortcutUsageReporter.Task";
     80 
     81     private final Context context;
     82 
     83     public Task(Context context) {
     84       this.context = context;
     85     }
     86 
     87     /** @param phoneNumbers array with exactly one non-empty phone number */
     88     @Override
     89     @WorkerThread
     90     protected Void doInBackground(@NonNull String... phoneNumbers) {
     91       Assert.isWorkerThread();
     92 
     93       String lookupKey = queryForLookupKey(phoneNumbers[0]);
     94       if (!TextUtils.isEmpty(lookupKey)) {
     95         LogUtil.i("ShortcutUsageReporter.backgroundLogUsage", "%s", lookupKey);
     96         ShortcutManager shortcutManager =
     97             (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
     98 
     99         // Note: There may not currently exist a shortcut with the provided key, but it is logged
    100         // anyway, so that launcher applications at least have the information should the shortcut
    101         // be created in the future.
    102         shortcutManager.reportShortcutUsed(lookupKey);
    103       }
    104       return null;
    105     }
    106 
    107     @Nullable
    108     @WorkerThread
    109     private String queryForLookupKey(String phoneNumber) {
    110       Assert.isWorkerThread();
    111 
    112       if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
    113           != PackageManager.PERMISSION_GRANTED) {
    114         LogUtil.i("ShortcutUsageReporter.queryForLookupKey", "No contact permissions");
    115         return null;
    116       }
    117 
    118       Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
    119       try (Cursor cursor =
    120           context
    121               .getContentResolver()
    122               .query(uri, new String[] {Contacts.LOOKUP_KEY}, null, null, null)) {
    123 
    124         if (cursor == null || !cursor.moveToNext()) {
    125           return null; // No contact for dialed number
    126         }
    127         // Arbitrarily use first result.
    128         return cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
    129       }
    130     }
    131   }
    132 }
    133