DropBoxManager.java revision e9f18815218b2ff1f01ea16f2eb0dd17504a9cf3
1/*
2 * Copyright (C) 2009 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
17package android.os;
18
19import android.util.Log;
20
21import com.android.internal.os.IDropBoxManagerService;
22
23import java.io.ByteArrayInputStream;
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.IOException;
27import java.io.InputStream;
28import java.util.zip.GZIPInputStream;
29
30/**
31 * Enqueues chunks of data (from various sources -- application crashes, kernel
32 * log records, etc.).  The queue is size bounded and will drop old data if the
33 * enqueued data exceeds the maximum size.  You can think of this as a
34 * persistent, system-wide, blob-oriented "logcat".
35 *
36 * <p>You can obtain an instance of this class by calling
37 * {@link android.content.Context#getSystemService}
38 * with {@link android.content.Context#DROPBOX_SERVICE}.
39 *
40 * <p>DropBoxManager entries are not sent anywhere directly, but other system
41 * services and debugging tools may scan and upload entries for processing.
42 */
43public class DropBoxManager {
44    private static final String TAG = "DropBoxManager";
45    private final IDropBoxManagerService mService;
46
47    /** Flag value: Entry's content was deleted to save space. */
48    public static final int IS_EMPTY = 1;
49
50    /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
51    public static final int IS_TEXT = 2;
52
53    /** Flag value: Content can be decompressed with {@link java.util.zip.GZIPOutputStream}. */
54    public static final int IS_GZIPPED = 4;
55
56    /**
57     * A single entry retrieved from the drop box.
58     * This may include a reference to a stream, so you must call
59     * {@link #close()} when you are done using it.
60     */
61    public static class Entry implements Parcelable {
62        private final String mTag;
63        private final long mTimeMillis;
64
65        private final byte[] mData;
66        private final ParcelFileDescriptor mFileDescriptor;
67        private final int mFlags;
68
69        /** Create a new empty Entry with no contents. */
70        public Entry(String tag, long millis) {
71            this(tag, millis, (Object) null, IS_EMPTY);
72        }
73
74        /** Create a new Entry with plain text contents. */
75        public Entry(String tag, long millis, String text) {
76            this(tag, millis, (Object) text.getBytes(), IS_TEXT);
77        }
78
79        /**
80         * Create a new Entry with byte array contents.
81         * The data array must not be modified after creating this entry.
82         */
83        public Entry(String tag, long millis, byte[] data, int flags) {
84            this(tag, millis, (Object) data, flags);
85        }
86
87        /**
88         * Create a new Entry with streaming data contents.
89         * Takes ownership of the ParcelFileDescriptor.
90         */
91        public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
92            this(tag, millis, (Object) data, flags);
93        }
94
95        /**
96         * Create a new Entry with the contents read from a file.
97         * The file will be read when the entry's contents are requested.
98         */
99        public Entry(String tag, long millis, File data, int flags) throws IOException {
100            this(tag, millis, (Object) ParcelFileDescriptor.open(
101                    data, ParcelFileDescriptor.MODE_READ_ONLY), flags);
102        }
103
104        /** Internal constructor for CREATOR.createFromParcel(). */
105        private Entry(String tag, long millis, Object value, int flags) {
106            if (tag == null) throw new NullPointerException();
107            if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
108
109            mTag = tag;
110            mTimeMillis = millis;
111            mFlags = flags;
112
113            if (value == null) {
114                mData = null;
115                mFileDescriptor = null;
116            } else if (value instanceof byte[]) {
117                mData = (byte[]) value;
118                mFileDescriptor = null;
119            } else if (value instanceof ParcelFileDescriptor) {
120                mData = null;
121                mFileDescriptor = (ParcelFileDescriptor) value;
122            } else {
123                throw new IllegalArgumentException();
124            }
125        }
126
127        /** Close the input stream associated with this entry. */
128        public void close() {
129            try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
130        }
131
132        /** @return the tag originally attached to the entry. */
133        public String getTag() { return mTag; }
134
135        /** @return time when the entry was originally created. */
136        public long getTimeMillis() { return mTimeMillis; }
137
138        /** @return flags describing the content returned by @{link #getInputStream()}. */
139        public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
140
141        /**
142         * @param maxBytes of string to return (will truncate at this length).
143         * @return the uncompressed text contents of the entry, null if the entry is not text.
144         */
145        public String getText(int maxBytes) {
146            if ((mFlags & IS_TEXT) == 0) return null;
147            if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
148
149            InputStream is = null;
150            try {
151                is = getInputStream();
152                byte[] buf = new byte[maxBytes];
153                int readBytes = 0;
154                int n = 0;
155                while (n >= 0 && (readBytes += n) < maxBytes) {
156                    n = is.read(buf, readBytes, maxBytes - readBytes);
157                }
158                return new String(buf, 0, readBytes);
159            } catch (IOException e) {
160                return null;
161            } finally {
162                try { if (is != null) is.close(); } catch (IOException e) {}
163            }
164        }
165
166        /** @return the uncompressed contents of the entry, or null if the contents were lost */
167        public InputStream getInputStream() throws IOException {
168            InputStream is;
169            if (mData != null) {
170                is = new ByteArrayInputStream(mData);
171            } else if (mFileDescriptor != null) {
172                is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
173            } else {
174                return null;
175            }
176            return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
177        }
178
179        public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
180            public Entry[] newArray(int size) { return new Entry[size]; }
181            public Entry createFromParcel(Parcel in) {
182                return new Entry(
183                        in.readString(), in.readLong(), in.readValue(null), in.readInt());
184            }
185        };
186
187        public int describeContents() {
188            return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
189        }
190
191        public void writeToParcel(Parcel out, int flags) {
192            out.writeString(mTag);
193            out.writeLong(mTimeMillis);
194            if (mFileDescriptor != null) {
195                out.writeValue(mFileDescriptor);
196            } else {
197                out.writeValue(mData);
198            }
199            out.writeInt(mFlags);
200        }
201    }
202
203    /** {@hide} */
204    public DropBoxManager(IDropBoxManagerService service) { mService = service; }
205
206    /**
207     * Create a dummy instance for testing.  All methods will fail unless
208     * overridden with an appropriate mock implementation.  To obtain a
209     * functional instance, use {@link android.content.Context#getSystemService}.
210     */
211    protected DropBoxManager() { mService = null; }
212
213    /**
214     * Stores human-readable text.  The data may be discarded eventually (or even
215     * immediately) if space is limited, or ignored entirely if the tag has been
216     * blocked (see {@link #isTagEnabled}).
217     *
218     * @param tag describing the type of entry being stored
219     * @param data value to store
220     */
221    public void addText(String tag, String data) {
222        try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
223    }
224
225    /**
226     * Stores binary data, which may be ignored or discarded as with {@link #addText}.
227     *
228     * @param tag describing the type of entry being stored
229     * @param data value to store
230     * @param flags describing the data
231     */
232    public void addData(String tag, byte[] data, int flags) {
233        if (data == null) throw new NullPointerException();
234        try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
235    }
236
237    /**
238     * Stores the contents of a file, which may be ignored or discarded as with
239     * {@link #addText}.
240     *
241     * @param tag describing the type of entry being stored
242     * @param file to read from
243     * @param flags describing the data
244     * @throws IOException if the file can't be opened
245     */
246    public void addFile(String tag, File file, int flags) throws IOException {
247        if (file == null) throw new NullPointerException();
248        Entry entry = new Entry(tag, 0, file, flags);
249        try {
250            mService.add(new Entry(tag, 0, file, flags));
251        } catch (RemoteException e) {
252            // ignore
253        } finally {
254            entry.close();
255        }
256    }
257
258    /**
259     * Checks any blacklists (set in system settings) to see whether a certain
260     * tag is allowed.  Entries with disabled tags will be dropped immediately,
261     * so you can save the work of actually constructing and sending the data.
262     *
263     * @param tag that would be used in {@link #addText} or {@link #addFile}
264     * @return whether events with that tag would be accepted
265     */
266    public boolean isTagEnabled(String tag) {
267        try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
268    }
269
270    /**
271     * Gets the next entry from the drop box *after* the specified time.
272     * Requires android.permission.READ_LOGS.  You must always call
273     * {@link Entry#close()} on the return value!
274     *
275     * @param tag of entry to look for, null for all tags
276     * @param msec time of the last entry seen
277     * @return the next entry, or null if there are no more entries
278     */
279    public Entry getNextEntry(String tag, long msec) {
280        try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
281    }
282
283    // TODO: It may be useful to have some sort of notification mechanism
284    // when data is added to the dropbox, for demand-driven readers --
285    // for now readers need to poll the dropbox to find new data.
286}
287