1c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott/* 2c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Copyright (C) 2016 The Android Open Source Project 3c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * 4c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Licensed under the Apache License, Version 2.0 (the "License"); 5c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * you may not use this file except in compliance with the License. 6c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * You may obtain a copy of the License at 7c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * 8c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * http://www.apache.org/licenses/LICENSE-2.0 9c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * 10c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Unless required by applicable law or agreed to in writing, software 11c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * distributed under the License is distributed on an "AS IS" BASIS, 12c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * See the License for the specific language governing permissions and 14c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * limitations under the License. 15c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott */ 16c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottpackage com.android.contacts; 17c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 18c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.annotation.TargetApi; 1902c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Maiimport android.app.ActivityManager; 20c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.app.job.JobInfo; 21c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.app.job.JobParameters; 22c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.app.job.JobScheduler; 23c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.app.job.JobService; 248ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerottimport android.content.BroadcastReceiver; 25c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.ComponentName; 26c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.ContentResolver; 27c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.ContentUris; 28c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.Context; 298ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerottimport android.content.Intent; 308ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerottimport android.content.IntentFilter; 31c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.pm.ShortcutInfo; 32c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.content.pm.ShortcutManager; 33c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.database.Cursor; 34c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.Bitmap; 35c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.BitmapFactory; 36c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.BitmapRegionDecoder; 37c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.Canvas; 38c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.Rect; 39a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Maiimport android.graphics.drawable.AdaptiveIconDrawable; 40c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.drawable.Drawable; 41c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.graphics.drawable.Icon; 42c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.net.Uri; 43c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.os.AsyncTask; 44c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.os.Build; 45c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.os.PersistableBundle; 46c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.provider.ContactsContract; 47c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.provider.ContactsContract.Contacts; 48c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.support.annotation.VisibleForTesting; 498ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerottimport android.support.v4.content.LocalBroadcastManager; 50a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Maiimport android.support.v4.os.BuildCompat; 51c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport android.util.Log; 52c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 530a49afa2ad697307cc04ef4cb86570574fa720f2Gary Maiimport com.android.contacts.activities.RequestPermissionsActivity; 5469c182afb0e6d82a341a28b4317aa703af768906Gary Maiimport com.android.contacts.compat.CompatUtils; 5502c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Maiimport com.android.contacts.lettertiles.LetterTileDrawable; 5669c182afb0e6d82a341a28b4317aa703af768906Gary Maiimport com.android.contacts.util.BitmapUtil; 5769c182afb0e6d82a341a28b4317aa703af768906Gary Maiimport com.android.contacts.util.ImplicitIntentsUtil; 5869c182afb0e6d82a341a28b4317aa703af768906Gary Maiimport com.android.contacts.util.PermissionsUtil; 59c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport com.android.contactsbind.experiments.Flags; 60c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 61c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport java.io.IOException; 62c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport java.io.InputStream; 63c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport java.util.ArrayList; 64c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport java.util.Collections; 65c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottimport java.util.List; 66c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 67c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott/** 68c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * This class creates and updates the dynamic shortcuts displayed on the Nexus launcher for the 69c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Contacts app. 70c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * 71c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Currently it adds shortcuts for the top 3 contacts in the {@link Contacts#CONTENT_STREQUENT_URI} 72c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * 73c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott * Usage: DynamicShortcuts.initialize should be called during Application creation. This will 7491520d763209fa61de58fa54ba4864ed87a2df6fGary Mai * schedule a Job to keep the shortcuts up-to-date so no further interactions should be necessary. 75c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott */ 76c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott@TargetApi(Build.VERSION_CODES.N_MR1) 77c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerottpublic class DynamicShortcuts { 78c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private static final String TAG = "DynamicShortcuts"; 79c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 80d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott // Must be the same as shortcutId in res/xml/shortcuts.xml 81d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott // Note: This doesn't fit very well because this is a "static" shortcut but it's still the most 82d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott // sensible place to put it right now. 83d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott public static final String SHORTCUT_ADD_CONTACT = "shortcut-add-contact"; 84d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott 85c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Note the Nexus launcher automatically truncates shortcut labels if they exceed these limits 86c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // however, we implement our own truncation in case the shortcut is shown on a launcher that 87c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // has different behavior 88c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private static final int SHORT_LABEL_MAX_LENGTH = 12; 89c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private static final int LONG_LABEL_MAX_LENGTH = 30; 90c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private static final int MAX_SHORTCUTS = 3; 91c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 925d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott private static final String EXTRA_SHORTCUT_TYPE = "extraShortcutType"; 935d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott 945d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott // Because pinned shortcuts persist across app upgrades these values should not be changed 955d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott // though new ones may be added 965d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott private static final int SHORTCUT_TYPE_UNKNOWN = 0; 975d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott private static final int SHORTCUT_TYPE_CONTACT_URI = 1; 98191868386add89315f5bc8057dfa6b64f627c960Gary Mai private static final int SHORTCUT_TYPE_ACTION_URI = 2; 995d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott 100c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 101c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott static final String[] PROJECTION = new String[] { 102c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott Contacts._ID, Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME_PRIMARY 103c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott }; 104c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 105c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private final Context mContext; 10602c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai 107c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private final ContentResolver mContentResolver; 108c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private final ShortcutManager mShortcutManager; 109c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private int mShortLabelMaxLength = SHORT_LABEL_MAX_LENGTH; 110c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private int mLongLabelMaxLength = LONG_LABEL_MAX_LENGTH; 11102c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai private int mIconSize; 1128debbac602d98b63602a27c29e9d78d2468c3d45Arthur Wang private final int mContentChangeMinUpdateDelay; 1138debbac602d98b63602a27c29e9d78d2468c3d45Arthur Wang private final int mContentChangeMaxUpdateDelay; 114020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott private final JobScheduler mJobScheduler; 115c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 116c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public DynamicShortcuts(Context context) { 117c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott this(context, context.getContentResolver(), (ShortcutManager) 118020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott context.getSystemService(Context.SHORTCUT_SERVICE), 119020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE)); 120c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 121c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 122020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott @VisibleForTesting 123c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public DynamicShortcuts(Context context, ContentResolver contentResolver, 124020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott ShortcutManager shortcutManager, JobScheduler jobScheduler) { 125c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mContext = context; 126c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mContentResolver = contentResolver; 127c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager = shortcutManager; 128020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott mJobScheduler = jobScheduler; 129df86ede2101d902eb95838adf4176c89d9263c89Walter Jang mContentChangeMinUpdateDelay = Flags.getInstance() 1308debbac602d98b63602a27c29e9d78d2468c3d45Arthur Wang .getInteger(Experiments.DYNAMIC_MIN_CONTENT_CHANGE_UPDATE_DELAY_MILLIS); 131df86ede2101d902eb95838adf4176c89d9263c89Walter Jang mContentChangeMaxUpdateDelay = Flags.getInstance() 1328debbac602d98b63602a27c29e9d78d2468c3d45Arthur Wang .getInteger(Experiments.DYNAMIC_MAX_CONTENT_CHANGE_UPDATE_DELAY_MILLIS); 13302c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai final ActivityManager am = (ActivityManager) context 13402c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai .getSystemService(Context.ACTIVITY_SERVICE); 13502c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai mIconSize = context.getResources().getDimensionPixelSize(R.dimen.shortcut_icon_size); 13602c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai if (mIconSize == 0) { 13702c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai mIconSize = am.getLauncherLargeIconSize(); 13802c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai } 139c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 140c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 141c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 142c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott void setShortLabelMaxLength(int length) { 143c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott this.mShortLabelMaxLength = length; 144c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 145c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 146c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 147c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott void setLongLabelMaxLength(int length) { 148c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott this.mLongLabelMaxLength = length; 149c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 150c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 151c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 152c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott void refresh() { 1538ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott // Guard here in addition to initialize because this could be run by the JobScheduler 1548ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott // after permissions are revoked (maybe) 1558ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott if (!hasRequiredPermissions()) return; 1568ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 157020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott final List<ShortcutInfo> shortcuts = getStrequentShortcuts(); 158020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott mShortcutManager.setDynamicShortcuts(shortcuts); 159020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott if (Log.isLoggable(TAG, Log.DEBUG)) { 160020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "set dynamic shortcuts " + shortcuts); 161020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott } 162c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott updatePinned(); 163c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 164c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 165c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 166c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott void updatePinned() { 167c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<ShortcutInfo> updates = new ArrayList<>(); 168c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<String> removedIds = new ArrayList<>(); 169c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<String> enable = new ArrayList<>(); 170c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 171c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott for (ShortcutInfo shortcut : mShortcutManager.getPinnedShortcuts()) { 172c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final PersistableBundle extras = shortcut.getExtras(); 1735d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott 174191868386add89315f5bc8057dfa6b64f627c960Gary Mai if (extras == null || extras.getInt(EXTRA_SHORTCUT_TYPE, SHORTCUT_TYPE_UNKNOWN) != 175191868386add89315f5bc8057dfa6b64f627c960Gary Mai SHORTCUT_TYPE_CONTACT_URI) { 1765d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott continue; 1775d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott } 1785d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott 179c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // The contact ID may have changed but that's OK because it is just an optimization 1805d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott final long contactId = extras.getLong(Contacts._ID); 181c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 182c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final ShortcutInfo update = createShortcutForUri( 183c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott Contacts.getLookupUri(contactId, shortcut.getId())); 184c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (update != null) { 185c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott updates.add(update); 186c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (!shortcut.isEnabled()) { 187c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Handle the case that a contact is disabled because it doesn't exist but 188c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // later is created (for instance by a sync) 189c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott enable.add(update.getId()); 190c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 191c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } else if (shortcut.isEnabled()) { 192c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott removedIds.add(shortcut.getId()); 193c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 194c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 195c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 196020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott if (Log.isLoggable(TAG, Log.DEBUG)) { 197020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "updating " + updates); 198020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "enabling " + enable); 199020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "disabling " + removedIds); 200020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott } 201020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott 202c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager.updateShortcuts(updates); 203c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager.enableShortcuts(enable); 204c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager.disableShortcuts(removedIds, 205c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mContext.getString(R.string.dynamic_shortcut_contact_removed_message)); 206c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 207c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 208c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private ShortcutInfo createShortcutForUri(Uri contactUri) { 209c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Cursor cursor = mContentResolver.query(contactUri, PROJECTION, null, null, null); 210c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (cursor == null) return null; 211c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 212c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott try { 213c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (cursor.moveToFirst()) { 214c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return createShortcutFromRow(cursor); 215c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 216c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } finally { 217c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott cursor.close(); 218c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 219c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return null; 220c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 221c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 222c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public List<ShortcutInfo> getStrequentShortcuts() { 223c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // The limit query parameter doesn't seem to work for this uri but we'll leave it because in 224c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // case it does work on some phones or platform versions. 225c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon() 226c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, 227c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott String.valueOf(MAX_SHORTCUTS)) 228c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .build(); 229c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Cursor cursor = mContentResolver.query(uri, PROJECTION, null, null, null); 230c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 231c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (cursor == null) return Collections.emptyList(); 232c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 233c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<ShortcutInfo> result = new ArrayList<>(); 234c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 235c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott try { 236f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott int i = 0; 237f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott while (i < MAX_SHORTCUTS && cursor.moveToNext()) { 238f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott final ShortcutInfo shortcut = createShortcutFromRow(cursor); 239f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott if (shortcut == null) { 240f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott continue; 241f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott } 242f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott result.add(shortcut); 243f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott i++; 244c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 245c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } finally { 246c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott cursor.close(); 247c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 248c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return result; 249c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 250c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 251c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 252c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 253c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ShortcutInfo createShortcutFromRow(Cursor cursor) { 254c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final ShortcutInfo.Builder builder = builderForContactShortcut(cursor); 255f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott if (builder == null) { 256f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott return null; 257f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott } 258c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott addIconForContact(cursor, builder); 259c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return builder.build(); 260c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 261c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 262c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 263c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ShortcutInfo.Builder builderForContactShortcut(Cursor cursor) { 264c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final long id = cursor.getLong(0); 265c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final String lookupKey = cursor.getString(1); 266c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final String displayName = cursor.getString(2); 267c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return builderForContactShortcut(id, lookupKey, displayName); 268c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 269c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 270c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 271c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ShortcutInfo.Builder builderForContactShortcut(long id, String lookupKey, String displayName) { 272f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott if (lookupKey == null || displayName == null) { 273f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott return null; 274f2e3808991751107666808627b88cb03b85d616fMarcus Hagerott } 275c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final PersistableBundle extras = new PersistableBundle(); 276c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott extras.putLong(Contacts._ID, id); 2775d1ec1da58cbfe1403c2f24380138b0e32b0a357Marcus Hagerott extras.putInt(EXTRA_SHORTCUT_TYPE, SHORTCUT_TYPE_CONTACT_URI); 278c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 279c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, lookupKey) 280c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .setIntent(ImplicitIntentsUtil.getIntentForQuickContactLauncherShortcut(mContext, 281c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott Contacts.getLookupUri(id, lookupKey))) 282c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .setDisabledMessage(mContext.getString(R.string.dynamic_shortcut_disabled_message)) 283c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .setExtras(extras); 284c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 28508d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai setLabel(builder, displayName); 28608d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai return builder; 28708d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } 288c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 28908d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai @VisibleForTesting 29008d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai ShortcutInfo getActionShortcutInfo(String id, String label, Intent action, Icon icon) { 29108d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai if (id == null || label == null) { 29208d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai return null; 293c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 29408d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai final PersistableBundle extras = new PersistableBundle(); 295191868386add89315f5bc8057dfa6b64f627c960Gary Mai extras.putInt(EXTRA_SHORTCUT_TYPE, SHORTCUT_TYPE_ACTION_URI); 29608d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai 29708d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai final ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, id) 29808d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai .setIntent(action) 29908d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai .setIcon(icon) 3009b1b93772df8a52bcb22a1eccadfa9a31614fa3dGary Mai .setExtras(extras) 30108d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai .setDisabledMessage(mContext.getString(R.string.dynamic_shortcut_disabled_message)); 30208d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai 30308d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai setLabel(builder, label); 30408d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai return builder.build(); 305c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 306c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 30791520d763209fa61de58fa54ba4864ed87a2df6fGary Mai public ShortcutInfo getQuickContactShortcutInfo(long id, String lookupKey, String displayName) { 30891520d763209fa61de58fa54ba4864ed87a2df6fGary Mai final ShortcutInfo.Builder builder = builderForContactShortcut(id, lookupKey, displayName); 3099b1b93772df8a52bcb22a1eccadfa9a31614fa3dGary Mai if (builder == null) { 3109b1b93772df8a52bcb22a1eccadfa9a31614fa3dGary Mai return null; 3119b1b93772df8a52bcb22a1eccadfa9a31614fa3dGary Mai } 31291520d763209fa61de58fa54ba4864ed87a2df6fGary Mai addIconForContact(id, lookupKey, displayName, builder); 31391520d763209fa61de58fa54ba4864ed87a2df6fGary Mai return builder.build(); 31491520d763209fa61de58fa54ba4864ed87a2df6fGary Mai } 31591520d763209fa61de58fa54ba4864ed87a2df6fGary Mai 31608d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai private void setLabel(ShortcutInfo.Builder builder, String label) { 31708d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai if (label.length() < mLongLabelMaxLength) { 31808d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai builder.setLongLabel(label); 31908d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } else { 32008d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai builder.setLongLabel(label.substring(0, mLongLabelMaxLength - 1).trim() + "…"); 32108d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } 32208d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai 32308d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai if (label.length() < mShortLabelMaxLength) { 32408d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai builder.setShortLabel(label); 32508d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } else { 32608d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai builder.setShortLabel(label.substring(0, mShortLabelMaxLength - 1).trim() + "…"); 32708d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } 32808d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai } 32908d87ee355cdac231c9ca4ef16bcfa5451a1a0b4Gary Mai 330c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private void addIconForContact(Cursor cursor, ShortcutInfo.Builder builder) { 331c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final long id = cursor.getLong(0); 332c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final String lookupKey = cursor.getString(1); 333c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final String displayName = cursor.getString(2); 33491520d763209fa61de58fa54ba4864ed87a2df6fGary Mai addIconForContact(id, lookupKey, displayName, builder); 33591520d763209fa61de58fa54ba4864ed87a2df6fGary Mai } 336c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 33791520d763209fa61de58fa54ba4864ed87a2df6fGary Mai private void addIconForContact(long id, String lookupKey, String displayName, 33891520d763209fa61de58fa54ba4864ed87a2df6fGary Mai ShortcutInfo.Builder builder) { 339191868386add89315f5bc8057dfa6b64f627c960Gary Mai Bitmap bitmap = getContactPhoto(id); 340191868386add89315f5bc8057dfa6b64f627c960Gary Mai if (bitmap == null) { 341191868386add89315f5bc8057dfa6b64f627c960Gary Mai bitmap = getFallbackAvatar(displayName, lookupKey); 342c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 343a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai final Icon icon; 344a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai if (BuildCompat.isAtLeastO()) { 345a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai icon = Icon.createWithAdaptiveBitmap(bitmap); 346a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai } else { 347a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai icon = Icon.createWithBitmap(bitmap); 348a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai } 349191868386add89315f5bc8057dfa6b64f627c960Gary Mai 350191868386add89315f5bc8057dfa6b64f627c960Gary Mai builder.setIcon(icon); 351c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 352c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 353c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private Bitmap getContactPhoto(long id) { 354c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final InputStream photoStream = Contacts.openContactPhotoInputStream( 355c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mContext.getContentResolver(), 356c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ContentUris.withAppendedId(Contacts.CONTENT_URI, id), true); 357c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 358c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott if (photoStream == null) return null; 359c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott try { 360c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Bitmap bitmap = decodeStreamForShortcut(photoStream); 361c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott photoStream.close(); 362c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return bitmap; 363c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } catch (IOException e) { 364c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott Log.e(TAG, "Failed to decode contact photo for shortcut. ID=" + id, e); 365c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return null; 366c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } finally { 367c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott try { 368c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott photoStream.close(); 369c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } catch (IOException e) { 370c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // swallow 371c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 372c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 373c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 374c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 375c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private Bitmap decodeStreamForShortcut(InputStream stream) throws IOException { 376c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final BitmapRegionDecoder bitmapDecoder = BitmapRegionDecoder.newInstance(stream, false); 377c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 378c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int sourceWidth = bitmapDecoder.getWidth(); 379c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int sourceHeight = bitmapDecoder.getHeight(); 380c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 381a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai final int iconMaxWidth = mShortcutManager.getIconMaxWidth(); 382c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int iconMaxHeight = mShortcutManager.getIconMaxHeight(); 383c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 384c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int sampleSize = Math.min( 38502c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai BitmapUtil.findOptimalSampleSize(sourceWidth, mIconSize), 38602c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai BitmapUtil.findOptimalSampleSize(sourceHeight, mIconSize)); 387c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final BitmapFactory.Options opts = new BitmapFactory.Options(); 388c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott opts.inSampleSize = sampleSize; 389c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 390c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int scaledWidth = sourceWidth / opts.inSampleSize; 391c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int scaledHeight = sourceHeight / opts.inSampleSize; 392c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 393c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int targetWidth = Math.min(scaledWidth, iconMaxWidth); 394c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int targetHeight = Math.min(scaledHeight, iconMaxHeight); 395c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 396c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Make it square. 397c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int targetSize = Math.min(targetWidth, targetHeight); 398c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 399c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // The region is defined in the coordinates of the source image then the sampling is 400c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // done on the extracted region. 401c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int prescaledXOffset = ((scaledWidth - targetSize) * opts.inSampleSize) / 2; 402c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final int prescaledYOffset = ((scaledHeight - targetSize) * opts.inSampleSize) / 2; 403c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 404c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Bitmap bitmap = bitmapDecoder.decodeRegion(new Rect( 405c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott prescaledXOffset, prescaledYOffset, 406c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott sourceWidth - prescaledXOffset, sourceHeight - prescaledYOffset 407c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ), opts); 408c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott bitmapDecoder.recycle(); 409c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 410a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai if (!BuildCompat.isAtLeastO()) { 411a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai return BitmapUtil.getRoundedBitmap(bitmap, targetSize, targetSize); 412a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai } 413a80b937c0e7f98c547066ce3750b808645ac1ea9Gary Mai 41402c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai return bitmap; 415c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 416c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 417c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private Bitmap getFallbackAvatar(String displayName, String lookupKey) { 41802c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai // Use a circular icon if we're not on O or higher. 41902c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai final boolean circularIcon = !BuildCompat.isAtLeastO(); 420c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 421c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final ContactPhotoManager.DefaultImageRequest request = 42202c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai new ContactPhotoManager.DefaultImageRequest(displayName, lookupKey, circularIcon); 42302c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai if (BuildCompat.isAtLeastO()) { 42402c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai // On O, scale the image down to add the padding needed by AdaptiveIcons. 42502c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai request.scale = LetterTileDrawable.getAdaptiveIconScale(); 42602c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai } 427c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Drawable avatar = ContactPhotoManager.getDefaultAvatarDrawableForContact( 428c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mContext.getResources(), true, request); 42902c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai final Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888); 430c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // The avatar won't draw unless it thinks it is visible 431c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott avatar.setVisible(true, true); 432c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final Canvas canvas = new Canvas(result); 43302c3dee53a1be051bc7369d401585cfe87e7c5d7Gary Mai avatar.setBounds(0, 0, mIconSize, mIconSize); 434c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott avatar.draw(canvas); 435c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return result; 436c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 437c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 438020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott @VisibleForTesting 439020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott void handleFlagDisabled() { 4408ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott removeAllShortcuts(); 4418ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott mJobScheduler.cancel(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID); 4428ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 4438ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 4448ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott private void removeAllShortcuts() { 445c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager.removeAllDynamicShortcuts(); 446c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 447c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<ShortcutInfo> pinned = mShortcutManager.getPinnedShortcuts(); 448c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final List<String> ids = new ArrayList<>(pinned.size()); 449c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott for (ShortcutInfo shortcut : pinned) { 450c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ids.add(shortcut.getId()); 451c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 452c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mShortcutManager.disableShortcuts(ids, mContext 453c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .getString(R.string.dynamic_shortcut_disabled_message)); 454020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott if (Log.isLoggable(TAG, Log.DEBUG)) { 455020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "DynamicShortcuts have been removed."); 456020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott } 457c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 458c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 459c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 460c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott void scheduleUpdateJob() { 461c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final JobInfo job = new JobInfo.Builder( 462c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID, 463c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott new ComponentName(mContext, ContactsJobService.class)) 464c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // We just observe all changes to contacts. It would be better to be more granular 465c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // but CP2 only notifies using this URI anyway so there isn't any point in adding 466c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // that complexity. 467c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .addTriggerContentUri(new JobInfo.TriggerContentUri(ContactsContract.AUTHORITY_URI, 468c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) 4698debbac602d98b63602a27c29e9d78d2468c3d45Arthur Wang .setTriggerContentUpdateDelay(mContentChangeMinUpdateDelay) 470d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott .setTriggerContentMaxDelay(mContentChangeMaxUpdateDelay) 471d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott .build(); 472020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott mJobScheduler.schedule(job); 473c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 474c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 4758ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott void updateInBackground() { 4768ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott new ShortcutUpdateTask(this).execute(); 4778ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 4788ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 479c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public synchronized static void initialize(Context context) { 480020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott if (Log.isLoggable(TAG, Log.DEBUG)) { 481df86ede2101d902eb95838adf4176c89d9263c89Walter Jang final Flags flags = Flags.getInstance(); 482020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "DyanmicShortcuts.initialize\nVERSION >= N_MR1? " + 483020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) + 484b2504c7327f3c43e6bc532a834766df9c4a3fdb0Marcus Hagerott "\nisJobScheduled? " + 485b2504c7327f3c43e6bc532a834766df9c4a3fdb0Marcus Hagerott (CompatUtils.isLauncherShortcutCompatible() && isJobScheduled(context)) + 486020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott "\nminDelay=" + 487020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott flags.getInteger(Experiments.DYNAMIC_MIN_CONTENT_CHANGE_UPDATE_DELAY_MILLIS) + 488020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott "\nmaxDelay=" + 489020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott flags.getInteger(Experiments.DYNAMIC_MAX_CONTENT_CHANGE_UPDATE_DELAY_MILLIS)); 490020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott } 491020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott 492d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott if (!CompatUtils.isLauncherShortcutCompatible()) return; 493c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 494c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final DynamicShortcuts shortcuts = new DynamicShortcuts(context); 4958ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 4969adc9ef59dd668a92dee7f913733a188eadde8b3Walter Jang if (!shortcuts.hasRequiredPermissions()) { 4978ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott final IntentFilter filter = new IntentFilter(); 4988ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott filter.addAction(RequestPermissionsActivity.BROADCAST_PERMISSIONS_GRANTED); 4998ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott LocalBroadcastManager.getInstance(shortcuts.mContext).registerReceiver( 5008ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott new PermissionsGrantedReceiver(), filter); 501c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } else if (!isJobScheduled(context)) { 502c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Update the shortcuts. If the job is already scheduled then either the app is being 503c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // launched to run the job in which case the shortcuts will get updated when it runs or 504c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // it has been launched for some other reason and the data we care about for shortcuts 505c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // hasn't changed. Because the job reschedules itself after completion this check 506c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // essentially means that this will run on each app launch that happens after a reboot. 507c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Note: the task schedules the job after completing. 508c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott new ShortcutUpdateTask(shortcuts).execute(); 509c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 510c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 511c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 5128ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott @VisibleForTesting 5138ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott public static void reset(Context context) { 5148ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott final JobScheduler jobScheduler = 5158ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 5168ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott jobScheduler.cancel(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID); 5178ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 5188ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott if (!CompatUtils.isLauncherShortcutCompatible()) { 5198ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott return; 5208ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 5218ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott new DynamicShortcuts(context).removeAllShortcuts(); 5228ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 5238ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 5248ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott @VisibleForTesting 5258ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott boolean hasRequiredPermissions() { 5268ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott return PermissionsUtil.hasContactsPermissions(mContext); 5278ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 5288ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 529c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public static void updateFromJob(final JobService service, final JobParameters jobParams) { 530c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott new ShortcutUpdateTask(new DynamicShortcuts(service)) { 531c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @Override 532c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott protected void onPostExecute(Void aVoid) { 533c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // Must call super first which will reschedule the job before we call jobFinished 534c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott super.onPostExecute(aVoid); 535c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott service.jobFinished(jobParams, false); 536c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 537c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott }.execute(); 538c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 539c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 540c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @VisibleForTesting 541c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public static boolean isJobScheduled(Context context) { 542c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott final JobScheduler scheduler = (JobScheduler) context 543c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott .getSystemService(Context.JOB_SCHEDULER_SERVICE); 544c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return scheduler.getPendingJob(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID) != null; 545c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 546c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 547d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott public static void reportShortcutUsed(Context context, String lookupKey) { 548677ee2bd9c37f3bc4cbfd1f05b3c93f12a7299f3Marcus Hagerott if (!CompatUtils.isLauncherShortcutCompatible() || lookupKey == null) return; 549d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott final ShortcutManager shortcutManager = (ShortcutManager) context 550d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott .getSystemService(Context.SHORTCUT_SERVICE); 551d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott shortcutManager.reportShortcutUsed(lookupKey); 552d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott } 553d105c1e23485927b408ee6041d6fac8a71267768Marcus Hagerott 554c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private static class ShortcutUpdateTask extends AsyncTask<Void, Void, Void> { 555c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott private DynamicShortcuts mDynamicShortcuts; 556c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 557c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott public ShortcutUpdateTask(DynamicShortcuts shortcuts) { 558c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mDynamicShortcuts = shortcuts; 559c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 560c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 561c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @Override 562c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott protected Void doInBackground(Void... voids) { 563c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mDynamicShortcuts.refresh(); 564c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott return null; 565c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 566c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott 567c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott @Override 568c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott protected void onPostExecute(Void aVoid) { 569020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott if (Log.isLoggable(TAG, Log.DEBUG)) { 570020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott Log.d(TAG, "ShorcutUpdateTask.onPostExecute"); 571020f041f25b41d1315ab67078e524f4fa595f6c7Marcus Hagerott } 572c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // The shortcuts may have changed so update the job so that we are observing the 573c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott // correct Uris 574c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott mDynamicShortcuts.scheduleUpdateJob(); 575c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 576c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott } 5778ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott 5788ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott private static class PermissionsGrantedReceiver extends BroadcastReceiver { 5798ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott @Override 5808ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott public void onReceive(Context context, Intent intent) { 5818ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott // Clear the receiver. 5828ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott LocalBroadcastManager.getInstance(context).unregisterReceiver(this); 5838ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott DynamicShortcuts.initialize(context); 5848ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 5858ac989cfc4de29743f946f00df7a78ecccb73802Marcus Hagerott } 586c5083f9a9cf22f3b4acc2f8a6f098a37c6759800Marcus Hagerott} 587