Commits

Robert Craig committed 298d9ac

Major changes.
- Moved all fragments out into own separate files
- Changed initial menu view to include headings
- Log denials fragment layout redone.

  • Participants
  • Parent commits 482698a

Comments (0)

Files changed (24)

File res/drawable-hdpi/ic_menu_moreoverflow.png

Added
New image

File res/drawable-hdpi/ic_menu_refresh.png

Added
New image

File res/drawable-ldpi/ic_menu_refresh.png

Added
New image

File res/drawable-mdpi/ic_menu_moreoverflow.png

Added
New image

File res/drawable-mdpi/ic_menu_refresh.png

Added
New image

File res/drawable-xhdpi/ic_menu_refresh.png

Added
New image

File res/layout/avc_denied_menu_toast.xml

-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:id="@+id/avc_denied_menu_toast"
-              android:orientation="horizontal"
-              android:layout_width="fill_parent"
-              android:layout_height="fill_parent"
-              android:padding="10dp"
-              android:background="#DAAA"
-              >
-    <TextView android:id="@+id/text"
-              android:layout_width="wrap_content"
-              android:layout_height="fill_parent"
-              android:textColor="#FFF"
-              android:textSize="30sp"
-              />
-</LinearLayout>

File res/layout/avc_denied_reader.xml

-<?xml version="1.0" encoding="utf-8"?>
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/linearLayout1"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="vertical" >
-
-        <RelativeLayout
-            android:id="@+id/linearLayout2"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" >
-
-            <Button
-                android:id="@+id/refreshButton"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_centerVertical="true"
-                android:layout_alignParentRight="@+id/linearLayout2"
-                android:text="@string/avc_denied_log_refresh_button_label"
-                android:layout_toRightOf="@+id/textView1" />
-
-        </RelativeLayout>
-        
-        <ScrollView
-            android:id="@+id/avcLogScrollView"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent">    
-          
-            <TextView
-                android:id="@+id/avcLogTextView"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:text="@string/avc_denied_log_reload_msg" 
-                android:textIsSelectable="true"/>
-            
-        </ScrollView>
-            
-        
-    </LinearLayout>

File res/layout/log_denied_reader.xml

+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/linearLayout1"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical" >
+
+  <!-- 'choose a refresh' helpful hint -->
+  <TextView
+      android:id="@+id/avcLogTextViewI"
+      android:text="@string/avc_denied_log_reload_msg"
+      android:gravity="center_vertical|center_horizontal"
+      android:textColor="#FFF"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent"/>
+
+  <!-- Holds the log messages -->
+  <ScrollView
+      android:id="@+id/avcLogScrollView"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
+
+    <TextView
+        android:id="@+id/avcLogTextView"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:textIsSelectable="true"/>
+
+  </ScrollView>
+
+</LinearLayout>

File res/layout/selinux_manage_booleans_item.xml

     android:orientation="horizontal"
     android:gravity="center_vertical">
 
-    <TextView android:id="@+id/text"
-	    android:layout_width="wrap_content"
-	    android:layout_height="wrap_content"
-	    android:layout_weight="1" />
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
 
-    <CheckBox android:id="@+id/checkbox"
+    <CheckBox
+        android:id="@+id/checkbox"
         android:layout_width="wrap_content"
-	    android:layout_height="wrap_content"
-	    android:layout_gravity="right"
-	    android:focusable="false" />
+        android:layout_height="wrap_content"
+        android:layout_gravity="right"
+        android:focusable="false" />
 
 </LinearLayout>

File res/menu/main_activity.xml~

+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+  <item 
+      android:id="@+id/refreshButtonAVC" 
+      android:showAsAction="always|withText"
+      android:title="AVC"
+      android:icon="@drawable/ic_menu_refresh" />
+
+  <item 
+      android:id="@+id/refreshButtonMAC" 
+      android:showAsAction="always|withText"
+      android:title="MAC"
+      android:icon="@drawable/ic_menu_refresh" />
+
+  <item 
+      android:id="@+id/saveButton" 
+      android:showAsAction="always|withText"
+      android:title="Save"
+      android:icon="@android:drawable/ic_menu_save" />
+
+</menu>

File res/menu/menu_action_bar.xml

+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+      
+  <!-- save to sdcard action bar item -->
+  <item android:id="@+id/saveButton"
+        android:title="Save"
+        android:icon="@android:drawable/ic_menu_save"
+        android:showAsAction="always" />
+  
+  <!-- action bar submenu that has avc/clear items -->
+  <item android:id="@+id/submenu"
+        android:icon="@drawable/ic_menu_moreoverflow"
+        android:title="Logs"
+        android:showAsAction="always" >
+    <menu>
+      <item android:id="@+id/AVC"
+            android:icon="@drawable/ic_menu_refresh"
+            android:title="AVC" />
+      <item android:id="@+id/clear"
+            android:icon="@android:drawable/ic_menu_close_clear_cancel"
+            android:title="Clear" />
+    </menu>
+  </item>
+  
+</menu>

File res/values/strings.xml

 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="app_name">SEAndroidManager</string>
-    <string name="activity_name">SEAndroidManager</string>
-    <string name="app_label">SEAndroid Manager</string>
-    <string name="app_label_launcher">SEAndroid Manager</string>
-    <string name="selinux_disabled_fragment_title">SELinux Disabled</string>
-    <string name="selinux_enforcing_fragment_title">Change SELinux Enforcing Mode</string>
-    <string name="selinux_boolean_fragment_title">Manage SELinux Booleans</string>
-    <string name="selinux_not_enabled">SELinux is not enabled</string>
-    <string name="selinux_status">SELinux status</string>
-    <string name="selinux_title">SELinux</string>
-    <string name="selinux_set_enforcing">SELinux Enforcing Mode</string>
-    <string name="selinux_set_enforcing_summary">Toggle SELinux Enforcing Mode</string>
-    <string name="selinux_manage_booleans">SELinux Booleans</string>
-    <string name="selinux_manage_booleans_summary">Manage SELinux Booleans</string>
-    <string name="selinux_no_booleans">No Booleans</string>
-    <string name="avc_denied_log_fragment_title">AVC Denied Logs</string>
-    <string name="avc_denied_log_reload_msg">Press The "Refresh" Button to Update AVC Denied Messages</string>
-    <string name="avc_denied_log_refresh_button_label">Refresh</string>
-    <string name="avc_denied_log_refresh_text">Refresh AVC Logs</string>
-    <string name="avc_progress_bar_message">Loading&#8230;</string>
-    <string name="avc_logs_key">AVCLogs</string>
-    <string name="avc_dialog_title_error">Error</string>
-    <string name="avc_dialog_title_info">Info</string>
-    <string name="avc_ok_button_text">OK</string>
-    <string name="avc_option_save_button_title">Save</string>
+    <string name="app_name">SEManager</string>
+    <string name="activity_name">SEManager</string>
+    <string name="avc_denied_log_empty_log_msg">No Logs To Save</string>
+    <string name="avc_denied_log_file_extension">.log</string>
     <string name="avc_denied_log_filename">avc_denied_logs</string>
-    <string name="avc_denied_log_onsave_dialog_title">File Saved</string>
+    <string name="avc_denied_log_fragment_title">Log Messages</string>
     <string name="avc_denied_log_onsave_dialog_message">Logs Saved To: </string>
-    <string name="avc_logs_pulled_key">logsPulled</string>
-    <string name="avc_denied_log_empty_log_msg">No Logs To Save</string>
+    <string name="avc_denied_log_reload_msg">Choose a "Refresh" Button to Update Messages</string>
     <!--
-		In java and in reg expressions "\" is an escape
+	In java and in reg expressions "\" is an escape
      	character, so it takes 4 to get 1
      	See http://www.regular-expressions.info/java.html for
      	more info
      	a filename.
     -->
     <string name="avc_denied_timestamp_format_regex">[\\\\,: ]</string>
-    
     <!-- Everything matched in the regular expression above is replaced with the below string -->
     <string name="avc_denied_timestamp_replacement_char">_</string>
-    <string name="avc_denied_log_file_extension">.log</string>
+    <string name="avc_dialog_title_error">Error</string>
+    <string name="avc_dialog_title_info">Info</string>
+    <string name="avc_progress_bar_message">Loading&#8230;</string>
+    <string name="avc_ok_button_text">OK</string>
+    <string name="avc_title">AVC Denied Log</string>
+    <string name="log_fragment_title">Logs</string>
+    <string name="log_reload_msg">Press One of The "Refresh" Buttons to Update Messages</string>
+    <string name="manage_selinux_header">MANAGE</string>
+    <string name="policy_builder_header">POLICY TOOLS</string>
+    <string name="selinux_boolean_fragment_title">Booleans</string>
+    <string name="selinux_disabled_fragment_title">SELinux Disabled</string>
+    <string name="selinux_enforcing_fragment_title">Enforcing Mode</string>
+    <string name="selinux_manage_booleans">SELinux Booleans</string>
+    <string name="selinux_not_enabled">SELinux is not enabled</string>
+    <string name="selinux_no_booleans">No Booleans</string>
+    <string name="selinux_set_enforcing">SELinux Mode</string>
+    <string name="selinux_set_enforcing_summary">Toggle SELinux Enforcing Mode</string>
 </resources>
 

File res/xml/enabled_headers.xml

 <preference-headers
     xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <header android:title="@string/manage_selinux_header"/>
+
     <header
-        android:fragment="com.android.seandroid_manager.SEAndroidManager$SELinuxEnforcingFragment"
+        android:fragment="com.android.seandroid_manager.SELinuxEnforcingFragment"
         android:title="@string/selinux_enforcing_fragment_title">
     </header>
 
     <header
-        android:fragment="com.android.seandroid_manager.SEAndroidManager$SELinuxBooleanFragment"
+        android:fragment="com.android.seandroid_manager.SELinuxBooleanFragment"
         android:title="@string/selinux_boolean_fragment_title">
     </header>
 
+    <header android:title="@string/policy_builder_header" />
+
     <header
-        android:fragment="com.android.seandroid_manager.SEAndroidManager$AVCDeniedReaderFragment"
-        android:title="@string/avc_denied_log_fragment_title">
+        android:fragment="com.android.seandroid_manager.LogDeniedReaderFragment"
+        android:title="@string/log_fragment_title">
     </header>
 
 </preference-headers>

File src/com/android/seandroid_manager/AVCCallback.java

-
-package com.android.seandroid_manager;
-
-/**
- * Used to handle events from parsing the kernel logs looking for AVC messages.
- */
-public abstract class AVCCallback {
-
-    /**
-     * Before parsing begins, this function is called. By default nothing
-     * happens, as this function is not required for proper function, but can be
-     * overridden by subclass for perhaps starting a progress dialog.
-     */
-    public void onStart() {
-    }
-
-    /**
-     * For each AVC denied message encountered, this callback is called. This
-     * must be implemented, each raw kernel AVC message is passed to this
-     * callback. From here further processing can be done.
-     * 
-     * @param message The exact AVC denied message encountered while parsing.
-     */
-    public abstract void onEvent(String logMessage);
-
-    /**
-     * This is called at the end of parsing, when no more messages are found. By
-     * default nothing happens, as this function is not required for proper
-     * function, but can be overridden by a subclass for perhaps starting a
-     * progress dialog.
-     */
-    public void onFinish() {
-    }
-
-    /**
-     * Called when an exception occurs.
-     * The exception generated while trying to get the kernel ring buffer.
-     * 
-     * @param e The exception generated
-     */
-    public void onException(Exception e) {
-    }
-
-}

File src/com/android/seandroid_manager/AVCReader.java

-
-package com.android.seandroid_manager;
-
-import java.io.ByteArrayInputStream;
-import java.util.Scanner;
-
-/**
- * Reads the kernel message log and sends AVCDenied messages to the registered
- * handler.
- */
-public class AVCReader extends Thread {
-
-    private String avcSearchTag = "avc";
-    private AVCCallback callback = null;
-
-    public AVCReader(AVCCallback avcHandler) {
-        super();
-        callback = avcHandler;
-    }
-
-    /**
-     * Reads from kernel logs, parses them and calls the callbacks set by
-     * AVCReader(AVCCallback avcHandler) for each avc denied message
-     * encountered.
-     */
-    private void parseLogs() {
-
-        int value;
-        byte logs[];
-        String line;
-
-        callback.onStart();
-
-        try {
-            value = KLogCtl.kLogCtl(10, null, 0);
-            logs = new byte[value];
-            value = KLogCtl.kLogCtl(3, logs, value);
-
-            Scanner stream = new Scanner(new ByteArrayInputStream(logs));
-
-            while (stream.hasNextLine() && !interrupted()) {
-                line = stream.nextLine();
-                if (line.contains(avcSearchTag)) {
-                    callback.onEvent(line);
-                }
-            }
-        } catch (Exception e) {
-            callback.onException(e);
-        } finally {
-            callback.onFinish();
-        }
-        return;
-    }
-
-    @Override
-    public void run() {
-        parseLogs();
-    }
-
-}

File src/com/android/seandroid_manager/LogCallback.java

+
+package com.android.seandroid_manager;
+
+/**
+ * Used to handle events from parsing logs looking for messages.
+ */
+public abstract class LogCallback {
+
+    /**
+     * Before parsing begins, this function is called. By default nothing
+     * happens, as this function is not required for proper function, but can be
+     * overridden by subclass for perhaps starting a progress dialog.
+     */
+    public void onStart() {
+    }
+
+    /**
+     * For each message encountered, this callback is called. This
+     * must be implemented, each raw message is passed to this
+     * callback. From here further processing can be done.
+     * 
+     * @param message The exact message encountered while parsing.
+     */
+    public abstract void onEvent(String logMessage);
+
+    /**
+     * This is called at the end of parsing, when no more messages are found. By
+     * default nothing happens, as this function is not required for proper
+     * function, but can be overridden by a subclass for perhaps starting a
+     * progress dialog.
+     */
+    public void onFinish() {
+    }
+
+    /**
+     * Called when an exception occurs.
+     * 
+     * @param e The exception generated
+     */
+    public void onException(Exception e) {
+    }
+
+}

File src/com/android/seandroid_manager/LogDeniedReaderFragment.java

+   
+package com.android.seandroid_manager;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.seandroid_manager.logreaders.KLogReader;
+import com.android.seandroid_manager.logreaders.LogcatReader;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.Date;
+
+public class LogDeniedReaderFragment extends Fragment {
+
+    private TextView mMessageLog;
+    private TextView mDefaultMessage;
+    private ScrollView mScrollView;
+    private ProgressDialog mProgressDialog;
+    private Handler mHandler;
+    private Activity mActivity;
+    private ActionBar mActionBar;
+    
+    private String defaultText;
+    private String replacement;
+    private String regExp;
+    private String fileExtension;
+    private String fileOnSave;
+    private String logPrefix;
+    private String emptyLogMessage;
+    private String dialogInfo;
+    private String dialogError;
+    private String logsKey;
+    private String okButtonText;
+    
+    private final static int DIALOG_ERROR = 0;
+    private final static int DIALOG_INFO = 1;
+
+    private LogCallback handleMessage = new LogCallback() {
+
+        @Override
+        public void onStart() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mProgressDialog.show();
+                }
+            });
+        }
+
+        @Override
+        public void onEvent(final String logMessage) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mMessageLog.append(logMessage + "\n\n");
+                }
+            });
+        }
+            
+        @Override
+        public void onFinish() {
+            mScrollView.post(new Runnable() {
+                @Override
+                public void run() {
+                    mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
+                    mProgressDialog.cancel();
+                }
+           });
+        }
+
+        @Override
+        public void onException(Exception e) {
+            showDialog(e.getMessage(), DIALOG_ERROR);
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                   Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        View v = inflater.inflate(R.layout.log_denied_reader, container, false);
+
+        mMessageLog = (TextView) v.findViewById(R.id.avcLogTextView);
+        mDefaultMessage = (TextView) v.findViewById(R.id.avcLogTextViewI);
+        
+        mScrollView = (ScrollView) v.findViewById(R.id.avcLogScrollView);
+        
+        mHandler = new Handler();
+        
+        mActivity = getActivity();
+        
+        mActionBar = mActivity.getActionBar();
+        
+        defaultText = getString(R.string.avc_denied_log_reload_msg);
+        replacement = getString(R.string.avc_denied_timestamp_replacement_char);
+        fileExtension = getString(R.string.avc_denied_log_file_extension);
+        regExp = getString(R.string.avc_denied_timestamp_format_regex);
+        fileOnSave = getString(R.string.avc_denied_log_onsave_dialog_message);
+        dialogInfo = getString(R.string.avc_dialog_title_info);
+        dialogError = getString(R.string.avc_dialog_title_error);
+        emptyLogMessage = getString(R.string.avc_denied_log_empty_log_msg);
+        okButtonText = getString(R.string.avc_ok_button_text);
+        
+        mProgressDialog = new ProgressDialog(mActivity);
+        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+        mProgressDialog.setMessage(getString(R.string.avc_progress_bar_message));
+        
+        if (savedInstanceState != null) {
+            CharSequence oldLogs = savedInstanceState.getCharSequence(logsKey);
+            String title = savedInstanceState.getString("title");
+            
+            if (title != null) {
+                mActionBar.setTitle(title);
+            }
+            
+            if (oldLogs != null && !oldLogs.toString().equals("")) {
+                mDefaultMessage.setVisibility(View.GONE);
+                mMessageLog.setText(oldLogs);
+            } else {
+                mDefaultMessage.setVisibility(View.VISIBLE);
+            }
+        }
+        
+        setHasOptionsMenu(true);
+        return v;
+    }
+    
+    @Override
+    public void onSaveInstanceState(Bundle savedInstanceState) {
+        savedInstanceState.putCharSequence(logsKey, mMessageLog.getText());
+        savedInstanceState.putString("title", mActionBar.getTitle().toString());
+        super.onSaveInstanceState(savedInstanceState);
+    }
+    
+    /**
+     * Displays a pop up ERROR dialog. Safe to call from non gui thread.
+     *
+     * @param message
+     * @param type The type of message to be displayed, see DIALOG_ERROR and
+     *            friends.
+     */
+    private void showDialog(final String message, final int type) {
+
+        mHandler.post(new Runnable() {
+
+            @Override
+            public void run() {
+                String title = (type == DIALOG_INFO) ? dialogInfo : dialogError;
+
+                AlertDialog alertDialog = new AlertDialog.Builder(mActivity).create();
+                alertDialog.setTitle(title);
+                alertDialog.setMessage(message);
+                
+                alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, okButtonText,
+                                      new DialogInterface.OnClickListener() {
+                                          public void onClick(DialogInterface dialog, int which) {
+                                              // intentionally left empty
+                                          }
+                                      });
+                
+                // alertDialog
+                alertDialog.show();
+                return;
+            }
+         });
+    }
+
+    /**
+     * Saves the message logs to disk. This function runs on a
+     * separate thread.
+     *
+     * @param filename The filename to save the logs as
+     */
+    private void saveLogs(final String filename) {
+
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    final File file =
+                        new File(Environment.getExternalStorageDirectory(), filename);
+
+                    FileOutputStream fos = new FileOutputStream(file);
+                    CharSequence c = mMessageLog.getText();
+                    fos.write(c.toString().getBytes());
+                    fos.flush();
+                    fos.close();
+                    showDialog(fileOnSave + filename, DIALOG_INFO);
+                }
+                catch (FileNotFoundException e) {
+                    showDialog(e.toString(), DIALOG_ERROR);
+                }
+                catch (IOException e) {
+                    showDialog(e.toString(), DIALOG_ERROR);
+                }
+            }
+        }).run();
+    }
+
+    /**
+     * Create the action bar items
+     */
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.menu_action_bar, menu);
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    /**
+     * Handle when one of the action bar items is selected
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+
+        switch (item.getItemId()) {
+            case R.id.saveButton:
+                // save the log to the sdcard
+                int length = mMessageLog.getText().length();
+                if (length != 0) {
+                    
+                    String fileName = logPrefix + replacement +
+                        DateFormat.getDateTimeInstance().format(new Date()).toLowerCase() +
+                        fileExtension;
+
+                    // See res/strings.xml for details on regExp formatting
+                    fileName = fileName.replaceAll(regExp, replacement);
+                    saveLogs(fileName);
+                } else if (length == 0) {
+                    showDialog(emptyLogMessage, DIALOG_INFO);
+                }
+                else {
+                    showDialog(defaultText, DIALOG_ERROR);
+                }
+                return true;
+
+            case R.id.AVC:
+                // display all the avc denials
+                mActionBar.setTitle(R.string.avc_title);
+                mDefaultMessage.setVisibility(View.GONE);
+                logPrefix = getString(R.string.avc_denied_log_filename);
+                mMessageLog.setText(null);
+                KLogReader logReader = new KLogReader(handleMessage, "avc");
+                logReader.start();
+                return true;
+                
+            case R.id.clear:
+                // just clear the screen
+                mActionBar.setTitle(R.string.avc_denied_log_fragment_title);
+                mDefaultMessage.setVisibility(View.VISIBLE);
+                mMessageLog.setText(null);
+                return true;
+
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+}

File src/com/android/seandroid_manager/SEAndroidManager.java

 
 package com.android.seandroid_manager;
 
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.app.ListFragment;
-import android.app.ProgressDialog;
+//import android.app.Fragment;
+//import android.app.ListFragment;
+//import android.content.Intent;
+
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.SELinux;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
 import android.preference.PreferenceActivity;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
+import android.os.SELinux;
 import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.ScrollView;
+import android.widget.ListAdapter;
 import android.widget.TextView;
-import android.provider.Settings;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.util.Date;
+
+import java.util.ArrayList;
 import java.util.List;
 
 public class SEAndroidManager extends PreferenceActivity {
 
+    private List<Header> mHeaders;
+    private Header mFirstHeader;
+
     @Override
     public void onBuildHeaders(List<Header> target) {
         if (SELinux.isSELinuxEnabled()) {
             loadHeadersFromResource(R.xml.enabled_headers, target);
+
         } else {
             loadHeadersFromResource(R.xml.disabled_headers, target);
         }
-    }
-
-    public static class SELinuxDisabledFragment extends PreferenceFragment {
 
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
+        mHeaders = target;
 
-            addPreferencesFromResource(R.xml.selinux_not_enabled);
+        // find the first header (non CATEGORY), Android expects the first to have a fragment
+        int i = 0;
+        while (i < target.size()) {
+            Header header = target.get(i);
+            if (HeaderAdapter.getHeaderType(header) != HeaderAdapter.HEADER_TYPE_CATEGORY) {
+                mFirstHeader = header;
+                break;
+            }
+            i++;
         }
     }
 
-    public static class AVCDeniedReaderFragment extends Fragment {
-
-        private TextView logs;
-        private ScrollView scrollView;
-        private ProgressDialog progressDialog;
-        private Handler handler;
-        private Activity activity;
-
-        private String defaultText;
-        private String replacement;
-        private String regExp;
-        private String fileExtension;
-        private String fileOnSave;
-        private String logPrefix;
-        private String emptyLogMessage;
-        private String dialogInfo;
-        private String dialogError;
-        private String logsKey;
-        private String okButtonText;
-
-        private final static int DIALOG_ERROR = 0;
-        private final static int DIALOG_INFO = 1;
-
-        private AVCCallback handleMessage = new AVCCallback() {
-
-            @Override
-            public void onStart() {
-                handler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        progressDialog.show();
-                    }
-                });
-            }
-
-            @Override
-            public void onEvent(final String logMessage) {
-
-                handler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        logs.append(logMessage + "\n\n");
-                    }
-                });
-            }
-
-            @Override
-            public void onFinish() {
-                scrollView.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        scrollView.fullScroll(ScrollView.FOCUS_DOWN);
-                        progressDialog.cancel();
-                    }
-                });
-            }
-
-            @Override
-            public void onException(Exception e) {
-                showDialog(e.getMessage(), DIALOG_ERROR);
-            }
-
-        };
-
-        private OnClickListener onAVCRefreshClick = new OnClickListener() {
-
-            @Override
-            public void onClick(View v) {
-                logs.setText(null);
-                AVCReader logReader = new AVCReader(handleMessage);
-                logReader.start();
-            }
-        };
-
-        @Override
-        public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            View v = inflater.inflate(R.layout.avc_denied_reader, container, false);
-
-            Button b = (Button) v.findViewById(R.id.refreshButton);
-            b.setOnClickListener(onAVCRefreshClick);
-
-            logs = (TextView) v.findViewById(R.id.avcLogTextView);
-
-            scrollView = (ScrollView) v.findViewById(R.id.avcLogScrollView);
-
-            handler = new Handler();
+    @Override
+    public Header onGetInitialHeader() {
+        return mFirstHeader;
+    }
 
-            activity = getActivity();
+    private static class HeaderAdapter extends ArrayAdapter<Header> {
+        static final int HEADER_TYPE_CATEGORY = 0;
+        static final int HEADER_TYPE_NORMAL = 1;
+        private static final int HEADER_TYPE_COUNT = HEADER_TYPE_NORMAL + 1;
 
-            defaultText = getString(R.string.avc_denied_log_reload_msg);
-            replacement = getString(R.string.avc_denied_timestamp_replacement_char);
-            fileExtension = getString(R.string.avc_denied_log_file_extension);
-            regExp = getString(R.string.avc_denied_timestamp_format_regex);
-            fileOnSave = getString(R.string.avc_denied_log_onsave_dialog_message);
-            logPrefix = getString(R.string.avc_denied_log_filename);
-            dialogInfo = getString(R.string.avc_dialog_title_info);
-            dialogError = getString(R.string.avc_dialog_title_error);
-            logsKey = getString(R.string.avc_logs_key);
-            emptyLogMessage = getString(R.string.avc_denied_log_empty_log_msg);
-            okButtonText = getString(R.string.avc_ok_button_text);
+        private static class HeaderViewHolder {
+            TextView title;
+        }
 
-            progressDialog = new ProgressDialog(activity);
-            progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
-            progressDialog.setMessage(getString(R.string.avc_progress_bar_message));
+        private LayoutInflater mInflater;
 
-            if (savedInstanceState != null) {
-                CharSequence oldLogs = savedInstanceState.getCharSequence(logsKey);
-                if (oldLogs != null) {
-                    logs.setText(oldLogs);
-                }
+        static int getHeaderType(Header header) {
+            if (header.fragment == null && header.intent == null) {
+                return HEADER_TYPE_CATEGORY;
+            } else {
+                return HEADER_TYPE_NORMAL;
             }
-
-            setHasOptionsMenu(true);
-            return v;
         }
 
         @Override
-        public void onSaveInstanceState(Bundle savedInstanceState) {
-            savedInstanceState.putCharSequence(logsKey, logs.getText());
-            super.onSaveInstanceState(savedInstanceState);
+        public int getItemViewType(int position) {
+            Header header = getItem(position);
+            return getHeaderType(header);
         }
 
         @Override
-        public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-            inflater.inflate(R.layout.avc_denied_options, menu);
-            return;
+        public boolean areAllItemsEnabled() {
+            return false; // because of categories
         }
 
         @Override
-        public boolean onOptionsItemSelected(MenuItem item) {
-
-            int id = item.getItemId();
-
-            switch (id) {
-
-                case R.id.fileSave:
-
-                    int length = logs.getText().length();
-                    if (length != defaultText.length() &&
-                            !defaultText.equals(logs.getText())) {
-
-                        String fileName = logPrefix + replacement +
-                                DateFormat.getDateTimeInstance().format(new Date()).toLowerCase() +
-                                fileExtension;
-
-                        // See res/strings.xml for details on regExp formatting
-                        fileName = fileName.replaceAll(regExp, replacement);
-                        saveLogs(fileName);
-                    }
-                    else if (length == 0) {
-                        showDialog(emptyLogMessage, DIALOG_INFO);
-                    }
-                    else {
-                        showDialog(defaultText, DIALOG_ERROR);
-                    }
-                    break;
-                default:
-                    return super.onOptionsItemSelected(item);
-            }
-            return true;
-        }
-
-        /**
-         * Displays a pop up ERROR dialog. Safe to call from non gui thread.
-         *
-         * @param message
-         * @param type The type of message to be displayed, see DIALOG_ERROR and
-         *            friends.
-         */
-        private void showDialog(final String message, final int type) {
-
-            handler.post(new Runnable() {
-
-                @Override
-                public void run() {
-
-                    String title = (type == DIALOG_INFO) ? dialogInfo : dialogError;
-
-                    AlertDialog alertDialog = new AlertDialog.Builder(activity).create();
-                    alertDialog.setTitle(title);
-                    alertDialog.setMessage(message);
-
-                    alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, okButtonText,
-                            new DialogInterface.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int which) {
-                                    // intentionally left empty
-                                }
-                            });
-
-                    // alertDialog
-                    alertDialog.show();
-                    return;
-                }
-            });
-        }
-
-        /**
-         * Saves the AVC denied message logs to disk. This function runs on a
-         * separate thread.
-         *
-         * @param filename The filename to save the logs as
-         */
-        private void saveLogs(final String filename) {
-
-            new Thread(new Runnable() {
-                @Override
-                public void run() {
-
-                    try {
-
-                        final File file =
-                                new File(Environment.getExternalStorageDirectory(), filename);
-
-                        FileOutputStream fos = new FileOutputStream(file);
-                        CharSequence c = logs.getText();
-                        fos.write(c.toString().getBytes());
-                        fos.flush();
-                        fos.close();
-                        showDialog(fileOnSave + filename, DIALOG_INFO);
-
-                    }
-                    catch (FileNotFoundException e) {
-
-                        showDialog(e.toString(), DIALOG_ERROR);
-                    }
-                    catch (IOException e) {
-
-                        showDialog(e.toString(), DIALOG_ERROR);
-                    }
-                }
-            }).run();
-        }
-    }
-
-    public static class SELinuxEnforcingFragment extends PreferenceFragment {
-
-        private static final String KEY_SELINUX_ENFORCING = "selinux_enforcing";
-        private CheckBoxPreference mSELinuxToggleEnforce;
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            addPreferencesFromResource(R.xml.selinux_enforcing_fragment);
-
-            mSELinuxToggleEnforce = (CheckBoxPreference) getPreferenceScreen().findPreference(
-                    KEY_SELINUX_ENFORCING);
-            mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
+        public boolean isEnabled(int position) {
+            return getItemViewType(position) != HEADER_TYPE_CATEGORY;
         }
 
         @Override
-        public void onResume() {
-            super.onResume();
-
-            if (mSELinuxToggleEnforce != null) {
-                mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
-            }
+        public int getViewTypeCount() {
+            return HEADER_TYPE_COUNT;
         }
 
         @Override
-        public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
-                Preference preference) {
-            final String key = preference.getKey();
-            if (preference == mSELinuxToggleEnforce) {
-                SELinux.setSELinuxEnforce(!SELinux.isSELinuxEnforced());
-                mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
-                saveEnforcing();
-            }
+        public boolean hasStableIds() {
             return true;
         }
 
-        private void saveEnforcing()
-        {
-            String enforcing = SELinux.isSELinuxEnforced() ? "1" : "0";
-            Settings.Secure.putString(getActivity().getContentResolver(),
-                                      Settings.Secure.SELINUX_ENFORCING,
-                                      enforcing);
+        public HeaderAdapter(Context context, List<Header> objects) {
+            super(context, 0, objects);
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         }
-    }
-
-    public static class SELinuxBooleanFragment extends ListFragment {
-
-        private SharedPreferences mPrefs;
-        private myBooleanAdapter mAdapter;
-
-        private class myBooleanAdapter extends ArrayAdapter<String> {
-            private final LayoutInflater mInflater;
-            private String[] mBooleans;
-
-            public myBooleanAdapter(Context context, int textViewResourceId, String[] items) {
-                super(context, textViewResourceId, items);
-                mInflater = (LayoutInflater) context
-                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-                mBooleans = items;
-            }
 
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                final ViewHolder holder;
-                if (convertView  == null) {
-                    convertView = mInflater.inflate(R.layout.selinux_manage_booleans_item, parent, 
-                                                    false);
-                    holder = new ViewHolder();
-                    holder.tx = (TextView) convertView.findViewById(R.id.text);
-                    holder.cb = (CheckBox) convertView.findViewById(R.id.checkbox);
-                    convertView.setTag(holder);
-                } else {
-                    holder = (ViewHolder) convertView.getTag();
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            HeaderViewHolder holder;
+            Header header = getItem(position);
+            int headerType = getHeaderType(header);
+            View view = null;
+
+            if (convertView == null) {
+                holder = new HeaderViewHolder();
+                switch (headerType) {
+                    case HEADER_TYPE_CATEGORY:
+                        view = new TextView(getContext(), null,
+                                android.R.attr.listSeparatorTextViewStyle);
+                        holder.title = (TextView) view;
+                        break;
+
+                    case HEADER_TYPE_NORMAL:
+                        view = mInflater.inflate(
+                                com.android.internal.R.layout.simple_list_item_1, parent,
+                                false);
+
+                        holder.title = (TextView)
+                                view.findViewById(com.android.internal.R.id.text1);
+
+                        holder.title.setPadding(25,0,0,0);
+                        break;
                 }
 
-                String name = mBooleans[position];
-                holder.tx.setText(name);
-                holder.cb.setChecked(SELinux.getBooleanValue(name));
-                holder.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
-                        @Override
-                        public void onCheckedChanged(CompoundButton buttonView,
-                                                     boolean isChecked) {
-                            String name = (String)holder.tx.getText();
-                            SELinux.setBooleanValue(name, isChecked);
-                            holder.cb.setChecked(SELinux.getBooleanValue(name));
-                            saveBooleans();
-                        }
-                    });
-                return convertView;
-            }
+                holder.title.setText(header.getTitle(getContext().getResources()));
+                view.setTag(holder);
 
-            private class ViewHolder {
-                TextView tx;
-                CheckBox cb;
+            } else {
+                view = convertView;
+                holder = (HeaderViewHolder) view.getTag();
             }
 
-            private void saveBooleans()
-            {
-                String[] mNames = SELinux.getBooleanNames();
-                StringBuilder newBooleanList = new StringBuilder();
-                boolean first = true;
-                for (String n : mNames) {
-                    if (first) {
-                        first = false;
-                    } else {
-                        newBooleanList.append(",");
-                    }
-                    newBooleanList.append(n);
-                    newBooleanList.append(":");
-                    newBooleanList.append(SELinux.getBooleanValue(n) ? 1 : 0);
-                }
-                Settings.Secure.putString(getActivity().getContentResolver(),
-                                          Settings.Secure.SELINUX_BOOLEANS,
-                                          newBooleanList.toString());
-            }
-        }
-
-        @Override
-        public void onActivityCreated(Bundle savedInstanceState) {
-            super.onActivityCreated(savedInstanceState);
-            setEmptyText(getActivity().getText(R.string.selinux_no_booleans));
-        }
-
-        @Override
-        public void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mAdapter = new myBooleanAdapter(getActivity(), R.layout.selinux_manage_booleans,
-                                            SELinux.getBooleanNames());
-            setListAdapter(mAdapter);
+            return view;
         }
     }
+
+   @Override
+   public void setListAdapter(ListAdapter adapter) {
+       if (mHeaders == null) {
+           mHeaders = new ArrayList<Header>();
+           for (int i = 0; i < adapter.getCount(); i++) {
+               mHeaders.add((Header) adapter.getItem(i));
+           }
+       }
+       super.setListAdapter(new HeaderAdapter(this, mHeaders));
+   }
 }

File src/com/android/seandroid_manager/SELinuxBooleanFragment.java

+
+package com.android.seandroid_manager;
+
+import android.app.ListFragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SELinux;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+public class SELinuxBooleanFragment extends ListFragment {
+
+    private myBooleanAdapter mAdapter;
+    
+    private class myBooleanAdapter extends ArrayAdapter<String> {
+        private final LayoutInflater mInflater;
+        private String[] mBooleans;
+        
+        public myBooleanAdapter(Context context, int textViewResourceId, String[] items) {
+            super(context, textViewResourceId, items);
+            mInflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            mBooleans = items;
+        }
+        
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final ViewHolder holder;
+            if (convertView  == null) {
+                convertView = mInflater.inflate(R.layout.selinux_manage_booleans_item, parent, 
+                                                false);
+                holder = new ViewHolder();
+                holder.tx = (TextView) convertView.findViewById(R.id.text);
+                holder.cb = (CheckBox) convertView.findViewById(R.id.checkbox);
+                convertView.setTag(holder);
+            } else {
+                holder = (ViewHolder) convertView.getTag();
+            }
+            
+            String name = mBooleans[position];
+            holder.tx.setText(name);
+            holder.cb.setChecked(SELinux.getBooleanValue(name));
+            holder.cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                    @Override
+                    public void onCheckedChanged(CompoundButton buttonView,
+                                                 boolean isChecked) {
+                        String name = (String)holder.tx.getText();
+                        SELinux.setBooleanValue(name, isChecked);
+                        holder.cb.setChecked(SELinux.getBooleanValue(name));
+                        saveBooleans();
+                    }
+                });
+            return convertView;
+        }
+        
+        private class ViewHolder {
+            TextView tx;
+            CheckBox cb;
+        }
+        
+        private void saveBooleans()
+        {
+            String[] mNames = SELinux.getBooleanNames();
+            StringBuilder newBooleanList = new StringBuilder();
+            boolean first = true;
+            for (String n : mNames) {
+                if (first) {
+                    first = false;
+                } else {
+                    newBooleanList.append(",");
+                }
+                newBooleanList.append(n);
+                newBooleanList.append(":");
+                newBooleanList.append(SELinux.getBooleanValue(n) ? 1 : 0);
+            }
+            Settings.Secure.putString(getActivity().getContentResolver(),
+                                      Settings.Secure.SELINUX_BOOLEANS,
+                                      newBooleanList.toString());
+        }
+    }
+    
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        setEmptyText(getActivity().getText(R.string.selinux_no_booleans));
+    }
+    
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAdapter = new myBooleanAdapter(getActivity(), R.layout.selinux_manage_booleans,
+                                        SELinux.getBooleanNames());
+        setListAdapter(mAdapter);
+    }
+}
+

File src/com/android/seandroid_manager/SELinuxDisabledFragment.java

+
+package com.android.seandroid_manager;
+
+import android.preference.PreferenceFragment;
+import android.os.Bundle;
+
+public class SELinuxDisabledFragment extends PreferenceFragment {
+
+    @Override
+	public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        addPreferencesFromResource(R.xml.selinux_not_enabled);
+    }
+}

File src/com/android/seandroid_manager/SELinuxEnforcingFragment.java

+  
+package com.android.seandroid_manager;
+
+import android.widget.ImageView;
+import android.widget.BaseAdapter;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.ListFragment;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageManager;
+import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.SELinux;
+import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import android.util.Log;
+
+public class SELinuxEnforcingFragment extends PreferenceFragment {
+
+    private static final String KEY_SELINUX_ENFORCING = "selinux_enforcing";
+
+    private CheckBoxPreference mSELinuxToggleEnforce;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.selinux_enforcing_fragment);
+        
+        mSELinuxToggleEnforce =
+            (CheckBoxPreference) getPreferenceScreen().findPreference(KEY_SELINUX_ENFORCING);
+
+        mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
+        
+    }
+
+    @Override
+	public void onResume() {
+        super.onResume();
+        
+        if (mSELinuxToggleEnforce != null) {
+            mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
+        }
+    }
+
+    @Override
+	public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+                                         Preference preference) {
+        final String key = preference.getKey();
+
+        if (preference == mSELinuxToggleEnforce) {
+            SELinux.setSELinuxEnforce(!SELinux.isSELinuxEnforced());
+            mSELinuxToggleEnforce.setChecked(SELinux.isSELinuxEnforced());
+            saveEnforcing();
+        }
+        return true;
+    }
+    
+    private void saveEnforcing() {
+        String enforcing = SELinux.isSELinuxEnforced() ? "1" : "0";
+        Settings.Secure.putString(getActivity().getContentResolver(),
+                                  Settings.Secure.SELINUX_ENFORCING,
+                                  enforcing);
+    }
+}

File src/com/android/seandroid_manager/logreaders/KLogReader.java

+package com.android.seandroid_manager.logreaders;
+
+import com.android.seandroid_manager.KLogCtl;
+import com.android.seandroid_manager.LogCallback;
+
+import java.io.ByteArrayInputStream;
+import java.util.Scanner;
+
+public class KLogReader extends Thread {
+    
+    LogCallback mCallback;
+    String mSearch;
+    
+    public KLogReader(LogCallback handler, String search) {
+        super();
+        mCallback = handler;
+        mSearch = search;
+    }
+    
+    private void parseLogs() {
+        int value;
+        byte logs[];
+        String line;
+        
+        mCallback.onStart();
+        
+        try {
+            value = KLogCtl.kLogCtl(10, null, 0);
+            logs = new byte[value];
+            value = KLogCtl.kLogCtl(3, logs, value);
+
+            Scanner stream = new Scanner(new ByteArrayInputStream(logs));
+
+            while (stream.hasNextLine() && !interrupted()) {
+                line = stream.nextLine();
+                if (line.contains(mSearch)) {
+                    mCallback.onEvent(line);
+                }
+            }
+            stream.close();
+        } catch (Exception e) {
+            mCallback.onException(e);
+        } finally {
+            mCallback.onFinish();
+        }
+    }
+    
+    @Override
+    public void run() {
+        parseLogs();
+    }
+    
+}

File src/com/android/seandroid_manager/logreaders/LogcatReader.java

+package com.android.seandroid_manager.logreaders;
+
+import com.android.seandroid_manager.LogCallback;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class LogcatReader extends Thread {
+    
+    static final String DEFAULT_ARGS = " -v raw -d -s ";
+    
+    final LogCallback mCallback;
+    final String mBuffer;
+    final String mFilterspec;
+    final String mSearch;
+    
+    /**
+     * Retrieve logs using logcat.
+     * 
+     * See {@link http://developer.android.com/guide/developing/tools/logcat.html}
+     * for details.
+     * @param handler
+     * @param buffer buffer to load, can be "main", "event", "radio", or
+     * "system"
+     * @param filterspec
+     * @param search string to search for
+     */
+    public LogcatReader(LogCallback handler, String buffer,
+            String filterspec, String search) {
+        super();
+        mCallback = handler;
+        mBuffer = buffer;
+        mFilterspec = filterspec;
+        mSearch = search;
+    }
+    
+    private void parseLogs() {
+        
+        mCallback.onStart();
+        
+        Process p = null;
+        BufferedReader br = null;
+        try {
+            //XXX Convert to use ProcessBuilder. See
+            // http://developer.android.com/reference/java/lang/Process.html
+            p = Runtime.getRuntime().exec("logcat" + DEFAULT_ARGS
+                    + " -b " + mBuffer
+                    + " " + mFilterspec);
+            br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            String line;
+            while ((line = br.readLine()) != null && !interrupted()) {
+                if (line.contains(mSearch)) {
+                    mCallback.onEvent(line);
+                }
+            }
+            br.close();
+        } catch (IOException e) {
+            mCallback.onException(e);
+        } finally {
+            if (p != null) {
+                p.destroy();
+            }
+            mCallback.onFinish();
+        }
+    }
+    
+    @Override
+    public void run() {
+        parseLogs();
+    }
+
+}