Commits

Joman Chu  committed d985e1c

Fork SEAndroidManager to become a Device Admin app

Includes major changes to the UI to support taking of locks needed to
change SELinux and MMAC settings via the Device Admin API.

  • Participants
  • Parent commits 6208fa9

Comments (0)

Files changed (41)

 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_PACKAGE_NAME := SEAndroidManager
-LOCAL_CERTIFICATE := platform
+LOCAL_PACKAGE_NAME := SEAndroidAdmin
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
-LOCAL_JNI_SHARED_LIBRARIES := libjni_klogctl
+LOCAL_JNI_SHARED_LIBRARIES :=
 
-LOCAL_REQUIRED_MODULES := libjni_klogctl
+LOCAL_REQUIRED_MODULES :=
 
 include $(BUILD_PACKAGE)
 

File AndroidManifest.xml

 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.seandroid_manager"
+    package="com.android.seandroid_admin"
     android:versionCode="1"
-    android:versionName="1.0"
-    coreApp="true"
-    android:sharedUserId="android.uid.system">
+    android:versionName="1.0">
 
-    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_LOGS" />
+    <!-- SE Android Admin -->
 
-    <!-- SEAndroid Manager -->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
 
     <application android:label="@string/app_name">
 
-        <activity android:name="SEAndroidManager"
+        <activity android:name="SEAndroidAdminActivity"
             android:label="@string/activity_name">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
 
-        <receiver android:name="RestoreSettings" android:exported="false">
-            <intent-filter android:priority="1">
-                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+        <receiver android:name="SEAndroidAdmin$myDeviceAdminReceiver"
+                android:label="@string/device_admin_label"
+                android:description="@string/device_admin_desc"
+                android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
             </intent-filter>
         </receiver>
 
-This program (SEAndroidManager) is public domain software, i.e. not copyrighted.
+This program (SEAndroidAdmin) is public domain software, i.e. not copyrighted.
 
 Warranty Exclusion
 ------------------

File jni/Android.mk

-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE    := libjni_klogctl
-LOCAL_SRC_FILES := \
-		klogctl.c \
-		exception.c
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SHARED_LIBRARIES := liblog
-
-include $(BUILD_SHARED_LIBRARY)
-

File jni/exception.c

-#include <string.h>
-#include <errno.h>
-#include <string.h>
-#include <stdlib.h>
-
-#define LOG_TAG "exception"
-
-#include <cutils/log.h>
-
-#include "klogctl.h"
-
-/* Bionic does not define this*/
-#ifndef ERESTARTSYS
-#define ERESTARTSYS     512
-#endif
-
-static jint
-throwNoClassDefError(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/lang/NoClassDefFoundError" ;
-
-	exClass = (*env)->FindClass( env, className );
-
-	if(exClass == NULL) {
-            ALOGE("Could not throw java/lang/NoClassDefFoundError");
-            /* If this happens, the program is out of control */
-            exit(42);
-        }
-	return (*env)->ThrowNew( env, exClass, message );
-}
-
-static jint
-throwIllegalArgumentException(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/lang/IllegalArgumentException" ;
-
-	exClass = (*env)->FindClass(env, className);
-	if (exClass == NULL) {
-        return throwNoClassDefError(env, className);
-	}
-
-	return (*env)->ThrowNew(env, exClass, message);
-}
-
-static jint
-throwAccessControlException(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/security/AccessControlException" ;
-
-	exClass = (*env)->FindClass(env, className);
-	if (exClass == NULL) {
-        return throwNoClassDefError(env, className);
-	}
-
-	return (*env)->ThrowNew(env, exClass, message);
-}
-
-static jint
-throwInterruptedException(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/lang/InterruptedException" ;
-
-	exClass = (*env)->FindClass(env, className);
-	if (exClass == NULL) {
-        return throwNoClassDefError(env, className);
-	}
-
-	return (*env)->ThrowNew(env, exClass, message);
-}
-
-static jint
-throwUnsupportedOperationException(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/lang/UnsupportedOperationException" ;
-
-	exClass = (*env)->FindClass(env, className);
-	if (exClass == NULL) {
-        return throwNoClassDefError(env, className);
-	}
-
-	return (*env)->ThrowNew(env, exClass, message);
-}
-
-static jint
-throwException(JNIEnv *env, char *message) {
-
-	jclass exClass;
-	char *className = "java/lang/Exception" ;
-
-	exClass = (*env)->FindClass(env, className);
-	if (exClass == NULL) {
-        return throwNoClassDefError(env, className);
-	}
-
-	return (*env)->ThrowNew(env, exClass, message);
-}
-
-void exception_throw(int error, JNIEnv *env) {
-
-	char message[128];
-	snprintf(message, 128, "Error reading kernel buffer : %s", strerror(error));
-
-	switch(error) {
-		case ENOSYS:
-			throwUnsupportedOperationException(env, message);
-			break;
-
-		case EPERM:
-			throwAccessControlException(env, message);
-			break;
-
-		case ERESTARTSYS:
-			throwInterruptedException(env, message);
-			break;
-
-		case EINVAL:
-			throwIllegalArgumentException(env, message);
-			break;
-
-		default:
-			throwException(env, message);
-			break;
-	}
-	return;
-}

File jni/exception.h

-#ifndef _EXCEPTION_H_
-#define _EXCEPTION_H_
-
-#include <jni.h>
-
-extern void exception_throw(int error, JNIEnv *env);
-
-#endif

File jni/klogctl.c

-#include <string.h>
-#include <jni.h>
-#include <errno.h>
-
-#define LOG_TAG "klogctl"
-
-#include <cutils/log.h>
-
-#include "klogctl.h"
-#include "exception.h"
-
-extern int klogctl(int type, char *buf, int length);
-
-JNIEXPORT
-jint
-JNICALL
-Java_com_android_seandroid_1manager_KLogCtl_kLogCtl(JNIEnv *env, jclass thiz, jint type, jbyteArray buf, jint length) {
-
-	jint error;
-	jboolean is_copy;
-	jbyte *bytes = (buf) ? (*env)->GetByteArrayElements(env, buf, &is_copy) : NULL;
-
-	error = klogctl(type, (char *)bytes, length);
-
-	if(bytes) {
-		(*env)->ReleaseByteArrayElements(env, buf, bytes, 0);
-	}
-
-	if(error == -1) {
-		exception_throw(errno, env);
-		return 0;
-	}
-
-	return error;
-}
-

File jni/klogctl.h

-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class com_android_seandroid_manager_KLogCtl */
-
-#ifndef _Included_com_android_seandroid_manager_KLogCtl
-#define _Included_com_android_seandroid_manager_KLogCtl
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     com_android_seandroid_manager_KLogCtl
- * Method:    kLogCtl
- * Signature: (I[BI)I
- */
-JNIEXPORT jint JNICALL Java_com_android_seandroid_1manager_KLogCtl_kLogCtl
-  (JNIEnv *, jclass, jint, jbyteArray, jint);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
-

File proguard.flags

 # Keep all Fragments in this package, which are used by reflection.
--keep class com.android.seandroid_manager.*Fragment*
--keep class com.android.senadroid_manager.*Settings*
+-keep class com.android.seandroid_admin.*Fragment*
+-keep class com.android.senadroid_admin.*Settings*

File res/layout/avc_denied_options.xml

-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-        <item android:id="@+id/fileSave"
-          android:title="Save">
-    </item>
-</menu>

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/preference_header_item.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout of a header item in PreferenceActivity. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="48dp"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="2dip"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:ellipsize="end"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+
+</LinearLayout>

File res/layout/preference_header_switch_item.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Layout of a header item in PreferenceActivity. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="48dp"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:gravity="center_vertical"
+    android:paddingRight="?android:attr/scrollbarSize">
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="2dip"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_marginBottom="6dip"
+        android:layout_weight="1">
+
+        <TextView android:id="@+android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
+
+        <TextView android:id="@+android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignLeft="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:ellipsize="end"
+            android:maxLines="2" />
+
+    </RelativeLayout>
+
+   <Switch android:id="@+id/switchWidget"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:layout_gravity="center"
+       android:padding="8dip"
+       android:focusable="false"
+       android:clickable="true" />
+
+</LinearLayout>

File res/layout/selinux_manage_booleans.xml

-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/list_container"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-	     
-    <ListView android:id="@android:id/list"
-	    android:layout_width="match_parent"
-	    android:layout_height="match_parent"
-	    android:drawSelectorOnTop="false"
-	    android:scrollbarStyle="@*android:integer/preference_fragment_scrollbarStyle" />
-
-    <TextView android:id="@android:id/empty"
-	    android:layout_width="match_parent"
-	    android:layout_height="match_parent"
-	    android:text="@string/selinux_no_booleans"/>
-
-</LinearLayout>

File res/layout/selinux_manage_booleans_item.xml

-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    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" />
-
-    <CheckBox
-        android:id="@+id/checkbox"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="right"
-        android:focusable="false" />
-
-</LinearLayout>

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/MAC"
-            android:icon="@drawable/ic_menu_refresh"
-            android:title="MAC" />
-      <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">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_fragment_title">Log Messages</string>
-    <string name="avc_denied_log_onsave_dialog_message">Logs Saved To: </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
-     	character, so it takes 4 to get 1
-     	See http://www.regular-expressions.info/java.html for
-     	more info
-     	This string determines how a date time object is formated into
-     	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_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="mac_denied_log_filename">mac_denied_logs</string>
-    <string name="mac_title">MAC Denied Log</string>
-    <string name="mac_set_enforcing">MAC Mode</string>
-    <string name="mac_set_enforcing_summary">Toggle MAC Enforcing Mode</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="app_name">SEAdmin</string>
+    <string name="activity_name">SEAdmin</string>
+
+    <string name="device_admin_label">SE Android Settings Admin</string>
+    <string name="device_admin_desc">This app is the reference implementation
+        for an Device Admin app that can control SELinux and SE Android MMAC
+        settings and policies.</string>
+
+    <!-- Used in home page screen -->
     <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>
+
+    <string name="selinux_admin_title">SELinux Administration</string>
+    <string name="mmac_admin_title">SE Android MMAC Administration</string>
+    <string name="selinux_boolean_fragment_title">Booleans</string>
+
+    <!-- Used in Enforcing Fragment -->
+    <string name="enable_admins_cat">Enable admin</string>
+
+    <string name="device_admin_cb_title">Device administrator</string>
+    <string name="device_admin_cb_summary">Allow SEAdmin to become a Device Admin. This is required for the app to function.</string>
+
+    <string name="selinux_cat">SELinux capabilities</string>
+
+    <string name="selinux_admin_cb_title">SELinux admin</string>
+    <string name="selinux_admin_cb_summary">Allow SEAdmin to become a SELinux Admin. This is required for the app to set SELinux settings.</string>
+
+    <string name="selinux_enforcing_cb_title">Enforce SELinux policy</string>
+    <string name="selinux_enforcing_cb_summaryChecked">SELinux in enforcing mode</string>
+    <string name="selinux_enforcing_cb_summaryUnchecked">SELinux in permissive mode</string>
+    <string name="selinux_enforcing_cb_summaryDisabled">Unable to query mode of SELinux</string>
+
+    <string name="selinux_reload_title">Reload SELinux policy</string>
+    <string name="selinux_restore_title">Restore default SELinux policy</string>
+
+    <string name="propertycontexts_reload_title">Reload Property Contexts policy</string>
+    <string name="propertycontexts_restore_title">Restore default Property Contexts policy</string>
+
+    <string name="filecontexts_reload_title">Reload File Contexts policy</string>
+    <string name="filecontexts_restore_title">Restore default File Contexts policy</string>
+
+    <string name="seappcontexts_reload_title">Reload SEApp Contexts policy</string>
+    <string name="seappcontexts_restore_title">Restore default SEApp Contexts policy</string>
+
+    <string name="seandroid_cat">SE Android MMAC capabilities</string>
+
+    <string name="mmac_admin_cb_title">SE Android MMAC admin</string>
+    <string name="mmac_admin_cb_summary">Allow SEAdmin to become a MMAC Admin. This is required for the app to set MMAC settings.</string>
+
+    <string name="mmac_enforcing_cb_title">Enforce MMAC policy</string>
+    <string name="mmac_enforcing_cb_summaryChecked">MMAC in enforcing mode</string>
+    <string name="mmac_enforcing_cb_summaryUnchecked">MMAC in permissive mode</string>
+    <string name="mmac_enforcing_cb_summaryDisabled">Unable to query mode of MMAC</string>
+
+    <string name="mmac_reload_title">Reload MMAC policy</string>
+
+    <string name="mmac_restore_title">Restore default MMAC policy</string>
+
+    <string name="ext_storage_unavail">SDcard unavailable</string>
+
+    <!-- Used in Boolean Fragment -->
+    <string name="selinuxBooleans_title">SELinux Booleans</string>
+    <string name="selinuxBooleans_err_selinuxDisabled">SELinux is not enabled on this device.</string>
+    <string name="selinuxBooleans_err_notDeviceAdmin">SEAdmin is not yet a Device Admin. Go to the Enforcing screen to enable.</string>
+    <string name="selinuxBooleans_err_notSELinuxAdmin">SEAdmin is not yet a SELinux Admin. Go to the Enforcing screen to enable.</string>
+    <string name="selinuxBooleans_err_getBooleans">Error occurred while retrieving booleans</string>
+    <string name="selinuxBooleans_err_noBooleans">No SELinux booleans found</string>
+
 </resources>
 

File res/xml/device_admin.xml

+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- BEGIN_INCLUDE(meta_data) -->
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-policies>
+        <enforce-selinux />
+        <enforce-mmac />
+    </uses-policies>
+</device-admin>
+<!-- END_INCLUDE(meta_data) -->

File res/xml/disabled_headers.xml

     xmlns:android="http://schemas.android.com/apk/res/android">
 
     <header
-        android:fragment="com.android.seandroid_manager.SEAndroidManager$SELinuxDisabledFragment"
         android:title="@string/selinux_disabled_fragment_title">
     </header>
 

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:id="@+id/enable_device_admin"
+        android:title="@string/device_admin_cb_title"
+        android:summary="@string/device_admin_cb_summary" />
 
-    <header
-        android:fragment="com.android.seandroid_manager.SELinuxEnforcingFragment"
-        android:title="@string/selinux_enforcing_fragment_title">
-    </header>
+    <header android:id="@+id/selinux_admin"
+        android:fragment="com.android.seandroid_admin.SELinuxEnforcingFragment"
+        android:title="@string/selinux_admin_title" />
 
-    <header
-        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.LogDeniedReaderFragment"
-        android:title="@string/log_fragment_title">
-    </header>
+    <header android:id="@+id/mmac_admin"
+        android:fragment="com.android.seandroid_admin.MMACFragment"
+        android:title="@string/mmac_admin_title" />
 
 </preference-headers>

File res/xml/mmac_fragment.xml

+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <PreferenceCategory
+        android:title="@string/seandroid_cat">
+
+        <CheckBoxPreference
+            android:key="key_mmac_enforcing"
+            android:title="@string/mmac_enforcing_cb_title" />
+
+        <Preference
+            android:title="@string/mmac_reload_title"
+            android:key="key_mmac_reload" />
+
+        <Preference
+            android:title="@string/mmac_restore_title"
+            android:key="key_mmac_restore" />
+
+    </PreferenceCategory>
+
+</PreferenceScreen>

File res/xml/selinux_enforcing_fragment.xml

 <?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen 
+<PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <CheckBoxPreference 
-        android:key="selinux_enforcing"
-        android:title="@string/selinux_set_enforcing"
-        android:summary="@string/selinux_set_enforcing_summary"/>
+    <PreferenceCategory
+        android:title="@string/selinux_cat">
+
+        <CheckBoxPreference android:id="@+id/selinux_enforcing_cb"
+            android:key="key_selinux_enforcing"
+            android:title="@string/selinux_enforcing_cb_title" />
+
+        <Preference android:id="@+id/selinux_reload"
+            android:title="@string/selinux_reload_title"
+            android:key="key_selinux_reload" />
+
+        <Preference android:id="@+id/selinux_restore"
+            android:title="@string/selinux_restore_title"
+            android:key="key_selinux_restore" />
+
+        <Preference android:id="@+id/propertycontexts_reload"
+            android:title="@string/propertycontexts_reload_title"
+            android:key="key_propertycontexts_reload" />
+
+        <Preference android:id="@+id/propertycontexts_restore"
+            android:title="@string/propertycontexts_restore_title"
+            android:key="key_propertycontexts_restore" />
+
+        <Preference android:id="@+id/filecontexts_reload"
+            android:title="@string/filecontexts_reload_title"
+            android:key="key_filecontexts_reload" />
+
+        <Preference android:id="@+id/filecontexts_restore"
+            android:title="@string/filecontexts_restore_title"
+            android:key="key_filecontexts_restore" />
+
+        <Preference android:id="@+id/seappcontexts_reload"
+            android:title="@string/seappcontexts_reload_title"
+            android:key="key_seappcontexts_reload" />
+
+        <Preference android:id="@+id/seappcontexts_restore"
+            android:title="@string/seappcontexts_restore_title"
+            android:key="key_seappcontexts_restore" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:title="@string/selinux_boolean_fragment_title">
+    </PreferenceCategory>
 
-    <CheckBoxPreference
-        android:key="mac_enforcing"
-        android:title="@string/mac_set_enforcing"
-        android:summary="@string/mac_set_enforcing_summary"/>
-	
 </PreferenceScreen>

File res/xml/selinux_not_enabled.xml

-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-    android:title="@string/selinux_not_enabled" >
-</PreferenceScreen>

File src/com/android/seandroid_admin/MMACFragment.java

+package com.android.seandroid_admin;
+
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.util.Log;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class MMACFragment extends SEAndroidAdminFragment
+        implements OnPreferenceChangeListener {
+    public static final String TAG = "MMACfragment";
+
+    private static final String KEY_MMAC_ENFORCING = "key_mmac_enforcing";
+    private static final String KEY_MMAC_RELOAD = "key_mmac_reload";
+    private static final String KEY_MMAC_RESTORE = "key_mmac_restore";
+
+    private static final String MMAC_POLICY_FILE = "mac_permissions.xml";
+
+    private CheckBoxPreference mMMACenforceCheckbox;
+    private Preference mMMACreload;
+    private Preference mMMACrestore;
+
+    private File mMMACpolicyFile = null;
+
+    private String mMMACenforceCheckboxSummaryChecked;
+    private String mMMACenforceCheckboxSummaryUnchecked;
+    private String mMMACenforceCheckboxSummaryDisabled;
+
+    private TextView mEmptyView;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mAdmin.updateMMACstate();
+
+        addPreferencesFromResource(R.xml.mmac_fragment);
+
+        mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
+        getListView().setEmptyView(mEmptyView);
+
+        if (!mAdmin.isDeviceAdmin) {
+            addMessagePreference("not device admin");
+
+        } else if (!mAdmin.isMMACadmin) {
+            addMessagePreference("not mmac admin");
+
+        } else {
+            File extFileDir = mActivity.getExternalFilesDir(null);
+            if (extFileDir != null) {
+                mMMACpolicyFile = new File(extFileDir, MMAC_POLICY_FILE);
+            }
+
+            mMMACenforceCheckbox =
+                    (CheckBoxPreference) getPreferenceScreen().findPreference(KEY_MMAC_ENFORCING);
+            mMMACenforceCheckbox.setOnPreferenceChangeListener(this);
+            mMMACenforceCheckboxSummaryChecked =
+                    getString(R.string.mmac_enforcing_cb_summaryChecked);
+            mMMACenforceCheckboxSummaryUnchecked =
+                    getString(R.string.mmac_enforcing_cb_summaryUnchecked);
+            mMMACenforceCheckboxSummaryDisabled =
+                    getString(R.string.mmac_enforcing_cb_summaryDisabled);
+
+            mMMACreload =
+                    getPreferenceScreen().findPreference(KEY_MMAC_RELOAD);
+            mMMACreload.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Reload of MMAC policy requested");
+                    try {
+                        byte[] policy = FileUtils.readFileToByteArray(mMMACpolicyFile);
+                        if (!mAdmin.mDPM.setMMACpolicy(mAdmin.mDeviceAdmin, policy)) {
+                            Toast.makeText(mActivity, "Unable to set policy", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (IOException ioex) {
+                        Log.e(TAG, "Exception ocurred", ioex);
+                        Toast.makeText(mActivity, ioex.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mMMACrestore =
+                    getPreferenceScreen().findPreference(KEY_MMAC_RESTORE);
+            mMMACrestore.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Delete custom MMAC policy requested");
+                    if (!mAdmin.mDPM.setMMACpolicy(mAdmin.mDeviceAdmin, null)) {
+                        Toast.makeText(mActivity, "Unable to remove custom policy", Toast.LENGTH_SHORT).show();
+                    } else {
+                        Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onResume() {
+        // XXX Unfortunately, both super.onResume and updateViews will update
+        // the Admin state to the same thing.
+        super.onResume();
+        updateViews();
+    }
+    
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (super.onPreferenceChange(preference, newValue)) {
+            return true;
+        }
+        
+        if (preference == mMMACenforceCheckbox) {
+            boolean value = (Boolean) newValue;
+            boolean ret = mAdmin.mDPM.setMMACenforcing(mAdmin.mDeviceAdmin, value);
+            mAdmin.updateMMACstate();
+            updateViews();
+            return ret;
+        }
+        
+        return false;
+    }
+    
+    private void updateViews() {
+        if (TRACE_UPDATE) { Log.v(TAG, "UPDATE updateViews()"); }
+        
+        if (mAdmin.isMMACadmin) {
+            mMMACenforceCheckbox.setEnabled(true);
+            mMMACenforceCheckbox.setChecked(mAdmin.isEnforcingMMAC);
+            if (mAdmin.isEnforcingMMAC) {
+                mMMACenforceCheckbox.setSummary(mMMACenforceCheckboxSummaryChecked);
+            } else {
+                mMMACenforceCheckbox.setSummary(mMMACenforceCheckboxSummaryUnchecked);
+            }
+
+            mMMACreload.setEnabled(true);
+            if (mMMACpolicyFile != null) {
+                mMMACreload.setSummary(mMMACpolicyFile.getPath());
+            } else {
+                mMMACreload.setSummary(R.string.ext_storage_unavail);
+            }
+
+            mMMACrestore.setEnabled(true);
+        }
+    }
+
+    private void addMessagePreference(int messageId) {
+        if (mEmptyView != null) mEmptyView.setText(messageId);
+        getPreferenceScreen().removeAll();
+    }
+
+    private void addMessagePreference(String message) {
+        if (mEmptyView != null) mEmptyView.setText(message);
+        getPreferenceScreen().removeAll();
+    }
+}

File src/com/android/seandroid_admin/SEAndroidAdmin.java

+package com.android.seandroid_admin;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * Keeps track of the state of the Device Admin system.
+ */
+public class SEAndroidAdmin {
+    public static final String TAG = "SEAdmin";
+    public static final boolean TRACE_UPDATE = false; // traces some of the update method call chains
+
+    public static final int REQUEST_CODE_ENABLE_ADMIN = 1;
+
+    // XXX Maybe this should be split out to do GUI-like stuff for callbacks.
+    public static class myDeviceAdminReceiver extends DeviceAdminReceiver { }
+
+    final SEAndroidAdminActivity mActivity;
+    final DevicePolicyManager mDPM;
+    final ComponentName mDeviceAdmin;
+
+    /** True if we are an active device administrator */
+    boolean isDeviceAdmin;
+
+    /** True if we are a SELinux admin */
+    boolean isSELinuxAdmin;
+    /** True if we are enforcing SELinux policy */
+    boolean isEnforcingSELinux;
+
+    /** True if we are a MMAC admin */
+    boolean isMMACadmin;
+    /** True if we are enforcing MMAC policy */
+    boolean isEnforcingMMAC;
+
+    SEAndroidAdmin(SEAndroidAdminActivity activity) {
+        mActivity = activity;
+        mDPM = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        mDeviceAdmin = new ComponentName(activity, myDeviceAdminReceiver.class);
+        updateState();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(TAG+'@'+Integer.toHexString(super.hashCode())+"={");
+        sb.append("isDeviceAdmin : "+isDeviceAdmin+", ");
+        sb.append("isSELinuxAdmin : "+isSELinuxAdmin+", ");
+        sb.append("isEnforcingSELinux : "+isEnforcingSELinux+", ");
+        sb.append("isMMACadmin : "+isMMACadmin+", ");
+        sb.append("isEnforcingMMAC : "+isEnforcingMMAC+", ");
+        sb.delete(sb.length()-2, sb.length()); //delete comma
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /**
+     * Be very careful about using this. This only exists because removeActiveAdmin()
+     * defers removal of this admin from the admins list until later and returns
+     * almost immediately. So this code could execute before the list removal is executed,
+     * resulting in isActiveAdmin() returning true. To mitigate this, we set mAdminActive
+     * to the value we want and proceed with updating state as normal.
+     */
+    void setAdminState(boolean value) {
+        if (TRACE_UPDATE) { Log.v(TAG, "UPDATE setAdminState(value="+value+")"); }
+        boolean old = isDeviceAdmin;
+        isDeviceAdmin = value;
+        if (old != isDeviceAdmin) { Log.v(TAG, "mAdminActive: " + old + " -> " + isDeviceAdmin); }
+        updateSELinuxState();
+        updateMMACstate();
+    }
+
+    void updateState() {
+        updateDeviceAdminState();
+        updateSELinuxState();
+        updateMMACstate();
+    }
+
+    /** Updates Device Admin state with current values */
+    void updateDeviceAdminState() {
+        if (TRACE_UPDATE) { Log.v(TAG, "UPDATE updateDeviceAdminState()"); }
+        boolean old = isDeviceAdmin;
+        isDeviceAdmin = mDPM.isAdminActive(mDeviceAdmin);
+        if (old != isDeviceAdmin) { Log.v(TAG, "mAdminActive: " + old + " -> " + isDeviceAdmin); }
+    }
+
+    /** Updates SELinux state with current device values */
+    void updateSELinuxState() {
+        if (TRACE_UPDATE) { Log.v(TAG, "UPDATE updateSELinuxState()"); }
+
+        boolean old;
+
+        // Device Admin necessary for SELinux Admin
+        old = isSELinuxAdmin;
+        isSELinuxAdmin = isDeviceAdmin && mDPM.isSELinuxAdmin(mDeviceAdmin);
+        if (old != isSELinuxAdmin) { Log.v(TAG, "mSELinuxAdmin: " + old + " -> " + isSELinuxAdmin); }
+
+        // SELinux Admin necessary for Enforcing SELinux Policy
+        old = isEnforcingSELinux;
+        isEnforcingSELinux = isSELinuxAdmin && mDPM.getSELinuxEnforcing(mDeviceAdmin);
+        if (old != isEnforcingSELinux) { Log.v(TAG, "mEnforcingSELinux: " + old + " -> " + isEnforcingSELinux); }        
+    }
+
+    /** Updates MMAC state with current device values */
+    void updateMMACstate() {
+        if (TRACE_UPDATE) { Log.v(TAG, "UPDATE updateMMACState()"); }
+
+        boolean old;
+
+        // Device Admin necessary for MMAC Admin
+        old = isMMACadmin;
+        isMMACadmin = isDeviceAdmin && mDPM.isMMACadmin(mDeviceAdmin);
+        if (old != isMMACadmin) { Log.v(TAG, "mMMACadmin: " + old + " -> " + isMMACadmin); }
+
+        // MMAC Admin necessary for Enforcing MMAC Policy
+        old = isEnforcingMMAC;
+        isEnforcingMMAC = isMMACadmin && mDPM.getMMACenforcing(mDeviceAdmin);
+        if (old != isEnforcingMMAC) { Log.v(TAG, "mEnforceMMAC: " + old + " -> " + isEnforcingMMAC); }
+    }
+
+    /** Asks user to enable SEAdmin as a Device Admin */
+    void enableAdmin() {
+        Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
+        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdmin);
+        mActivity.startActivityForResult(intent, REQUEST_CODE_ENABLE_ADMIN);
+    }
+
+    void removeAdmin() {
+        mDPM.removeActiveAdmin(mDeviceAdmin);
+        setAdminState(false);   // removeActiveAdmin returns immediately but the
+        updateSELinuxState();   // removal is delayed, so set state manually
+        updateMMACstate();
+    }
+}

File src/com/android/seandroid_admin/SEAndroidAdminActivity.java

+
+package com.android.seandroid_admin;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceActivity;
+import android.os.Bundle;
+import android.os.SELinux;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.ListAdapter;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.seandroid_admin.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SEAndroidAdminActivity extends PreferenceActivity {
+    private static final String TAG = "SEAdminActivity";
+    static final boolean TRACE_LIFECYCLE = false;
+    static final boolean TRACE_UPDATE = SEAndroidAdmin.TRACE_UPDATE;
+
+    private List<Header> mHeaders;
+
+    SEAndroidAdmin mAdmin;
+
+    private View mDeviceAdminView;
+    private View mSELinuxAdminView;
+    private View mMMACadminView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE ACTIVITY onCreate()"); }
+        super.onCreate(savedInstanceState);
+        mAdmin = new SEAndroidAdmin(this);
+    }
+
+    @Override
+    public void onBuildHeaders(List<Header> headers) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE ACTIVITY onBuildHeaders()"); }
+        if (!SELinux.isSELinuxEnabled()) {
+            // TODO Match with rest of theme
+            Log.v(TAG, "SELinux disabled");
+            loadHeadersFromResource(R.xml.disabled_headers, headers);
+            return;
+        }
+
+        loadHeadersFromResource(R.xml.enabled_headers, headers);
+        updateHeaderList(headers);
+    }
+
+    private void updateHeaderList(List<Header> target) {
+        //TODO maybe enable or disable headers here based on whether were device admin?
+    }
+
+    private static class HeaderViewHolder {
+        TextView title;
+        Switch switch_;
+        TextView summary;
+    }
+
+    private static class HeaderAdapter extends ArrayAdapter<Header> {
+        static final int HEADER_TYPE_CATEGORY = 0;
+        static final int HEADER_TYPE_NORMAL = 1;
+        static final int HEADER_TYPE_SWITCH = 2;
+        private static final int HEADER_TYPE_COUNT = HEADER_TYPE_SWITCH + 1;
+
+        private SEAndroidAdminActivity mActivity;
+        private LayoutInflater mInflater;
+
+        static int getHeaderType(Header header) {
+            int id = (int) header.id; // ids are integers, so downcast is okay
+            switch (id) {
+                //case ???:
+                //    return HEADER_TYPE_CATEGORY;
+                case R.id.enable_device_admin:
+                case R.id.selinux_admin:
+                case R.id.mmac_admin:
+                    return HEADER_TYPE_SWITCH;
+                default:
+                    return HEADER_TYPE_NORMAL;
+            }
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            Header header = getItem(position);
+            return getHeaderType(header);
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            return false; // because of categories
+            //return SELinux.isSELinuxEnabled();
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return getItemViewType(position) != HEADER_TYPE_CATEGORY;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return HEADER_TYPE_COUNT;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+
+        public HeaderAdapter(Context context, List<Header> objects) {
+            super(context, 0, objects);
+            mActivity = (SEAndroidAdminActivity) context;
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            HeaderViewHolder holder;
+            final Header header = getItem(position);
+            int headerType = getHeaderType(header);
+            View view = null;
+
+            if (convertView == null) {
+                // New view, so start inflating views
+                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_SWITCH:
+                        view = mInflater.inflate(R.layout.preference_header_switch_item,
+                                parent, false);
+                        holder.title = (TextView) view.findViewById(
+                                com.android.internal.R.id.title);
+                        holder.summary = (TextView) view.findViewById(
+                                com.android.internal.R.id.summary);
+                        holder.switch_ = (Switch) view.findViewById(R.id.switchWidget);
+                        break;
+
+                    case HEADER_TYPE_NORMAL:
+                        view = mInflater.inflate(R.layout.preference_header_item, parent,
+                                false);
+                        holder.title = (TextView) view.findViewById(
+                                com.android.internal.R.id.title);
+                        holder.summary = (TextView) view.findViewById(
+                                com.android.internal.R.id.summary);
+                        break;
+                }
+
+                view.setTag(holder);
+            } else {
+                view = convertView;
+                holder = (HeaderViewHolder) view.getTag();
+            }
+
+            // All view fields must be updated every time, because the view may be recycled
+            switch (headerType) {
+                case HEADER_TYPE_CATEGORY:
+                    holder.title.setText(header.getTitle(getContext().getResources()));
+                    Log.v(TAG, "Updated view for header CATEGORY " + holder.title.getText());
+                    break;
+
+                case HEADER_TYPE_SWITCH:
+                    holder.switch_.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+                        @Override
+                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                            mActivity.updateSwitchAssignment(header, (Switch) buttonView);
+                            SEAndroidAdmin mAdmin = mActivity.mAdmin;
+
+                            if (header.id == R.id.enable_device_admin) {
+                                boolean adminActive = mAdmin.isDeviceAdmin;
+                                Log.v(TAG, "Clicked Device admin: " + adminActive + " -> " + isChecked);
+                                if (isChecked != adminActive) {
+                                    if (isChecked) {
+                                        mAdmin.enableAdmin();
+                                        // Don't need to update views because onResume() fires
+                                        // after coming back from approval screen.
+                                    } else {
+                                        mAdmin.removeAdmin();
+                                        mActivity.setDeviceAdminView(false);
+                                    }
+                                }
+                                return;
+
+                            } else if (header.id == R.id.selinux_admin) {
+                                boolean adminActive = mAdmin.isSELinuxAdmin;
+                                Log.v(TAG, "Clicked SELinux admin: " + adminActive + " -> " + isChecked);
+                                if (isChecked != adminActive) {
+                                    boolean ret = mAdmin.mDPM.setSELinuxAdmin(mAdmin.mDeviceAdmin, isChecked);
+                                    // TODO show failure with toast or something
+                                    mAdmin.updateSELinuxState();
+                                    mActivity.updateSELinuxView();
+                                }
+                                return;
+
+                            } else if (header.id == R.id.mmac_admin) {
+                                boolean adminActive = mAdmin.isMMACadmin;
+                                Log.v(TAG, "Clicked MMAC admin: " + adminActive + " -> " + isChecked);
+                                if (isChecked != adminActive) {
+                                    boolean ret = mAdmin.mDPM.setMMACadmin(mAdmin.mDeviceAdmin, isChecked);
+                                    // TODO show failure  with toast or something
+                                    mAdmin.updateMMACstate();
+                                    mActivity.updateMMACview();
+                                }
+                                return;
+                            }
+                        }
+                    });
+                    // fallthrough to update common fields
+
+                case HEADER_TYPE_NORMAL:
+                    holder.title.setText(header.getTitle(getContext().getResources()));
+                    Log.v(TAG, "Updated view for header " + holder.title.getText());
+                    CharSequence summary = header.getSummary(getContext().getResources());
+                    if (!TextUtils.isEmpty(summary)) {
+                        holder.summary.setVisibility(View.VISIBLE);
+                        holder.summary.setText(summary);
+                    } else {
+                        holder.summary.setVisibility(View.GONE);
+                    }
+            }
+            mActivity.updateViewAssignment(header, view);
+
+            return view;
+        }
+
+    }
+
+    @Override
+    public void setListAdapter(ListAdapter adapter) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE ACTIVITY setListAdapter()"); }
+        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));
+    }
+
+    /** Tells the activity which View is what feature and updates that view
+     * with the current state. */
+    private void updateViewAssignment(Header header, View view) {
+        int id = (int) header.id; // ids are integers, so downcast is okay
+        switch (id) {
+            case R.id.enable_device_admin:
+                mDeviceAdminView = view;
+                updateDeviceAdminView();
+                break;
+            case R.id.mmac_admin:
+                mMMACadminView = view;
+                updateMMACview();
+                break;
+            case R.id.selinux_admin:
+                mSELinuxAdminView = view;
+                updateSELinuxView();
+                break;
+        }
+    }
+
+    /** Tells the activity which switch belongs to what view. This was needed
+     * because in the onCheckedChangeReceiver, the buttonView was different
+     * from the Switch in the saved Views. Not sure why. */
+    private void updateSwitchAssignment(Header header, Switch switch_) {
+        int id = (int) header.id; // ids are integers, so downcast is okay
+        switch (id) {
+            case R.id.enable_device_admin:
+                ((HeaderViewHolder) mDeviceAdminView.getTag()).switch_ = switch_;
+                break;
+            case R.id.selinux_admin:
+                ((HeaderViewHolder) mSELinuxAdminView.getTag()).switch_ = switch_;
+                break;
+            case R.id.mmac_admin:
+                ((HeaderViewHolder) mMMACadminView.getTag()).switch_ = switch_;
+                break;
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE ACTIVITY onActivityResult()"); }
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == SEAndroidAdmin.REQUEST_CODE_ENABLE_ADMIN) {
+            mAdmin.updateState();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE ACTIVITY onResume()"); }
+        super.onResume();
+        mAdmin.updateState();
+        updateDeviceAdminView();
+        updateMMACview();
+        updateSELinuxView();
+    }
+
+    /**
+     * Reset the SEAndroidAdmin's device admin state to reflect adminState and
+     * updates the UI.
+     * 
+     * Read {@link SEAndroidAdmin#setAdminState(boolean)} for more usage
+     * information before using this.
+     */
+    private void setDeviceAdminView(boolean adminState) {
+        if (SEAndroidAdmin.TRACE_UPDATE) { Log.v(TAG, "UPDATE ACTIVITY setDeviceAdminView("+adminState+")"); }
+        //mAdmin.setAdminState(adminState);
+
+        Switch switch_ = ((HeaderViewHolder) mDeviceAdminView.getTag()).switch_;
+        switch_.setChecked(mAdmin.isDeviceAdmin);
+        updateSELinuxView();
+        updateMMACview();
+    }
+
+    /** Updates the Device Admin view to reflect current device state.
+     * If you want truly current state, ask SEAndroidAdmin to update
+     * the state first. */
+    private void updateDeviceAdminView() {
+        if (SEAndroidAdmin.TRACE_UPDATE) { Log.v(TAG, "UPDATE ACTIVITY updateDeviceAdminView()"); }
+        //mAdmin.updateDeviceAdminState();
+
+        if (mDeviceAdminView != null) { // else, nothing to update yet
+            Switch switch_ = ((HeaderViewHolder) mDeviceAdminView.getTag()).switch_;
+            switch_.setChecked(mAdmin.isDeviceAdmin);
+        }
+    }
+
+    /** Updates the SELinux view to reflect the current device state.
+     * If you want truly current state, ask SEAndroidAdmin to update
+     * the state first. */
+    private void updateSELinuxView() {
+        if (SEAndroidAdmin.TRACE_UPDATE) { Log.v(TAG, "UPDATE ACTIVITY updateSELinuxView()"); }
+        //mAdmin.updateSELinuxState();
+
+        if (mSELinuxAdminView != null) { // else, nothing to update yet
+            Switch switch_ = ((HeaderViewHolder) mSELinuxAdminView.getTag()).switch_;
+            switch_.setEnabled(mAdmin.isDeviceAdmin);
+            switch_.setChecked(mAdmin.isSELinuxAdmin);
+        }
+    }
+
+    /** Updates the MMAC view to reflect the current device state.
+     * If you want truly current state, ask SEAndroidAdmin to update
+     * the state first. */
+    private void updateMMACview() {
+        if (SEAndroidAdmin.TRACE_UPDATE) { Log.v(TAG, "UPDATE ACTIVITY updateMMACview()"); }
+        //mAdmin.updateMMACstate();
+
+        if (mMMACadminView != null) { // else, nothing to update yet
+            Switch switch_ = ((HeaderViewHolder) mMMACadminView.getTag()).switch_;
+            switch_.setEnabled(mAdmin.isDeviceAdmin);
+            switch_.setChecked(mAdmin.isMMACadmin);
+        }
+    }
+}

File src/com/android/seandroid_admin/SEAndroidAdminFragment.java

+package com.android.seandroid_admin;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.util.Log;
+
+/**
+ * Common fragment code for DevicePolicyManager access. Provides two shared elements:
+ *   1. Provides instance variables to access activity/context, DevicePolicyManager, etc.
+ *   2. You can also place data shared by multiple Fragments in SEAdmin here.
+ */
+public class SEAndroidAdminFragment extends PreferenceFragment implements
+        OnPreferenceChangeListener, OnPreferenceClickListener {
+
+    private static final String TAG = "SEAdminFragment";
+    protected static final boolean TRACE_LIFECYCLE = SEAndroidAdminActivity.TRACE_LIFECYCLE;
+    protected static final boolean TRACE_UPDATE = SEAndroidAdminActivity.TRACE_UPDATE;
+
+    protected SEAndroidAdminActivity mActivity;
+    protected SEAndroidAdmin mAdmin;
+
+    @Override
+    public void onAttach(Activity activity) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE FRAGMENT onAttach()"); }
+        super.onAttach(activity);
+        mActivity = (SEAndroidAdminActivity) activity;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE FRAGMENT onCreate()"); }
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE FRAGMENT onActivityCreated()"); }
+        super.onActivityCreated(savedInstanceState);
+
+        mAdmin = mActivity.mAdmin;
+    }
+
+    @Override
+    public void onStart() {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE FRAGMENT onStart()"); }
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        if (TRACE_LIFECYCLE) { Log.v(TAG, "LIFECYCLE FRAGMENT onResume()"); }
+        super.onResume();
+
+        mAdmin.updateState();
+    }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        return false;
+    }
+
+}

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

+package com.android.seandroid_admin;
+
+import android.os.Bundle;
+import android.os.SELinux;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceChangeListener;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.util.Log;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.seandroid_admin.R;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+//TODO may want to rename to reflect real functionality
+public class SELinuxEnforcingFragment extends SEAndroidAdminFragment
+        implements OnPreferenceChangeListener {
+    public static final String TAG = "SELinuxFragment";
+
+    private static final String KEY_SELINUX_ENFORCING = "key_selinux_enforcing";
+
+    private static final String KEY_SELINUX_RELOAD = "key_selinux_reload";
+    private static final String KEY_SELINUX_RESTORE = "key_selinux_restore";
+    private static final String KEY_PROPERTYCONTEXTS_RELOAD = "key_propertycontexts_reload";
+    private static final String KEY_PROPERTYCONTEXTS_RESTORE = "key_propertycontexts_restore";
+    private static final String KEY_FILECONTEXTS_RELOAD = "key_filecontexts_reload";
+    private static final String KEY_FILECONTEXTS_RESTORE = "key_filecontexts_restore";
+    private static final String KEY_SEAPPCONTEXTS_RELOAD = "key_seappcontexts_reload";
+    private static final String KEY_SEAPPCONTEXTS_RESTORE = "key_seappcontexts_restore";
+
+    private static final String SELINUX_POLICY_FILE = "sepolicy";
+    private static final String PROPERTY_CONTEXTS_FILE = "property_contexts";
+    private static final String FILE_CONTEXTS_FILE = "file_contexts";
+    private static final String SEAPP_CONTEXTS_FILE = "seapp_contexts";
+
+    private CheckBoxPreference mSELinuxEnforceCheckbox;
+    private Preference mSELinuxReload, mSELinuxRestore;
+    private Preference mPropertyContextsReload, mPropertyContextsRestore;
+    private Preference mFileContextsReload, mFileContextsRestore;
+    private Preference mSEAppContextsReload, mSEAppContextsRestore;
+
+    private File mSELinuxPolicyFile = null;
+    private File mPropertyContextsPolicyFile = null;
+    private File mFileContextsPolicyFile = null;
+    private File mSEAppContextsPolicyFile = null;
+
+    private String mSELinuxEnforceCheckboxSummaryChecked;
+    private String mSELinuxEnforceCheckboxSummaryUnchecked;
+    private String mSELinuxEnforceCheckboxSummaryDisabled;
+
+    private TextView mEmptyView;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mAdmin.updateSELinuxState();
+
+        addPreferencesFromResource(R.xml.selinux_enforcing_fragment);
+
+        mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
+        getListView().setEmptyView(mEmptyView);
+
+        if (!SELinux.isSELinuxEnabled()) {
+            addMessagePreference(R.string.selinuxBooleans_err_selinuxDisabled);
+
+        } else if (!mAdmin.isDeviceAdmin) {
+            addMessagePreference(R.string.selinuxBooleans_err_notDeviceAdmin);
+
+        } else if (!mAdmin.isSELinuxAdmin) {
+            addMessagePreference(R.string.selinuxBooleans_err_notSELinuxAdmin);
+
+        } else {
+            File extFileDir = mActivity.getExternalFilesDir(null);
+            if (extFileDir != null) {
+                mSELinuxPolicyFile = new File(extFileDir, SELINUX_POLICY_FILE);
+                mPropertyContextsPolicyFile = new File(extFileDir, PROPERTY_CONTEXTS_FILE);
+                mFileContextsPolicyFile = new File(extFileDir, FILE_CONTEXTS_FILE);
+                mSEAppContextsPolicyFile = new File(extFileDir, SEAPP_CONTEXTS_FILE);
+            }
+
+            mSELinuxEnforceCheckbox =
+                    (CheckBoxPreference) getPreferenceScreen().findPreference(KEY_SELINUX_ENFORCING);
+            mSELinuxEnforceCheckbox.setOnPreferenceChangeListener(this);
+            mSELinuxEnforceCheckboxSummaryChecked =
+                    getString(R.string.selinux_enforcing_cb_summaryChecked);
+            mSELinuxEnforceCheckboxSummaryUnchecked =
+                    getString(R.string.selinux_enforcing_cb_summaryUnchecked);
+            mSELinuxEnforceCheckboxSummaryDisabled =
+                    getString(R.string.selinux_enforcing_cb_summaryDisabled);
+
+            List<String> boolnames = mAdmin.mDPM.getSELinuxBooleanNames(mAdmin.mDeviceAdmin);
+            Log.v(TAG, "SELinux booleans: " + boolnames);
+            if (boolnames != null) {
+                Collections.sort(boolnames);
+                for (final String name : boolnames) {
+                    //XXX To do small text, will need to define own xml layout
+                    CheckBoxPreference pref = new CheckBoxPreference(mActivity);
+                    pref.setTitle(name);
+                    pref.setChecked(mAdmin.mDPM.getSELinuxBooleanValue(mAdmin.mDeviceAdmin, name));
+                    pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+                        @Override
+                        public boolean onPreferenceChange(Preference preference, Object newValue) {
+                            boolean value = (Boolean) newValue;
+                            boolean ret = mAdmin.mDPM.setSELinuxBooleanValue(mAdmin.mDeviceAdmin, name, value);
+                            Toast.makeText(mActivity,
+                                    ret ? "Success" : "Unable to set boolean " + name,
+                                            Toast.LENGTH_SHORT).show();
+                            return ret;
+                        }
+                    });
+                    getPreferenceScreen().addPreference(pref);
+                }
+            }
+            //TODO Figure out how to handle failure of getSELinuxBooleanNames
+            //TODO Figure out how to add boolean prefs to the Booleans
+            //     PreferenceCategory, not just append to end
+
+            /* Warning, lots of duplicated code coming */
+
+            mSELinuxReload =
+                    getPreferenceScreen().findPreference(KEY_SELINUX_RELOAD);
+            mSELinuxReload.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Reload of SELinux policy requested");
+                    try {
+                        byte[] policy = FileUtils.readFileToByteArray(mSELinuxPolicyFile);
+                        if (!mAdmin.mDPM.setSELinuxPolicy(mAdmin.mDeviceAdmin, policy)) {
+                            Toast.makeText(mActivity, "Unable to set policy", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (IOException ioex) {
+                        Log.e(TAG, "Exception ocurred", ioex);
+                        Toast.makeText(mActivity, ioex.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mSELinuxRestore =
+                    getPreferenceScreen().findPreference(KEY_SELINUX_RESTORE);
+            mSELinuxRestore.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Delete custom SELinux policy requested");
+                    if (!mAdmin.mDPM.setSELinuxPolicy(mAdmin.mDeviceAdmin, null)) {
+                        Toast.makeText(mActivity, "Unable to remove custom policy", Toast.LENGTH_SHORT).show();
+                    } else {
+                        Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mPropertyContextsReload =
+                    getPreferenceScreen().findPreference(KEY_PROPERTYCONTEXTS_RELOAD);
+            mPropertyContextsReload.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Reload of Property Contexts policy requested");
+                    try {
+                        byte[] policy = FileUtils.readFileToByteArray(mPropertyContextsPolicyFile);
+                        if (!mAdmin.mDPM.setPropertyContexts(mAdmin.mDeviceAdmin, policy)) {
+                            Toast.makeText(mActivity, "Unable to set policy", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (IOException ioex) {
+                        Log.e(TAG, "Exception ocurred", ioex);
+                        Toast.makeText(mActivity, ioex.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mPropertyContextsRestore =
+                    getPreferenceScreen().findPreference(KEY_PROPERTYCONTEXTS_RESTORE);
+            mPropertyContextsRestore.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Delete custom Property Contexts policy requested");
+                    if (!mAdmin.mDPM.setPropertyContexts(mAdmin.mDeviceAdmin, null)) {
+                        Toast.makeText(mActivity, "Unable to remove custom policy", Toast.LENGTH_SHORT).show();
+                    } else {
+                        Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mFileContextsReload =
+                    getPreferenceScreen().findPreference(KEY_FILECONTEXTS_RELOAD);
+            mFileContextsReload.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Reload of File Contexts policy requested");
+                    try {
+                        byte[] policy = FileUtils.readFileToByteArray(mFileContextsPolicyFile);
+                        if (!mAdmin.mDPM.setFileContexts(mAdmin.mDeviceAdmin, policy)) {
+                            Toast.makeText(mActivity, "Unable to set policy", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (IOException ioex) {
+                        Log.e(TAG, "Exception ocurred", ioex);
+                        Toast.makeText(mActivity, ioex.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mFileContextsRestore =
+                    getPreferenceScreen().findPreference(KEY_FILECONTEXTS_RESTORE);
+            mFileContextsRestore.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Delete custom File Contexts policy requested");
+                    if (!mAdmin.mDPM.setFileContexts(mAdmin.mDeviceAdmin, null)) {
+                        Toast.makeText(mActivity, "Unable to remove custom policy", Toast.LENGTH_SHORT).show();
+                    } else {
+                        Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mSEAppContextsReload =
+                    getPreferenceScreen().findPreference(KEY_SEAPPCONTEXTS_RELOAD);
+            mSEAppContextsReload.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Reload of SEApp Contexts policy requested");
+                    try {
+                        byte[] policy = FileUtils.readFileToByteArray(mSEAppContextsPolicyFile);
+                        if (!mAdmin.mDPM.setSEappContexts(mAdmin.mDeviceAdmin, policy)) {
+                            Toast.makeText(mActivity, "Unable to set policy", Toast.LENGTH_SHORT).show();
+                        } else {
+                            Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                        }
+                    } catch (IOException ioex) {
+                        Log.e(TAG, "Exception ocurred", ioex);
+                        Toast.makeText(mActivity, ioex.toString(), Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+
+            mSEAppContextsRestore =
+                    getPreferenceScreen().findPreference(KEY_SEAPPCONTEXTS_RESTORE);
+            mSEAppContextsRestore.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    Log.v(TAG, "Delete custom SEApp Contexts policy requested");
+                    if (!mAdmin.mDPM.setSEappContexts(mAdmin.mDeviceAdmin, null)) {
+                        Toast.makeText(mActivity, "Unable to remove custom policy", Toast.LENGTH_SHORT).show();
+                    } else {
+                        Toast.makeText(mActivity, "Success", Toast.LENGTH_SHORT).show();
+                    }
+                    return false;
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onResume() {
+        // XXX Unfortunately, both super.onResume and updateViews will update
+        // the Admin state to the same thing.
+        super.onResume();
+        updateViews();
+    }
+
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object newValue) {
+        if (super.onPreferenceChange(preference, newValue)) {
+            return true;
+        }
+
+        if (preference == mSELinuxEnforceCheckbox) {
+            boolean value = (Boolean) newValue;
+            boolean ret = mAdmin.mDPM.setSELinuxEnforcing(mAdmin.mDeviceAdmin, value);
+            // TODO show toast on error
+            mAdmin.updateSELinuxState();
+            updateViews();
+            return ret;