1a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann/*
2a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * Copyright (C) 2016 The Android Open Source Project
3a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann *
4a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * Licensed under the Apache License, Version 2.0 (the "License");
5a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * you may not use this file except in compliance with the License.
6a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * You may obtain a copy of the License at
7a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann *
8a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann *      http://www.apache.org/licenses/LICENSE-2.0
9a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann *
10a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * Unless required by applicable law or agreed to in writing, software
11a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * distributed under the License is distributed on an "AS IS" BASIS,
12a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * See the License for the specific language governing permissions and
14a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * limitations under the License.
15a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann */
16a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
17a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannpackage com.android.packageinstaller;
18a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
19a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.content.Context;
20a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.content.Intent;
21a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.content.pm.PackageInstaller;
22a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.os.AsyncTask;
2374fa089b8c39d84b737607a3e3d2cde4d3b42d24Philip P. Moltmannimport android.support.annotation.NonNull;
2474fa089b8c39d84b737607a3e3d2cde4d3b42d24Philip P. Moltmannimport android.support.annotation.Nullable;
25a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.util.AtomicFile;
26a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.util.Log;
27a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.util.SparseArray;
28a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport android.util.Xml;
29a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
30a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport org.xmlpull.v1.XmlPullParser;
31fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmannimport org.xmlpull.v1.XmlPullParserException;
32fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmannimport org.xmlpull.v1.XmlSerializer;
33a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
34a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport java.io.File;
35a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport java.io.FileInputStream;
36a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport java.io.FileOutputStream;
37a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport java.io.IOException;
38a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannimport java.nio.charset.StandardCharsets;
39a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
40a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann/**
41a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann * Persists results of events and calls back observers when a matching result arrives.
42a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann */
43a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmannclass EventResultPersister {
44a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private static final String LOG_TAG = EventResultPersister.class.getSimpleName();
45a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
46a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id */
47a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
48a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
49a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
50a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * The extra with the id to set in the intent delivered to
51a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * {@link #onEventReceived(Context, Intent)}
52a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
53a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
54a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
55a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Persisted state of this object */
56a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private final AtomicFile mResultsFile;
57a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
58a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private final Object mLock = new Object();
59a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
60a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Currently stored but not yet called back results (install id -> status, status message) */
61a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private final SparseArray<EventResult> mResults = new SparseArray<>();
62a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
63a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Currently registered, not called back observers (install id -> observer) */
64a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private final SparseArray<EventResultObserver> mObservers = new SparseArray<>();
65a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
66a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Always increasing counter for install event ids */
67a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private int mCounter;
68a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
69a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** If a write that will persist the state is scheduled */
70a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private boolean mIsPersistScheduled;
71a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
72a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** If the state was changed while the data was being persisted */
73a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private boolean mIsPersistingStateValid;
74a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
754f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann    /**
764f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann     * @return a new event id.
774f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann     */
784f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann    public int getNewId() throws OutOfIdsException {
794f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann        synchronized (mLock) {
804f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann            if (mCounter == Integer.MAX_VALUE) {
814f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann                throw new OutOfIdsException();
824f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann            }
834f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann
844f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann            mCounter++;
854f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann            writeState();
864f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann
874f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann            return mCounter - 1;
884f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann        }
894f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann    }
904f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann
91a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /** Call back when a result is received. Observer is removed when onResult it called. */
92a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    interface EventResultObserver {
93ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann        void onResult(int status, int legacyStatus, @Nullable String message);
94a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
95a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
96a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
97fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * Progress parser to the next element.
98fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     *
99fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @param parser The parser to progress
100fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     */
101fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    private static void nextElement(@NonNull XmlPullParser parser)
102fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann            throws XmlPullParserException, IOException {
103fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann        int type;
104fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann        do {
105fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann            type = parser.next();
106fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann        } while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);
107fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    }
108fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann
109fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    /**
110fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * Read an int attribute from the current element
111fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     *
112fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @param parser The parser to read from
113fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @param name The attribute name to read
114fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     *
115fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @return The value of the attribute
116fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     */
117fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    private static int readIntAttribute(@NonNull XmlPullParser parser, @NonNull String name) {
118fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann        return Integer.parseInt(parser.getAttributeValue(null, name));
119fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    }
120fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann
121fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    /**
122fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * Read an String attribute from the current element
123fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     *
124fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @param parser The parser to read from
125fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @param name The attribute name to read
126fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     *
127fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     * @return The value of the attribute or null if the attribute is not set
128fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann     */
129fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    private static String readStringAttribute(@NonNull XmlPullParser parser, @NonNull String name) {
130fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann        return parser.getAttributeValue(null, name);
131fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    }
132fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann
133fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann    /**
134a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * Read persisted state.
135a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     *
136a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param resultFile The file the results are persisted in
137a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
138a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    EventResultPersister(@NonNull File resultFile) {
139a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        mResultsFile = new AtomicFile(resultFile);
140a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        mCounter = GENERATE_NEW_ID + 1;
141a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
142a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        try (FileInputStream stream = mResultsFile.openRead()) {
143a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            XmlPullParser parser = Xml.newPullParser();
144a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            parser.setInput(stream, StandardCharsets.UTF_8.name());
145a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
146fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann            nextElement(parser);
147a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
148a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                String tagName = parser.getName();
149a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                if ("results".equals(tagName)) {
150fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                    mCounter = readIntAttribute(parser, "counter");
151a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                } else if ("result".equals(tagName)) {
152fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                    int id = readIntAttribute(parser, "id");
153fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                    int status = readIntAttribute(parser, "status");
154fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                    int legacyStatus = readIntAttribute(parser, "legacyStatus");
155fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                    String statusMessage = readStringAttribute(parser, "statusMessage");
156a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
157a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    if (mResults.get(id) != null) {
158a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        throw new Exception("id " + id + " has two results");
159a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    }
160a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
161ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
162a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                } else {
163a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    throw new Exception("unexpected tag");
164a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                }
165a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
166fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                nextElement(parser);
167a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
168a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        } catch (Exception e) {
169a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            mResults.clear();
170a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            writeState();
171a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
172a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
173a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
174a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
175a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * Add a result. If the result is an pending user action, execute the pending user action
176a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * directly and do not queue a result.
177a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     *
178a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param context The context the event was received in
179a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param intent The intent the activity received
180a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
181a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    void onEventReceived(@NonNull Context context, @NonNull Intent intent) {
182a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
183a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
184a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
185a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
186a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
187a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            return;
188a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
189a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
190a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        int id = intent.getIntExtra(EXTRA_ID, 0);
191a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
192ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann        int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
193a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
194a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        EventResultObserver observerToCall = null;
195a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        synchronized (mLock) {
196a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            int numObservers = mObservers.size();
197a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            for (int i = 0; i < numObservers; i++) {
198a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                if (mObservers.keyAt(i) == id) {
199a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    observerToCall = mObservers.valueAt(i);
200a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    mObservers.removeAt(i);
201a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
202a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    break;
203a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                }
204a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
205a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
206a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            if (observerToCall != null) {
207ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                observerToCall.onResult(status, legacyStatus, statusMessage);
208a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            } else {
209ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
210a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                writeState();
211a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
212a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
213a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
214a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
215a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
216a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * Persist current state. The persistence might be delayed.
217a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
218a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private void writeState() {
219a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        synchronized (mLock) {
220a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            mIsPersistingStateValid = false;
221a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
222a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            if (!mIsPersistScheduled) {
223a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                mIsPersistScheduled = true;
224a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
225a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                AsyncTask.execute(() -> {
226a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    int counter;
227a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    SparseArray<EventResult> results;
228a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
229a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    while (true) {
230a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        // Take snapshot of state
231a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        synchronized (mLock) {
232a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            counter = mCounter;
233a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            results = mResults.clone();
234a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            mIsPersistingStateValid = true;
235a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        }
236a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
237a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        FileOutputStream stream = null;
238a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        try {
239a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            stream = mResultsFile.startWrite();
240fcf8ab8aadbf907b69f28d457243e2fb830da023Philip P. Moltmann                            XmlSerializer serializer = Xml.newSerializer();
241a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.setOutput(stream, StandardCharsets.UTF_8.name());
242a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.startDocument(null, true);
243a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.setFeature(
244a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
245a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.startTag(null, "results");
246a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.attribute(null, "counter", Integer.toString(counter));
247a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
248a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            int numResults = results.size();
249a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            for (int i = 0; i < numResults; i++) {
250a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                serializer.startTag(null, "result");
251a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                serializer.attribute(null, "id",
252a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                        Integer.toString(results.keyAt(i)));
253a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                serializer.attribute(null, "status",
254a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                        Integer.toString(results.valueAt(i).status));
255ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                                serializer.attribute(null, "legacyStatus",
256ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                                        Integer.toString(results.valueAt(i).legacyStatus));
257a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                if (results.valueAt(i).message != null) {
258a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                    serializer.attribute(null, "statusMessage",
259a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                            results.valueAt(i).message);
260a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                }
261a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                serializer.endTag(null, "result");
262a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            }
263a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
264a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.endTag(null, "results");
265a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            serializer.endDocument();
266a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
267a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            mResultsFile.finishWrite(stream);
268a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        } catch (IOException e) {
269a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            if (stream != null) {
270a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                mResultsFile.failWrite(stream);
271a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            }
272a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
273a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            Log.e(LOG_TAG, "error writing results", e);
274a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            mResultsFile.delete();
275a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        }
276a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
277a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        // Check if there was changed state since we persisted. If so, we need to
278a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        // persist again.
279a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        synchronized (mLock) {
280a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            if (mIsPersistingStateValid) {
281a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                mIsPersistScheduled = false;
282a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                                break;
283a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                            }
284a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                        }
285a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                    }
286a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                });
287a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
288a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
289a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
290a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
291a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
292a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * Add an observer. If there is already an event for this id, call back inside of this call.
293a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     *
294a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param id       The id the observer is for or {@code GENERATE_NEW_ID} to generate a new one.
295a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param observer The observer to call back.
296a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     *
297a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @return The id for this event
298a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
299a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    int addObserver(int id, @NonNull EventResultObserver observer)
300a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            throws OutOfIdsException {
301a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        synchronized (mLock) {
3029e9d3a368eebb82dac197ac4c92e356fc342af75Philip P. Moltmann            int resultIndex = -1;
303a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
304a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            if (id == GENERATE_NEW_ID) {
3054f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann                id = getNewId();
306a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            } else {
307a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                resultIndex = mResults.indexOfKey(id);
308a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
309a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
310a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            // Check if we can instantly call back
3119e9d3a368eebb82dac197ac4c92e356fc342af75Philip P. Moltmann            if (resultIndex >= 0) {
3129e9d3a368eebb82dac197ac4c92e356fc342af75Philip P. Moltmann                EventResult result = mResults.valueAt(resultIndex);
3139e9d3a368eebb82dac197ac4c92e356fc342af75Philip P. Moltmann
314ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann                observer.onResult(result.status, result.legacyStatus, result.message);
315a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                mResults.removeAt(resultIndex);
3164f78e1fa55255a261247e9a112bc4433cd1a9ab0Philip P. Moltmann                writeState();
317a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            } else {
318a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann                mObservers.put(id, observer);
319a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            }
320a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
321a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
322a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
323a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        return id;
324a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
325a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
326a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
327a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * Remove a observer.
328a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     *
329a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * @param id The id the observer was added for
330a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
331a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    void removeObserver(int id) {
332a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        synchronized (mLock) {
333a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            mObservers.delete(id);
334a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
335a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
336a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
337a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    /**
338a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     * The status from an event.
339a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann     */
340a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    private class EventResult {
341a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        public final int status;
342ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann        public final int legacyStatus;
343a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        @Nullable public final String message;
344a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
345ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann        private EventResult(int status, int legacyStatus, @Nullable String message) {
346a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            this.status = status;
347ac9155b2b20164da52cdc0d82a6ab5c3a9d7ec38Philip P. Moltmann            this.legacyStatus = legacyStatus;
348a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann            this.message = message;
349a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann        }
350a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    }
351a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann
352a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann    class OutOfIdsException extends Exception {}
353a35df07e28e94f78fefc88d24ebfa7643b6df7e6Philip P. Moltmann}
354