Skip to content

Most visited

Recently visited

navigation
AppShortcuts / src / com.example.android.appshortcuts /

ShortcutHelper.java

1
/*
2
 * Copyright (C) 2016 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.example.android.appshortcuts;
17
 
18
import android.content.Context;
19
import android.content.Intent;
20
import android.content.pm.ShortcutInfo;
21
import android.content.pm.ShortcutManager;
22
import android.graphics.Bitmap;
23
import android.graphics.BitmapFactory;
24
import android.graphics.drawable.Icon;
25
import android.net.Uri;
26
import android.os.AsyncTask;
27
import android.os.PersistableBundle;
28
import android.util.Log;
29
 
30
import java.io.BufferedInputStream;
31
import java.io.IOException;
32
import java.io.InputStream;
33
import java.net.URL;
34
import java.net.URLConnection;
35
import java.util.ArrayList;
36
import java.util.Arrays;
37
import java.util.HashSet;
38
import java.util.List;
39
import java.util.function.BooleanSupplier;
40
 
41
public class ShortcutHelper {
42
    private static final String TAG = Main.TAG;
43
 
44
    private static final String EXTRA_LAST_REFRESH =
45
            "com.example.android.shortcutsample.EXTRA_LAST_REFRESH";
46
 
47
    private static final long REFRESH_INTERVAL_MS = 60 * 60 * 1000;
48
 
49
    private final Context mContext;
50
 
51
    private final ShortcutManager mShortcutManager;
52
 
53
    public ShortcutHelper(Context context) {
54
        mContext = context;
55
        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
56
    }
57
 
58
    public void maybeRestoreAllDynamicShortcuts() {
59
        if (mShortcutManager.getDynamicShortcuts().size() == 0) {
60
            // NOTE: If this application is always supposed to have dynamic shortcuts, then publish
61
            // them here.
62
            // Note when an application is "restored" on a new device, all dynamic shortcuts
63
            // will *not* be restored but the pinned shortcuts *will*.
64
        }
65
    }
66
 
67
    public void reportShortcutUsed(String id) {
68
        mShortcutManager.reportShortcutUsed(id);
69
    }
70
 
71
    /**
72
     * Use this when interacting with ShortcutManager to show consistent error messages.
73
     */
74
    private void callShortcutManager(BooleanSupplier r) {
75
        try {
76
            if (!r.getAsBoolean()) {
77
                Utils.showToast(mContext, "Call to ShortcutManager is rate-limited");
78
            }
79
        } catch (Exception e) {
80
            Log.e(TAG, "Caught Exception", e);
81
            Utils.showToast(mContext, "Error while calling ShortcutManager: " + e.toString());
82
        }
83
    }
84
 
85
    /**
86
     * Return all mutable shortcuts from this app self.
87
     */
88
    public List<ShortcutInfo> getShortcuts() {
89
        // Load mutable dynamic shortcuts and pinned shortcuts and put them into a single list
90
        // removing duplicates.
91
 
92
        final List<ShortcutInfo> ret = new ArrayList<>();
93
        final HashSet<String> seenKeys = new HashSet<>();
94
 
95
        // Check existing shortcuts shortcuts
96
        for (ShortcutInfo shortcut : mShortcutManager.getDynamicShortcuts()) {
97
            if (!shortcut.isImmutable()) {
98
                ret.add(shortcut);
99
                seenKeys.add(shortcut.getId());
100
            }
101
        }
102
        for (ShortcutInfo shortcut : mShortcutManager.getPinnedShortcuts()) {
103
            if (!shortcut.isImmutable() && !seenKeys.contains(shortcut.getId())) {
104
                ret.add(shortcut);
105
                seenKeys.add(shortcut.getId());
106
            }
107
        }
108
        return ret;
109
    }
110
 
111
    /**
112
     * Called when the activity starts.  Looks for shortcuts that have been pushed and refreshes
113
     * them (but the refresh part isn't implemented yet...).
114
     */
115
    public void refreshShortcuts(boolean force) {
116
        new AsyncTask<Void, Void, Void>() {
117
            @Override
118
            protected Void doInBackground(Void... params) {
119
                Log.i(TAG, "refreshingShortcuts...");
120
 
121
                final long now = System.currentTimeMillis();
122
                final long staleThreshold = force ? now : now - REFRESH_INTERVAL_MS;
123
 
124
                // Check all existing dynamic and pinned shortcut, and if their last refresh
125
                // time is older than a certain threshold, update them.
126
 
127
                final List<ShortcutInfo> updateList = new ArrayList<>();
128
 
129
                for (ShortcutInfo shortcut : getShortcuts()) {
130
                    if (shortcut.isImmutable()) {
131
                        continue;
132
                    }
133
 
134
                    final PersistableBundle extras = shortcut.getExtras();
135
                    if (extras != null && extras.getLong(EXTRA_LAST_REFRESH) >= staleThreshold) {
136
                        // Shortcut still fresh.
137
                        continue;
138
                    }
139
                    Log.i(TAG, "Refreshing shortcut: " + shortcut.getId());
140
 
141
                    final ShortcutInfo.Builder b = new ShortcutInfo.Builder(
142
                            mContext, shortcut.getId());
143
 
144
                    setSiteInformation(b, shortcut.getIntent().getData());
145
                    setExtras(b);
146
 
147
                    updateList.add(b.build());
148
                }
149
                // Call update.
150
                if (updateList.size() > 0) {
151
                    callShortcutManager(() -> mShortcutManager.updateShortcuts(updateList));
152
                }
153
 
154
                return null;
155
            }
156
        }.execute();
157
    }
158
 
159
    private ShortcutInfo createShortcutForUrl(String urlAsString) {
160
        Log.i(TAG, "createShortcutForUrl: " + urlAsString);
161
 
162
        final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mContext, urlAsString);
163
 
164
        final Uri uri = Uri.parse(urlAsString);
165
        b.setIntent(new Intent(Intent.ACTION_VIEW, uri));
166
 
167
        setSiteInformation(b, uri);
168
        setExtras(b);
169
 
170
        return b.build();
171
    }
172
 
173
    private ShortcutInfo.Builder setSiteInformation(ShortcutInfo.Builder b, Uri uri) {
174
        // TODO Get the actual site <title> and use it.
175
        // TODO Set the current locale to accept-language to get localized title.
176
        b.setShortLabel(uri.getHost());
177
        b.setLongLabel(uri.toString());
178
 
179
        Bitmap bmp = fetchFavicon(uri);
180
        if (bmp != null) {
181
            b.setIcon(Icon.createWithBitmap(bmp));
182
        } else {
183
            b.setIcon(Icon.createWithResource(mContext, R.drawable.link));
184
        }
185
 
186
        return b;
187
    }
188
 
189
    private ShortcutInfo.Builder setExtras(ShortcutInfo.Builder b) {
190
        final PersistableBundle extras = new PersistableBundle();
191
        extras.putLong(EXTRA_LAST_REFRESH, System.currentTimeMillis());
192
        b.setExtras(extras);
193
        return b;
194
    }
195
 
196
    private String normalizeUrl(String urlAsString) {
197
        if (urlAsString.startsWith("http://") || urlAsString.startsWith("https://")) {
198
            return urlAsString;
199
        } else {
200
            return "http://" + urlAsString;
201
        }
202
    }
203
 
204
    public void addWebSiteShortcut(String urlAsString) {
205
        final String uriFinal = urlAsString;
206
        callShortcutManager(() -> {
207
            final ShortcutInfo shortcut = createShortcutForUrl(normalizeUrl(uriFinal));
208
            return mShortcutManager.addDynamicShortcuts(Arrays.asList(shortcut));
209
        });
210
    }
211
 
212
    public void removeShortcut(ShortcutInfo shortcut) {
213
        mShortcutManager.removeDynamicShortcuts(Arrays.asList(shortcut.getId()));
214
    }
215
 
216
    public void disableShortcut(ShortcutInfo shortcut) {
217
        mShortcutManager.disableShortcuts(Arrays.asList(shortcut.getId()));
218
    }
219
 
220
    public void enableShortcut(ShortcutInfo shortcut) {
221
        mShortcutManager.enableShortcuts(Arrays.asList(shortcut.getId()));
222
    }
223
 
224
    private Bitmap fetchFavicon(Uri uri) {
225
        final Uri iconUri = uri.buildUpon().path("favicon.ico").build();
226
        Log.i(TAG, "Fetching favicon from: " + iconUri);
227
 
228
        InputStream is = null;
229
        BufferedInputStream bis = null;
230
        try
231
        {
232
            URLConnection conn = new URL(iconUri.toString()).openConnection();
233
            conn.connect();
234
            is = conn.getInputStream();
235
            bis = new BufferedInputStream(is, 8192);
236
            return BitmapFactory.decodeStream(bis);
237
        } catch (IOException e) {
238
            Log.w(TAG, "Failed to fetch favicon from " + iconUri, e);
239
            return null;
240
        }
241
    }
242
}
This site uses cookies to store your preferences for site-specific language and display options.

Hooray!