1475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki/*
2475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * Copyright (C) 2017 The Android Open Source Project
3475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki *
4475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
5475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * you may not use this file except in compliance with the License.
6475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * You may obtain a copy of the License at
7475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki *
8475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
9475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki *
10475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * Unless required by applicable law or agreed to in writing, software
11475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
12475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * See the License for the specific language governing permissions and
14475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * limitations under the License.
15475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki */
16475c36539da9687d75447adb9054c98ce03c754aMakoto Onukipackage com.android.server.pm;
17475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
18475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.annotation.NonNull;
19475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.annotation.Nullable;
20475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.content.pm.ShortcutInfo;
21475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.graphics.Bitmap;
22475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.graphics.Bitmap.CompressFormat;
23475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.graphics.drawable.Icon;
24475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.os.SystemClock;
25475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.util.Log;
26475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport android.util.Slog;
27475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
28475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport com.android.internal.annotations.GuardedBy;
29475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport com.android.internal.util.Preconditions;
30475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
31475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
32475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport libcore.io.IoUtils;
33475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
34475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.io.ByteArrayOutputStream;
35475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.io.File;
36475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.io.IOException;
37475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.io.PrintWriter;
38475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.Deque;
39475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.CountDownLatch;
40475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.Executor;
41475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.LinkedBlockingDeque;
42475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.LinkedBlockingQueue;
43475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.ThreadPoolExecutor;
44475c36539da9687d75447adb9054c98ce03c754aMakoto Onukiimport java.util.concurrent.TimeUnit;
45475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
46475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki/**
47475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * Class to save shortcut bitmaps on a worker thread.
48475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki *
49475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki * The methods with the "Locked" prefix must be called with the service lock held.
50475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki */
51475c36539da9687d75447adb9054c98ce03c754aMakoto Onukipublic class ShortcutBitmapSaver {
52475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private static final String TAG = ShortcutService.TAG;
53475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private static final boolean DEBUG = ShortcutService.DEBUG;
54475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
55475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private static final boolean ADD_DELAY_BEFORE_SAVE_FOR_TEST = false; // DO NOT submit with true.
56475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private static final long SAVE_DELAY_MS_FOR_TEST = 1000; // DO NOT submit with true.
57475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
58475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    /**
59475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Before saving shortcuts.xml, and returning icons to the launcher, we wait for all pending
60475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * saves to finish.  However if it takes more than this long, we just give up and proceed.
61475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     */
62475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private final long SAVE_WAIT_TIMEOUT_MS = 30 * 1000;
63475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
64475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private final ShortcutService mService;
65475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
66475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    /**
67475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Bitmaps are saved on this thread.
68475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     *
69475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Note: Just before saving shortcuts into the XML, we need to wait on all pending saves to
70475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * finish, and we need to do it with the service lock held, which would still block incoming
71475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * binder calls, meaning saving bitmaps *will* still actually block API calls too, which is
72475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * not ideal but fixing it would be tricky, so this is still a known issue on the current
73475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * version.
74475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     *
75475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * In order to reduce the conflict, we use an own thread for this purpose, rather than
76475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * reusing existing background threads, and also to avoid possible deadlocks.
77475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     */
78475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private final Executor mExecutor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
79475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            new LinkedBlockingQueue<>());
80475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
81475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    /** Represents a bitmap to save. */
82475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private static class PendingItem {
83475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        /** Hosting shortcut. */
84475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        public final ShortcutInfo shortcut;
85475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
86475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        /** Compressed bitmap data. */
87475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        public final byte[] bytes;
88475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
89475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        /** Instantiated time, only for dogfooding. */
90475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        private final long mInstantiatedUptimeMillis; // Only for dumpsys.
91475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
92475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        private PendingItem(ShortcutInfo shortcut, byte[] bytes) {
93475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            this.shortcut = shortcut;
94475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            this.bytes = bytes;
95475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            mInstantiatedUptimeMillis = SystemClock.uptimeMillis();
96475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
97475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
98475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        @Override
99475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        public String toString() {
100475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            return "PendingItem{size=" + bytes.length
101475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    + " age=" + (SystemClock.uptimeMillis() - mInstantiatedUptimeMillis) + "ms"
102475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    + " shortcut=" + shortcut.toInsecureString()
103475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    + "}";
104475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
105475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
106475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
107475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    @GuardedBy("mPendingItems")
108475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private final Deque<PendingItem> mPendingItems = new LinkedBlockingDeque<>();
109475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
110475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public ShortcutBitmapSaver(ShortcutService service) {
111475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        mService = service;
112475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // mLock = lock;
113475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
114475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
115475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public boolean waitForAllSavesLocked() {
116475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final CountDownLatch latch = new CountDownLatch(1);
117475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
118475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        mExecutor.execute(() -> latch.countDown());
119475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
120475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        try {
121475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            if (latch.await(SAVE_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
122475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                return true;
123475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
124475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            mService.wtf("Timed out waiting on saving bitmaps.");
125475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        } catch (InterruptedException e) {
126475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            Slog.w(TAG, "interrupted");
127475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
128475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        return false;
129475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
130475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
131475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    /**
132475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Wait for all pending saves to finish, and then return the given shortcut's bitmap path.
133475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     */
134475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    @Nullable
135475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public String getBitmapPathMayWaitLocked(ShortcutInfo shortcut) {
136475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final boolean success = waitForAllSavesLocked();
137475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        if (success && shortcut.hasIconFile()) {
138475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            return shortcut.getBitmapPath();
139475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        } else {
140475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            return null;
141475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
142475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
143475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
144475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public void removeIcon(ShortcutInfo shortcut) {
145475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Do not remove the actual bitmap file yet, because if the device crashes before saving
146475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // the XML we'd lose the icon.  We just remove all dangling files after saving the XML.
147475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        shortcut.setIconResourceId(0);
148475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        shortcut.setIconResName(null);
149475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        shortcut.setBitmapPath(null);
150475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE |
151475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                ShortcutInfo.FLAG_ADAPTIVE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES |
152475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
153475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
154475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
155475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public void saveBitmapLocked(ShortcutInfo shortcut,
156475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            int maxDimension, CompressFormat format, int quality) {
157475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final Icon icon = shortcut.getIcon();
158475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        Preconditions.checkNotNull(icon);
159475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
160475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final Bitmap original = icon.getBitmap();
161475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        if (original == null) {
162475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            Log.e(TAG, "Missing icon: " + shortcut);
163475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            return;
164475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
165475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
166475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Compress it and enqueue to the requests.
167475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final byte[] bytes;
168475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        try {
169475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension);
170475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            try {
171475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) {
172475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    if (!shrunk.compress(format, quality, out)) {
173475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                        Slog.wtf(ShortcutService.TAG, "Unable to compress bitmap");
174475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    }
175475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    out.flush();
176475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    bytes = out.toByteArray();
177475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    out.close();
178475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
179475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            } finally {
180475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                if (shrunk != original) {
181475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    shrunk.recycle();
182475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
183475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
184475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        } catch (IOException | RuntimeException | OutOfMemoryError e) {
185475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
186475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            return;
187475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
188475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
189475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        shortcut.addFlags(
190475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
191475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
192475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        if (icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
193475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            shortcut.addFlags(ShortcutInfo.FLAG_ADAPTIVE_BITMAP);
194475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
195475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
196475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Enqueue a pending save.
197475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        final PendingItem item = new PendingItem(shortcut, bytes);
198475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        synchronized (mPendingItems) {
199475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            mPendingItems.add(item);
200475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
201475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
202475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        if (DEBUG) {
203475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            Slog.d(TAG, "Scheduling to save: " + item);
204475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
205475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
206475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        mExecutor.execute(mRunnable);
207475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
208475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
209475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private final Runnable mRunnable = () -> {
210475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Process all pending items.
211475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        while (processPendingItems()) {
212475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
213475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    };
214475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
215475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    /**
216475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Takes a {@link PendingItem} from {@link #mPendingItems} and process it.
217475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     *
218475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * Must be called {@link #mExecutor}.
219475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     *
220475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     * @return true if it processed an item, false if the queue is empty.
221475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki     */
222475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    private boolean processPendingItems() {
223475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        if (ADD_DELAY_BEFORE_SAVE_FOR_TEST) {
224475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            Slog.w(TAG, "*** ARTIFICIAL SLEEP ***");
225475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            try {
226475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                Thread.sleep(SAVE_DELAY_MS_FOR_TEST);
227475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            } catch (InterruptedException e) {
228475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
229475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
230475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
231475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // NOTE:
232475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Ideally we should be holding the service lock when accessing shortcut instances,
233475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // but that could cause a deadlock so we don't do it.
234475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        //
235475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // Instead, waitForAllSavesLocked() uses a latch to make sure changes made on this
236475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        // thread is visible on the caller thread.
237475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
238475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        ShortcutInfo shortcut = null;
239475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        try {
240475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            final PendingItem item;
241475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
242475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            synchronized (mPendingItems) {
243475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                if (mPendingItems.size() == 0) {
244475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    return false;
245475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
246475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                item = mPendingItems.pop();
247475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
248475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
249475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            shortcut = item.shortcut;
250475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
251475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            // See if the shortcut is still relevant. (It might have been removed already.)
252475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            if (!shortcut.isIconPendingSave()) {
253475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                return true;
254475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
255475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
256475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            if (DEBUG) {
257475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                Slog.d(TAG, "Saving bitmap: " + item);
258475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
259475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
260475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            File file = null;
261475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            try {
262475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                final FileOutputStreamWithPath out = mService.openIconFileForWrite(
263475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                        shortcut.getUserId(), shortcut);
264475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                file = out.getFile();
265475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
266475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                try {
267475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    out.write(item.bytes);
268475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                } finally {
269475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    IoUtils.closeQuietly(out);
270475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
271475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
272475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                shortcut.setBitmapPath(file.getAbsolutePath());
273475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
274475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            } catch (IOException | RuntimeException e) {
275475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                Slog.e(ShortcutService.TAG, "Unable to write bitmap to file", e);
276475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
277475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                if (file != null && file.exists()) {
278475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    file.delete();
279475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
280475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                return true;
281475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
282475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        } finally {
283475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            if (DEBUG) {
284475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                Slog.d(TAG, "Saved bitmap.");
285475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
286475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            if (shortcut != null) {
287475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                if (shortcut.getBitmapPath() == null) {
288475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                    removeIcon(shortcut);
289475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                }
290475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
291475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                // Whatever happened, remove this flag.
292475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                shortcut.clearFlags(ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE);
293475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
294475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
295475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        return true;
296475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
297475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
298475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    public void dumpLocked(@NonNull PrintWriter pw, @NonNull String prefix) {
299475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        synchronized (mPendingItems) {
300475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            final int N = mPendingItems.size();
301475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            pw.print(prefix);
302475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            pw.println("Pending saves: Num=" + N + " Executor=" + mExecutor);
303475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki
304475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            for (PendingItem item : mPendingItems) {
305475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                pw.print(prefix);
306475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                pw.print("  ");
307475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki                pw.println(item);
308475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki            }
309475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki        }
310475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki    }
311475c36539da9687d75447adb9054c98ce03c754aMakoto Onuki}
312