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