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