| /* |
| * 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.dialer.shortcuts; |
| |
| import android.Manifest; |
| import android.annotation.TargetApi; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ShortcutInfo; |
| import android.content.pm.ShortcutManager; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Build.VERSION_CODES; |
| import android.provider.ContactsContract.Contacts; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.WorkerThread; |
| import android.support.v4.content.ContextCompat; |
| import android.util.ArrayMap; |
| import com.android.dialer.common.Assert; |
| import com.android.dialer.common.LogUtil; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Handles refreshing of dialer pinned shortcuts. |
| * |
| * <p>Pinned shortcuts are icons that the user has dragged to their home screen from the dialer |
| * application launcher shortcut menu, which is accessible by tapping and holding the dialer |
| * launcher icon from the app drawer or a home screen. |
| * |
| * <p>When refreshing pinned shortcuts, we check to make sure that pinned contact information is |
| * still up to date (e.g. photo and name). We also check to see if the contact has been deleted from |
| * the user's contacts, and if so, we disable the pinned shortcut. |
| * |
| */ |
| @TargetApi(VERSION_CODES.N_MR1) // Shortcuts introduced in N MR1 |
| final class PinnedShortcuts { |
| |
| private static final String[] PROJECTION = |
| new String[] { |
| Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, |
| }; |
| |
| private static class Delta { |
| |
| final List<String> shortcutIdsToDisable = new ArrayList<>(); |
| final Map<String, DialerShortcut> shortcutsToUpdateById = new ArrayMap<>(); |
| } |
| |
| private final Context context; |
| private final ShortcutInfoFactory shortcutInfoFactory; |
| |
| PinnedShortcuts(@NonNull Context context) { |
| this.context = context; |
| this.shortcutInfoFactory = new ShortcutInfoFactory(context, new IconFactory(context)); |
| } |
| |
| /** |
| * Performs a "complete refresh" of pinned shortcuts. This is done by (synchronously) querying for |
| * all contacts which currently have pinned shortcuts. The query results are used to compute a |
| * delta which contains a list of shortcuts which need to be updated (e.g. because of name/photo |
| * changes) or disabled (if contacts were deleted). Note that pinned shortcuts cannot be deleted |
| * programmatically and must be deleted by the user. |
| * |
| * <p>If the delta is non-empty, it is applied by making appropriate calls to the {@link |
| * ShortcutManager} system service. |
| * |
| * <p>This is a slow blocking call which performs file I/O and should not be performed on the main |
| * thread. |
| */ |
| @WorkerThread |
| public void refresh() { |
| Assert.isWorkerThread(); |
| LogUtil.enterBlock("PinnedShortcuts.refresh"); |
| |
| if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) |
| != PackageManager.PERMISSION_GRANTED) { |
| LogUtil.i("PinnedShortcuts.refresh", "no contact permissions"); |
| return; |
| } |
| |
| Delta delta = new Delta(); |
| ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); |
| for (ShortcutInfo shortcutInfo : shortcutManager.getPinnedShortcuts()) { |
| if (shortcutInfo.isDeclaredInManifest()) { |
| // We never update/disable the manifest shortcut (the "create new contact" shortcut). |
| continue; |
| } |
| if (shortcutInfo.isDynamic()) { |
| // If the shortcut is both pinned and dynamic, let the logic which updates dynamic shortcuts |
| // handle the update. It would be problematic to try and apply the update here, because the |
| // setRank is nonsensical for pinned shortcuts and therefore could not be calculated. |
| continue; |
| } |
| |
| String lookupKey = DialerShortcut.getLookupKeyFromShortcutInfo(shortcutInfo); |
| Uri lookupUri = DialerShortcut.getLookupUriFromShortcutInfo(shortcutInfo); |
| |
| try (Cursor cursor = |
| context.getContentResolver().query(lookupUri, PROJECTION, null, null, null)) { |
| |
| if (cursor == null || !cursor.moveToNext()) { |
| LogUtil.i("PinnedShortcuts.refresh", "contact disabled"); |
| delta.shortcutIdsToDisable.add(shortcutInfo.getId()); |
| continue; |
| } |
| |
| // Note: The lookup key may have changed but we cannot refresh it because that would require |
| // changing the shortcut ID, which can only be accomplished with a remove and add; but |
| // pinned shortcuts cannot be added or removed. |
| DialerShortcut shortcut = |
| DialerShortcut.builder() |
| .setContactId(cursor.getLong(cursor.getColumnIndexOrThrow(Contacts._ID))) |
| .setLookupKey(lookupKey) |
| .setDisplayName( |
| cursor.getString(cursor.getColumnIndexOrThrow(Contacts.DISPLAY_NAME_PRIMARY))) |
| .build(); |
| |
| if (shortcut.needsUpdate(shortcutInfo)) { |
| LogUtil.i("PinnedShortcuts.refresh", "contact updated"); |
| delta.shortcutsToUpdateById.put(shortcutInfo.getId(), shortcut); |
| } |
| } |
| } |
| applyDelta(delta); |
| } |
| |
| private void applyDelta(@NonNull Delta delta) { |
| ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); |
| String shortcutDisabledMessage = |
| context.getResources().getString(R.string.dialer_shortcut_disabled_message); |
| if (!delta.shortcutIdsToDisable.isEmpty()) { |
| shortcutManager.disableShortcuts(delta.shortcutIdsToDisable, shortcutDisabledMessage); |
| } |
| if (!delta.shortcutsToUpdateById.isEmpty()) { |
| // Note: This call updates both pinned and dynamic shortcuts, but the delta should contain |
| // no dynamic shortcuts. |
| if (!shortcutManager.updateShortcuts( |
| shortcutInfoFactory.buildShortcutInfos(delta.shortcutsToUpdateById))) { |
| LogUtil.i("PinnedShortcuts.applyDelta", "shortcutManager rate limited."); |
| } |
| } |
| } |
| } |