/* * 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. * *

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. * *

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 shortcutIdsToDisable = new ArrayList<>(); final Map 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. * *

If the delta is non-empty, it is applied by making appropriate calls to the {@link * ShortcutManager} system service. * *

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; } // Exclude shortcuts not for contacts. String action = null; if (shortcutInfo.getIntent() != null) { action = shortcutInfo.getIntent().getAction(); } if (action == null || !action.equals("com.android.dialer.shortcuts.CALL_CONTACT")) { 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."); } } } }