DropBoxManager.java revision f18a01c77e78209b74e34d05cfb352fa4a92db5f
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 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                return new String(buf, 0, Math.max(0, is.read(buf)));
154            } catch (IOException e) {
155                return null;
156            } finally {
157                try { if (is != null) is.close(); } catch (IOException e) {}
158            }
159        }
160
161        /** @return the uncompressed contents of the entry, or null if the contents were lost */
162        public InputStream getInputStream() throws IOException {
163            InputStream is;
164            if (mData != null) {
165                is = new ByteArrayInputStream(mData);
166            } else if (mFileDescriptor != null) {
167                is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
168            } else {
169                return null;
170            }
171            return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
172        }
173
174        public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
175            public Entry[] newArray(int size) { return new Entry[size]; }
176            public Entry createFromParcel(Parcel in) {
177                return new Entry(
178                        in.readString(), in.readLong(), in.readValue(null), in.readInt());
179            }
180        };
181
182        public int describeContents() {
183            return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
184        }
185
186        public void writeToParcel(Parcel out, int flags) {
187            out.writeString(mTag);
188            out.writeLong(mTimeMillis);
189            if (mFileDescriptor != null) {
190                out.writeValue(mFileDescriptor);
191            } else {
192                out.writeValue(mData);
193            }
194            out.writeInt(mFlags);
195        }
196    }
197
198    /** {@hide} */
199    public DropBoxManager(IDropBoxManagerService service) { mService = service; }
200
201    /**
202     * Create a dummy instance for testing.  All methods will fail unless
203     * overridden with an appropriate mock implementation.  To obtain a
204     * functional instance, use {@link android.content.Context#getSystemService}.
205     */
206    protected DropBoxManager() { mService = null; }
207
208    /**
209     * Stores human-readable text.  The data may be discarded eventually (or even
210     * immediately) if space is limited, or ignored entirely if the tag has been
211     * blocked (see {@link #isTagEnabled}).
212     *
213     * @param tag describing the type of entry being stored
214     * @param data value to store
215     */
216    public void addText(String tag, String data) {
217        try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
218    }
219
220    /**
221     * Stores binary data, which may be ignored or discarded as with {@link #addText}.
222     *
223     * @param tag describing the type of entry being stored
224     * @param data value to store
225     * @param flags describing the data
226     */
227    public void addData(String tag, byte[] data, int flags) {
228        if (data == null) throw new NullPointerException();
229        try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
230    }
231
232    /**
233     * Stores data read from a file descriptor.  The data may be ignored or
234     * discarded as with {@link #addText}.  You must close your
235     * ParcelFileDescriptor object after calling this method!
236     *
237     * @param tag describing the type of entry being stored
238     * @param fd file descriptor to read from
239     * @param flags describing the data
240     */
241    public void addFile(String tag, ParcelFileDescriptor fd, int flags) {
242        if (fd == null) throw new NullPointerException();
243        try { mService.add(new Entry(tag, 0, fd, flags)); } catch (RemoteException e) {}
244    }
245
246    /**
247     * Checks any blacklists (set in system settings) to see whether a certain
248     * tag is allowed.  Entries with disabled tags will be dropped immediately,
249     * so you can save the work of actually constructing and sending the data.
250     *
251     * @param tag that would be used in {@link #addText} or {@link #addFile}
252     * @return whether events with that tag would be accepted
253     */
254    public boolean isTagEnabled(String tag) {
255        try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
256    }
257
258    /**
259     * Gets the next entry from the drop box *after* the specified time.
260     * Requires android.permission.READ_LOGS.  You must always call
261     * {@link Entry#close()} on the return value!
262     *
263     * @param tag of entry to look for, null for all tags
264     * @param msec time of the last entry seen
265     * @return the next entry, or null if there are no more entries
266     */
267    public Entry getNextEntry(String tag, long msec) {
268        try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
269    }
270
271    // TODO: It may be useful to have some sort of notification mechanism
272    // when data is added to the dropbox, for demand-driven readers --
273    // for now readers need to poll the dropbox to find new data.
274}
275