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 onOutgoingCallAdded(@onNull Context context, @Nullable String phoneNumber)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 Task(Context context)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 doInBackground(@onNull String... phoneNumbers)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 queryForLookupKey(String phoneNumber)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