Commits

Robert Craig committed b501899

Add AppOps management console code.

This will allow us to drop our Settings.apk
changes and subsequently drop tracking that
project. The functionality offered here is
similar in nature.

Signed-off-by: rpcraig <rpcraig@tycho.ncsc.mil>

  • Participants
  • Parent commits 891b4c8

Comments (0)

Files changed (21)

 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
-LOCAL_STATIC_JAVA_LIBRARIES := guava
+LOCAL_STATIC_JAVA_LIBRARIES := guava \
+	android-support-v4 android-support-v13
 
 LOCAL_PRIVILEGED_MODULE := true
 

AndroidManifest.xml

     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
+    <!-- AppOps related -->
+    <uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application android:label="@string/app_name">
 
 # Keep all Fragments in this package, which are used by reflection.
 -keep class com.android.seandroid_admin.*
+-keep class com.android.seandroid_admin.appops.*

res/layout/app_ops_details.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2013, 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.
+*/
+-->
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    android:scrollbarStyle="@integer/preference_scrollbar_style">
+
+    <LinearLayout
+        android:id="@+id/all_details"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingBottom="5dip"
+        android:orientation="vertical">
+
+        <!-- App snippet -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+            android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+            android:orientation="vertical">
+
+            <!-- Application snippet label, version and icon -->
+            <include
+                layout="@layout/manage_applications_item"
+                android:id="@+id/app_snippet" />
+
+        </LinearLayout>
+
+        <!-- Operations list -->
+        <LinearLayout
+            android:id="@+id/operations_section"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+        </LinearLayout>
+    </LinearLayout>
+</ScrollView>

res/layout/app_ops_details_item.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingTop="8dip"
+    android:paddingBottom="8dip"
+    android:columnCount="3">
+
+    <ImageView
+        android:id="@+id/op_icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_column="0"
+        android:layout_row="0"
+        android:layout_rowSpan="2"
+        android:layout_marginEnd="8dip"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <TextView
+        android:id="@+id/op_name"
+        android:layout_column="1"
+        android:layout_row="0"
+        android:layout_gravity="fill_horizontal"
+        android:layout_marginTop="2dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@+id/op_time"
+        android:layout_column="1"
+        android:layout_row="1"
+        android:layout_gravity="fill_horizontal|top"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAlignment="viewStart" />
+
+    <Switch android:id="@+id/switchWidget"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="8dip"
+        android:layout_column="2"
+        android:layout_row="0"
+        android:layout_rowSpan="2"
+        android:padding="8dip"
+        android:focusable="false"
+        android:clickable="true" />
+
+</GridLayout>

res/layout/app_ops_item.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingTop="8dip"
+    android:paddingBottom="8dip"
+    android:columnCount="3">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_rowSpan="2"
+        android:layout_marginEnd="8dip"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_width="0dip"
+        android:layout_columnSpan="2"
+        android:layout_gravity="fill_horizontal"
+        android:layout_marginTop="2dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@+id/op_name"
+        android:layout_width="0dip"
+        android:layout_gravity="fill_horizontal|top"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@+id/op_time"
+        android:layout_marginStart="8dip"
+        android:layout_gravity="top"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</GridLayout>

res/layout/app_ops_summary.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2013, 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.
+*/
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <android.support.v4.view.ViewPager
+            android:id="@+id/pager"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+        <android.support.v4.view.PagerTabStrip
+                android:id="@+id/tabs"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:textAppearance="@style/TextAppearance.PagerTabs"
+                android:paddingLeft="@dimen/pager_tabs_padding"
+                android:paddingRight="@dimen/pager_tabs_padding">
+        </android.support.v4.view.PagerTabStrip>
+    </android.support.v4.view.ViewPager>
+
+</LinearLayout>

res/layout/manage_applications_item.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingTop="8dip"
+    android:paddingBottom="8dip"
+    android:columnCount="4">
+
+    <ImageView
+        android:id="@+id/app_icon"
+        android:layout_width="@android:dimen/app_icon_size"
+        android:layout_height="@android:dimen/app_icon_size"
+        android:layout_rowSpan="3"
+        android:layout_marginTop="11dip"
+        android:layout_marginEnd="8dip"
+        android:scaleType="centerInside"
+        android:contentDescription="@null" />
+
+    <TextView
+        android:id="@+id/app_name"
+        android:layout_width="0dip"
+        android:layout_columnSpan="2"
+        android:layout_gravity="fill_horizontal"
+        android:layout_marginTop="2dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textAlignment="viewStart" />
+
+    <CheckBox android:id="@+id/app_on_sdcard"
+        android:layout_marginStart="8dip"
+        android:layout_gravity="center_vertical"
+        android:layout_rowSpan="2"
+        android:visibility="gone"
+        android:clickable="false"
+        android:focusable="false" />
+
+    <TextView
+        android:id="@+id/app_size"
+        android:layout_width="0dip"
+        android:layout_gravity="fill_horizontal|top"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textAlignment="viewStart" />
+
+    <TextView
+        android:id="@+id/app_disabled"
+        android:layout_marginStart="8dip"
+        android:layout_gravity="top"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    <TextView
+        android:id="@+id/app_seinfo"
+        android:layout_width="0dip"
+        android:layout_gravity="fill_horizontal"
+        android:layout_columnSpan="3"
+        android:ellipsize="marquee"
+        android:singleLine="true"
+        android:textAlignment="viewStart"
+        android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</GridLayout>

res/values/arrays.xml

+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+     <!-- Names of categories of app ops tabs -->
+    <string-array name="app_ops_categories">
+        <item>Location</item>
+        <item>Personal</item>
+        <item>Messaging</item>
+        <item>Media</item>
+        <item>Device</item>
+    </string-array>
+
+    <!-- User display names for app ops codes -->
+    <string-array name="app_ops_summaries">
+        <item>coarse location</item>
+        <item>fine location</item>
+        <item>GPS</item>
+        <item>vibrate</item>
+        <item>read contacts</item>
+        <item>modify contacts</item>
+        <item>read call log</item>
+        <item>modify call log</item>
+        <item>read calendar</item>
+        <item>modify calendar</item>
+        <item>wi-fi scan</item>
+        <item>notification</item>
+        <item>cell scan</item>
+        <item>call phone</item>
+        <item>read SMS</item>
+        <item>write SMS</item>
+        <item>receive SMS</item>
+        <item>receive emergency SMS</item>
+        <item>receive MMS</item>
+        <item>receive WAP push</item>
+        <item>send SMS</item>
+        <item>read ICC SMS</item>
+        <item>write ICC SMS</item>
+        <item>modify settings</item>
+        <item>draw on top</item>
+        <item>access notifications</item>
+        <item>camera</item>
+        <item>record audio</item>
+        <item>play audio</item>
+        <item>read clipboard</item>
+        <item>modify clipboard</item>
+        <item>media buttons</item>
+        <item>audio focus</item>
+        <item>master volume</item>
+        <item>voice volume</item>
+        <item>ring volume</item>
+        <item>media volume</item>
+        <item>alarm volume</item>
+        <item>notification volume</item>
+        <item>bluetooth volume</item>
+        <item>keep awake</item>
+        <item>monitor location</item>
+        <item>monitor high power location</item>
+    </string-array>
+
+    <!-- User display names for app ops codes -->
+    <string-array name="app_ops_labels">
+        <item>Location</item>
+        <item>Location</item>
+        <item>Location</item>
+        <item>Vibrate</item>
+        <item>Read contacts</item>
+        <item>Modify contacts</item>
+        <item>Read call log</item>
+        <item>Modify call log</item>
+        <item>Read calendar</item>
+        <item>Modify calendar</item>
+        <item>Location</item>
+        <item>Post notification</item>
+        <item>Location</item>
+        <item>Call phone</item>
+        <item>Read SMS/MMS</item>
+        <item>Write SMS/MMS</item>
+        <item>Receive SMS/MMS</item>
+        <item>Receive SMS/MMS</item>
+        <item>Receive SMS/MMS</item>
+        <item>Receive SMS/MMS</item>
+        <item>Send SMS/MMS</item>
+        <item>Read SMS/MMS</item>
+        <item>Write SMS/MMS</item>
+        <item>Modify settings</item>
+        <item>Draw on top</item>
+        <item>Access notifications</item>
+        <item>Camera</item>
+        <item>Record audio</item>
+        <item>Play audio</item>
+        <item>Read clipboard</item>
+        <item>Modify clipboard</item>
+        <item>Media buttons</item>
+        <item>Audio focus</item>
+        <item>Master volume</item>
+        <item>Voice volume</item>
+        <item>Ring volume</item>
+        <item>Media volume</item>
+        <item>Alarm volume</item>
+        <item>Notification volume</item>
+        <item>Bluetooth volume</item>
+        <item>Keep awake</item>
+        <item>Location</item>
+        <item>Location</item>
+    </string-array>
+
+    <!-- app ops brief description and current state -->
+    <string-array name="app_ops_desc">
+    </string-array>
+
+</resources>

res/values/attrs.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<resources>
+    <declare-styleable name="WifiEncryptionState">
+        <attr name="state_encrypted" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="IconPreferenceScreen">
+        <attr name="icon" format="reference" />
+    </declare-styleable>
+    
+    <declare-styleable name="BatteryHistoryChart">
+        <!-- Base text color, typeface, size, and style. -->
+        <attr name="android:textAppearance" />
+        <!-- Text color. -->
+        <attr name="android:textColor" />
+        <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+        <attr name="android:textSize" />
+        <!-- Typeface (normal, sans, serif, monospace) for the text. -->
+        <attr name="android:typeface" />
+        <!-- Style (bold, italic, bolditalic) for the text. -->
+        <attr name="android:textStyle" />
+        <!-- Place a shadow of the specified color behind the text. -->
+        <attr name="android:shadowColor" />
+        <!-- Horizontal offset of the shadow. -->
+        <attr name="android:shadowDx" />
+        <!-- Vertical offset of the shadow. -->
+        <attr name="android:shadowDy" />
+        <!-- Radius of the shadow. -->
+        <attr name="android:shadowRadius" />
+    </declare-styleable>
+
+    <declare-styleable name="PercentageBarChart">
+        <!-- Background color -->
+        <attr name="emptyColor" format="color" />
+        <!-- Minimum tick width for each slice in the bar chart. -->
+        <attr name="minTickWidth" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="ChartView">
+        <!-- optimal width of the chart -->
+        <attr name="optimalWidth" format="dimension" />
+        <!-- how to weight extra space beyond optimal width -->
+        <attr name="optimalWidthWeight" format="float" />
+    </declare-styleable>
+
+    <declare-styleable name="ChartSweepView">
+        <attr name="sweepDrawable" format="reference" />
+        <attr name="followAxis">
+            <enum name="horizontal" value="0" />
+            <enum name="vertical" value="1" />
+        </attr>
+        <attr name="neighborMargin" format="dimension" />
+        <attr name="labelSize" format="dimension" />
+        <attr name="labelTemplate" format="reference" />
+        <attr name="labelColor" format="color" />
+    </declare-styleable>
+
+    <declare-styleable name="ChartGridView">
+        <attr name="primaryDrawable" format="reference" />
+        <attr name="secondaryDrawable" format="reference" />
+        <attr name="borderDrawable" format="reference" />
+        <attr name="labelColor" />
+    </declare-styleable>
+
+    <declare-styleable name="ChartNetworkSeriesView">
+        <attr name="strokeColor" format="color" />
+        <attr name="fillColor" format="color" />
+        <attr name="fillColorSecondary" format="color" />
+    </declare-styleable>
+
+    <attr name="apnPreferenceStyle" format="reference" />
+</resources>

res/values/colors.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+    <color name="black">#000</color>
+    <color name="red">#F00</color>
+
+    <color name="memory_used">#F00</color>
+    <color name="memory_avail">#333333</color>
+    <color name="memory_apps_usage">#77831A</color>
+    <color name="memory_downloads">#476093</color>
+    <color name="memory_dcim">#793A7F</color>
+    <color name="memory_music">#8E562A</color>
+    <color name="memory_cache">#479392</color>
+    <color name="memory_misc">#7C3030</color>
+    <color name="memory_user_light">#479392</color>
+    <color name="memory_user_dark">#316665</color>
+
+    <color name="crypt_keeper_clock_background">#ff9a9a9a</color>
+    <color name="crypt_keeper_clock_foreground">#ff666666</color>
+    <color name="crypt_keeper_clock_am_pm">#ff9a9a9a</color>
+
+    <color name="divider_color">#20ffffff</color>
+    <color name="title_color">@android:color/holo_blue_light</color>
+    <color name="setup_divider_color_dark">#33ffffff</color>
+    <color name="setup_divider_color_light">#33000000</color>
+
+    <color name="circle_avatar_frame_color">#ffffffff</color>
+    <color name="circle_avatar_frame_shadow_color">#80000000</color>
+    <color name="circle_avatar_frame_pressed_color">#ffffffff</color>
+</resources>

res/values/dimens.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<resources>
+    <dimen name="device_memory_usage_button_width">16dip</dimen>
+    <dimen name="device_memory_usage_button_height">32dip</dimen>
+    <dimen name="action_bar_switch_padding">16dip</dimen>
+
+    <dimen name="app_icon_size">56dip</dimen>
+    <dimen name="screen_margin_sides">64dip</dimen>
+    <dimen name="screen_margin_top">72dip</dimen>
+    <dimen name="screen_margin_bottom">48dip</dimen>
+    <dimen name="content_margin_left">16dip</dimen>
+    <dimen name="description_margin_top">26dip</dimen>
+    <dimen name="description_margin_sides">40dip</dimen>
+    <dimen name="bind_app_widget_dialog_checkbox_bottom_padding">16dip</dimen>
+    <dimen name="installed_app_details_bullet_offset">8dip</dimen>
+
+    <dimen name="data_usage_chart_height">252dip</dimen>
+    <dimen name="data_usage_chart_optimalWidth">440dip</dimen>
+
+    <dimen name="volume_seekbar_side_margin">8dip</dimen>
+
+    <dimen name="crypt_clock_size">100sp</dimen>
+
+    <item type="dimen" name="setup_title_height">15%</item>
+    <item type="dimen" name="setup_border_width">5%</item>
+    <dimen name="setup_margin_bottom">0dip</dimen>
+    <dimen name="setup_title_size">25dp</dimen>
+    <dimen name="setup_button_size">32dip</dimen>
+    <dimen name="setup_item_margin">16dip</dimen>
+
+    <dimen name="divider_height">3dip</dimen>
+    <dimen name="divider_margin_top">6dip</dimen>
+    <dimen name="divider_margin_bottom">7dip</dimen>
+    <dimen name="vert_divider_width">1dip</dimen>
+
+    <!--  Size of icons in the top-level of settings  -->
+    <dimen name="header_icon_width">28dp</dimen>
+    <dimen name="appwidget_min_width">280dip</dimen>
+    <dimen name="appwidget_min_height">40dip</dimen>
+
+    <dimen name="pager_tabs_padding">0dp</dimen>
+
+    <!-- Size of AppWidget previews in KeyguardAppWidgetPickActivity -->
+    <dimen name="appwidget_preview_width">140dip</dimen>
+    <dimen name="appwidget_preview_height">80dip</dimen>
+    <dimen name="keyguard_appwidget_picker_max_width">800dip</dimen>
+    <dimen name="keyguard_appwidget_picker_margin_left">6dip</dimen>
+    <dimen name="keyguard_appwidget_picker_margin_right">6dip</dimen>
+    <integer name="keyguard_appwidget_picker_cols">1</integer>
+
+    <dimen name="circle_avatar_size">48dp</dimen>
+    <dimen name="circle_avatar_frame_stroke_width">1dp</dimen>
+    <dimen name="circle_avatar_frame_shadow_radius">3dp</dimen>
+
+    <!-- Minimum width for the popup for updating a user's photo. -->
+    <dimen name="update_user_photo_popup_min_width">300dip</dimen>
+
+    <dimen name="captioning_preview_height">200dp</dimen>
+
+    <dimen name="settings_side_margin">@*android:dimen/preference_fragment_padding_side</dimen>
+</resources>

res/values/strings.xml

 <?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
     <string name="app_name">SEAdmin</string>
     <string name="activity_name">SEAdmin</string>
 
                          for further details concerning SEAdmin and policy reload abilities.
     </string>
 
+    <!-- Middleware Ops -->
+    <string name="mmac_category_title">Middleware Ops</string>
+    <string name="config_eops_mmac_title">AppOps</string>
+
+    <string name="app_ops_settings">App ops</string>
+    <string name="app_ops_running">Running</string>
+    <string name="app_ops_never_used">(Never used)</string>
+    <string name="version_text">version <xliff:g id="version_num">%1$s</xliff:g></string>
+
 </resources>

res/values/styles.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.
+-->
+
+<resources>
+
+    <style name="info_label">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textAppearance">@style/TextAppearance.info_label</item>
+        <item name="android:paddingEnd">4dip</item>
+    </style>
+
+    <style name="info_value">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textAppearance">@style/TextAppearance.info_value</item>
+    </style>
+
+    <style name="info_small">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:textAppearance">@style/TextAppearance.info_small</item>
+    </style>
+
+    <style name="info_layout">
+        <item name="android:orientation">vertical</item>
+        <item name="android:paddingStart">10dip</item>
+        <item name="android:paddingTop">10dip</item>
+        <item name="android:paddingEnd">10dip</item>
+        <item name="android:paddingBottom">10dip</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+
+    <style name="entry_layout">
+        <item name="android:orientation">horizontal</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="form_value">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">match_parent</item>
+    </style>
+
+
+    <style name="TextAppearance" parent="android:TextAppearance">
+    </style>
+
+    <style name="TextAppearance.info_label">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="TextAppearance.info_small">
+        <item name="android:textSize">12sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="TextAppearance.info_value">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+
+    <style name="TallTitleBarTheme" parent="android:Theme.NoTitleBar">
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
+    <style name="Theme.CreateShortCut" parent="android:Theme.Holo.DialogWhenLarge">
+    </style>
+
+    <style name="PreferenceHeaderPanelSinglePane">
+        <item name="android:layout_marginStart">0dp</item>
+        <item name="android:layout_marginEnd">0dp</item>
+        <item name="android:background">@null</item>
+    </style>
+
+    <style name="PreferencePanelSinglePane" parent="@*android:style/PreferencePanel">
+        <item name="android:layout_marginStart">0dp</item>
+        <item name="android:layout_marginEnd">0dp</item>
+        <item name="android:paddingStart">0dp</item>
+        <item name="android:paddingEnd">0dp</item>
+        <item name="android:background">@null</item>
+        <item name="android:scrollbarStyle">outsideOverlay</item>
+    </style>
+
+    <style name="PreferenceHeaderListSinglePane" parent="@*android:style/PreferenceHeaderList">
+        <item name="android:paddingStart">@dimen/settings_side_margin</item>
+        <item name="android:paddingEnd">@dimen/settings_side_margin</item>
+        <item name="android:paddingTop">0dp</item>
+        <item name="android:paddingBottom">0dp</item>
+        <item name="android:layout_marginStart">0dp</item>
+        <item name="android:layout_marginEnd">0dp</item>
+        <item name="android:layout_marginTop">0dp</item>
+        <item name="android:layout_marginBottom">0dp</item>
+        <item name="android:scrollbarStyle">outsideOverlay</item>
+    </style>
+
+    <style name="PreferenceFragmentListSinglePane" parent="@*android:style/PreferenceFragmentList">
+        <item name="android:paddingStart">@dimen/settings_side_margin</item>
+        <item name="android:paddingEnd">@dimen/settings_side_margin</item>
+        <item name="android:layout_marginStart">0dp</item>
+        <item name="android:layout_marginEnd">0dp</item>
+        <item name="android:scrollbarStyle">outsideOverlay</item>
+    </style>
+
+    <style name="VertDivider">
+        <item name="android:layout_width">@dimen/vert_divider_width</item>
+        <item name="android:layout_height">fill_parent</item>
+        <item name="android:background">@color/divider_color</item>
+        <item name="android:focusable">false</item>
+        <item name="android:clickable">false</item>
+    </style>
+
+    <!-- We'd like to have this as 16dip hight including paddingTop/paddingBottom to
+         be consistent with ProgressBar -->
+    <style name="TopDivider">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/divider_height</item>
+        <item name="android:background">?attr/setup_divider_color</item>
+        <item name="android:focusable">false</item>
+        <item name="android:clickable">false</item>
+        <item name="android:layout_marginTop">@dimen/divider_margin_top</item>
+        <item name="android:layout_marginBottom">@dimen/divider_margin_bottom</item>
+    </style>
+
+    <style name="SetupTitle">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textSize">@dimen/setup_title_size</item>
+        <item name="android:textColor">@color/title_color</item>
+        <item name="android:clickable">false</item>
+        <item name="android:longClickable">false</item>
+    </style>
+
+    <style name="wifi_item">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginTop">8dip</item>
+        <item name="android:layout_marginStart">16dip</item>
+        <item name="android:layout_marginEnd">16dip</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:gravity">start</item>
+    </style>
+
+    <style name="wifi_item_label">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="wifi_item_content">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:textAlignment">viewStart</item>
+    </style>
+
+    <style name="wifi_item_edit_content">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <style name="wifi_section">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:orientation">vertical</item>
+    </style>
+
+    <style name="setup_wizard_button">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+    </style>
+
+    <style name="Transparent">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+    </style>
+
+    <style name="CryptKeeperBlankTheme" parent="@android:style/Theme.Holo.NoActionBar">
+        <item name="android:background">#ff000000</item>
+    </style>
+
+    <style name="SecurityPreferenceButtonContainer" parent="@android:style/Holo.SegmentedButton">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:weightSum">2</item>
+        <item name="android:dividerPadding">8dip</item>
+    </style>
+
+    <style name="SecurityPreferenceButton" parent="@android:style/Widget.Holo.Button.Borderless">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
+    <style name="vpn_label">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+    </style>
+
+    <style name="vpn_value">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:singleLine">true</item>
+    </style>
+<!--
+    <style name="InputMethodPreferenceStyle">
+        <item name="android:layout">@layout/preference_inputmethod</item>
+        <item name="android:widgetLayout">@layout/preference_inputmethod_widget</item>
+    </style>
+-->
+    <style name="TextAppearance.Switch" parent="@*android:style/TextAppearance.Holo.Widget.Switch">
+        <item name="android:textAllCaps">true</item>
+    </style>
+
+    <style name="TextAppearance.PagerTabs" parent="@android:style/TextAppearance.DeviceDefault.Small">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
+    <style name="KeyguardAppWidgetItem">
+        <item name="android:textSize">18sp</item>
+    </style>
+
+    <!-- Scrollbar style OUTSIDE_OVERLAY -->
+    <integer name="preference_scrollbar_style">33554432</integer>
+<!--
+    <style name="ApnPreference">
+        <item name="android:layout">@layout/apn_preference_layout</item>
+    </style>
+-->
+</resources>

res/values/themes.xml

+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<resources>
+    <attr name="setup_divider_color" format="reference" />
+
+    <style name="SetupWizardWifiTheme" parent="android:Theme.Holo.NoActionBar">
+        <item name="android:windowSoftInputMode">adjustPan</item>
+        <item name="@*android:preferencePanelStyle">@*android:style/PreferencePanel.Dialog</item>
+        <item name="android:alertDialogTheme">@style/Theme.WifiDialog</item>
+        <item name="setup_divider_color">@color/setup_divider_color_dark</item>
+    </style>
+
+    <style name="SetupWizardWifiTheme.Light" parent="android:Theme.Holo.Light.NoActionBar">
+        <item name="android:windowSoftInputMode">adjustPan</item>
+        <item name="@*android:preferencePanelStyle">@*android:style/PreferencePanel.Dialog</item>
+        <item name="android:alertDialogTheme">@style/Theme.Light.WifiDialog</item>
+        <item name="setup_divider_color">@color/setup_divider_color_light</item>
+    </style>
+
+    <style name="Theme.WifiDialog" parent="@*android:style/Theme.Holo.Dialog.Alert">
+        <item name="android:windowSoftInputMode">adjustResize</item>
+    </style>
+
+    <style name="Theme.Light.WifiDialog" parent="@*android:style/Theme.Holo.Light.Dialog.Alert">
+        <item name="android:windowSoftInputMode">adjustResize</item>
+    </style>
+
+    <style name="Theme.Settings" parent="@android:style/Theme.Holo">
+        <item name="@*android:preferenceHeaderPanelStyle">@style/PreferenceHeaderPanelSinglePane</item>
+        <item name="@*android:preferencePanelStyle">@style/PreferencePanelSinglePane</item>
+        <item name="@*android:preferenceListStyle">@style/PreferenceHeaderListSinglePane</item>
+        <item name="@*android:preferenceFragmentListStyle">@style/PreferenceFragmentListSinglePane</item>
+        <item name="@*android:preferenceFragmentPaddingSide">@dimen/settings_side_margin</item>
+        <item name="android:actionBarStyle">@android:style/Widget.Holo.ActionBar.Solid</item>
+    </style>
+</resources>

res/xml/enabled_headers.xml

         android:fragment="com.android.seandroid_admin.ConfigUpdateFragment"
         android:title="@string/config_update_mmac_title" />
 
+    <!-- EOps Fragment -->
+    <header android:id="@+id/update_category"
+        android:title="@string/mmac_category_title" />
+
+    <header android:id="@+id/config_update_mmac"
+        android:fragment="com.android.seandroid_admin.appops.AppOpsSummary"
+        android:title="@string/config_eops_mmac_title" />
+
     <!-- About Fragment -->
     <header android:id="@+id/about_category"
         android:title="@string/about_category_title" />

src/com/android/seandroid_admin/SEAndroidAdminActivity.java

     @Override
     protected boolean isValidFragment(String fragmentName) {
         return "com.android.seandroid_admin.AboutFragment".equals(fragmentName) ||
-            "com.android.seandroid_admin.ConfigUpdateFragment".equals(fragmentName);
+            "com.android.seandroid_admin.ConfigUpdateFragment".equals(fragmentName) ||
+            "com.android.seandroid_admin.appops.AppOpsSummary".equals(fragmentName) ||
+            "com.android.seandroid_admin.appops.AppOpsDetails".equals(fragmentName);
     }
 
     @Override

src/com/android/seandroid_admin/appops/AppOpsCategory.java

+/**
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.seandroid_admin.appops;
+
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.AsyncTaskLoader;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.Loader;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.List;
+
+import com.android.seandroid_admin.R;
+import com.android.seandroid_admin.appops.AppOpsState.AppOpEntry;
+
+public class AppOpsCategory extends ListFragment implements
+        LoaderManager.LoaderCallbacks<List<AppOpEntry>> {
+
+    private static final int RESULT_APP_DETAILS = 1;
+
+    AppOpsState mState;
+
+    // This is the Adapter being used to display the list's data.
+    AppListAdapter mAdapter;
+
+    String mCurrentPkgName;
+
+    public AppOpsCategory() {
+    }
+
+    public AppOpsCategory(AppOpsState.OpsTemplate template) {
+        Bundle args = new Bundle();
+        args.putParcelable("template", template);
+        setArguments(args);
+    }
+
+    /**
+     * Helper for determining if the configuration has changed in an interesting
+     * way so we need to rebuild the app list.
+     */
+    public static class InterestingConfigChanges {
+        final Configuration mLastConfiguration = new Configuration();
+        int mLastDensity;
+
+        boolean applyNewConfig(Resources res) {
+            int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+            boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
+            if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
+                    |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
+                mLastDensity = res.getDisplayMetrics().densityDpi;
+                return true;
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Helper class to look for interesting changes to the installed apps
+     * so that the loader can be updated.
+     */
+    public static class PackageIntentReceiver extends BroadcastReceiver {
+        final AppListLoader mLoader;
+
+        public PackageIntentReceiver(AppListLoader loader) {
+            mLoader = loader;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addDataScheme("package");
+            mLoader.getContext().registerReceiver(this, filter);
+            // Register for events related to sdcard installation.
+            IntentFilter sdFilter = new IntentFilter();
+            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+            mLoader.getContext().registerReceiver(this, sdFilter);
+        }
+
+        @Override public void onReceive(Context context, Intent intent) {
+            // Tell the loader about the change.
+            mLoader.onContentChanged();
+        }
+    }
+
+    /**
+     * A custom Loader that loads all of the installed applications.
+     */
+    public static class AppListLoader extends AsyncTaskLoader<List<AppOpEntry>> {
+        final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
+        final AppOpsState mState;
+        final AppOpsState.OpsTemplate mTemplate;
+
+        List<AppOpEntry> mApps;
+        PackageIntentReceiver mPackageObserver;
+
+        public AppListLoader(Context context, AppOpsState state, AppOpsState.OpsTemplate template) {
+            super(context);
+            mState = state;
+            mTemplate = template;
+        }
+
+        @Override public List<AppOpEntry> loadInBackground() {
+            return mState.buildState(mTemplate);
+        }
+
+        /**
+         * Called when there is new data to deliver to the client.  The
+         * super class will take care of delivering it; the implementation
+         * here just adds a little more logic.
+         */
+        @Override public void deliverResult(List<AppOpEntry> apps) {
+            if (isReset()) {
+                // An async query came in while the loader is stopped.  We
+                // don't need the result.
+                if (apps != null) {
+                    onReleaseResources(apps);
+                }
+            }
+            List<AppOpEntry> oldApps = apps;
+            mApps = apps;
+
+            if (isStarted()) {
+                // If the Loader is currently started, we can immediately
+                // deliver its results.
+                super.deliverResult(apps);
+            }
+
+            // At this point we can release the resources associated with
+            // 'oldApps' if needed; now that the new result is delivered we
+            // know that it is no longer in use.
+            if (oldApps != null) {
+                onReleaseResources(oldApps);
+            }
+        }
+
+        /**
+         * Handles a request to start the Loader.
+         */
+        @Override protected void onStartLoading() {
+            // We don't monitor changed when loading is stopped, so need
+            // to always reload at this point.
+            onContentChanged();
+
+            if (mApps != null) {
+                // If we currently have a result available, deliver it
+                // immediately.
+                deliverResult(mApps);
+            }
+
+            // Start watching for changes in the app data.
+            if (mPackageObserver == null) {
+                mPackageObserver = new PackageIntentReceiver(this);
+            }
+
+            // Has something interesting in the configuration changed since we
+            // last built the app list?
+            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
+
+            if (takeContentChanged() || mApps == null || configChange) {
+                // If the data has changed since the last time it was loaded
+                // or is not currently available, start a load.
+                forceLoad();
+            }
+        }
+
+        /**
+         * Handles a request to stop the Loader.
+         */
+        @Override protected void onStopLoading() {
+            // Attempt to cancel the current load task if possible.
+            cancelLoad();
+        }
+
+        /**
+         * Handles a request to cancel a load.
+         */
+        @Override public void onCanceled(List<AppOpEntry> apps) {
+            super.onCanceled(apps);
+
+            // At this point we can release the resources associated with 'apps'
+            // if needed.
+            onReleaseResources(apps);
+        }
+
+        /**
+         * Handles a request to completely reset the Loader.
+         */
+        @Override protected void onReset() {
+            super.onReset();
+
+            // Ensure the loader is stopped
+            onStopLoading();
+
+            // At this point we can release the resources associated with 'apps'
+            // if needed.
+            if (mApps != null) {
+                onReleaseResources(mApps);
+                mApps = null;
+            }
+
+            // Stop monitoring for changes.
+            if (mPackageObserver != null) {
+                getContext().unregisterReceiver(mPackageObserver);
+                mPackageObserver = null;
+            }
+        }
+
+        /**
+         * Helper function to take care of releasing resources associated
+         * with an actively loaded data set.
+         */
+        protected void onReleaseResources(List<AppOpEntry> apps) {
+            // For a simple List<> there is nothing to do.  For something
+            // like a Cursor, we would close it here.
+        }
+    }
+
+    public static class AppListAdapter extends BaseAdapter {
+        private final Resources mResources;
+        private final LayoutInflater mInflater;
+        private final AppOpsState mState;
+
+        List<AppOpEntry> mList;
+
+        public AppListAdapter(Context context, AppOpsState state) {
+            mResources = context.getResources();
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            mState = state;
+        }
+
+        public void setData(List<AppOpEntry> data) {
+            mList = data;
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mList != null ? mList.size() : 0;
+        }
+
+        @Override
+        public AppOpEntry getItem(int position) {
+            return mList.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        /**
+         * Populate new items in the list.
+         */
+        @Override public View getView(int position, View convertView, ViewGroup parent) {
+            View view;
+
+            if (convertView == null) {
+                view = mInflater.inflate(R.layout.app_ops_item, parent, false);
+            } else {
+                view = convertView;
+            }
+
+            AppOpEntry item = getItem(position);
+            ((ImageView)view.findViewById(R.id.app_icon)).setImageDrawable(
+                    item.getAppEntry().getIcon());
+            ((TextView)view.findViewById(R.id.app_name)).setText(item.getAppEntry().getLabel());
+            ((TextView)view.findViewById(R.id.op_name)).setText(item.getSummaryText(mState));
+            ((TextView)view.findViewById(R.id.op_time)).setText(
+                    item.getTimeText(mResources, false));
+
+            return view;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mState = new AppOpsState(getActivity());
+    }
+
+    @Override public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Give some text to display if there is no data.  In a real
+        // application this would come from a resource.
+        setEmptyText("No applications");
+
+        // We have a menu item to show in action bar.
+        setHasOptionsMenu(true);
+
+        // Create an empty adapter we will use to display the loaded data.
+        mAdapter = new AppListAdapter(getActivity(), mState);
+        setListAdapter(mAdapter);
+
+        // Start out with a progress indicator.
+        setListShown(false);
+
+        // Prepare the loader.
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    // utility method used to start sub activity
+    private void startApplicationDetailsActivity() {
+        // start new fragment to display extended information
+        Bundle args = new Bundle();
+        args.putString(AppOpsDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
+
+        PreferenceActivity pa = (PreferenceActivity)getActivity();
+        pa.startPreferencePanel(AppOpsDetails.class.getName(), args,
+                R.string.app_ops_settings, null, this, RESULT_APP_DETAILS);
+    }
+    
+    @Override public void onListItemClick(ListView l, View v, int position, long id) {
+        AppOpEntry entry = mAdapter.getItem(position);
+        if (entry != null) {
+            mCurrentPkgName = entry.getAppEntry().getApplicationInfo().packageName;
+            startApplicationDetailsActivity();
+        }
+    }
+
+    @Override public Loader<List<AppOpEntry>> onCreateLoader(int id, Bundle args) {
+        Bundle fargs = getArguments();
+        AppOpsState.OpsTemplate template = null;
+        if (fargs != null) {
+            template = (AppOpsState.OpsTemplate)fargs.getParcelable("template");
+        }
+        return new AppListLoader(getActivity(), mState, template);
+    }
+
+    @Override public void onLoadFinished(Loader<List<AppOpEntry>> loader, List<AppOpEntry> data) {
+        // Set the new data in the adapter.
+        mAdapter.setData(data);
+
+        // The list should now be shown.
+        if (isResumed()) {
+            setListShown(true);
+        } else {
+            setListShownNoAnimation(true);
+        }
+    }
+
+    @Override public void onLoaderReset(Loader<List<AppOpEntry>> loader) {
+        // Clear the data in the adapter.
+        mAdapter.setData(null);
+    }
+}

src/com/android/seandroid_admin/appops/AppOpsDetails.java

+/**
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.seandroid_admin.appops;
+
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import java.util.List;
+import com.android.seandroid_admin.R;
+
+import android.preference.PreferenceFrameLayout;
+
+public class AppOpsDetails extends Fragment {
+    static final String TAG = "AppOpsDetails";
+
+    public static final String APP_CHG = "chg";
+
+    public static final String ARG_PACKAGE_NAME = "package";
+
+    private AppOpsState mState;
+    private PackageManager mPm;
+    private AppOpsManager mAppOps;
+    private PackageInfo mPackageInfo;
+    private LayoutInflater mInflater;
+    private View mRootView;
+    private TextView mAppVersion;
+    private TextView mAppSeinfo;
+    private LinearLayout mOperationsSection;
+
+    // Utility method to set application label and icon.
+    private void setAppLabelAndIcon(PackageInfo pkgInfo) {
+        final View appSnippet = mRootView.findViewById(R.id.app_snippet);
+        appSnippet.setPaddingRelative(0, appSnippet.getPaddingTop(), 0, appSnippet.getPaddingBottom());
+
+        ImageView icon = (ImageView) appSnippet.findViewById(R.id.app_icon);
+        icon.setImageDrawable(mPm.getApplicationIcon(pkgInfo.applicationInfo));
+        // Set application name.
+        TextView label = (TextView) appSnippet.findViewById(R.id.app_name);
+        label.setText(mPm.getApplicationLabel(pkgInfo.applicationInfo));
+        // Version number of application
+        mAppVersion = (TextView) appSnippet.findViewById(R.id.app_size);
+
+        mAppSeinfo = (TextView) appSnippet.findViewById(R.id.app_seinfo);
+        mAppSeinfo.setText("seinfo: "+ pkgInfo.applicationInfo.seinfo);
+
+        if (pkgInfo.versionName != null) {
+            mAppVersion.setVisibility(View.VISIBLE);
+            mAppVersion.setText(getActivity().getString(R.string.version_text,
+                    String.valueOf(pkgInfo.versionName)));
+        } else {
+            mAppVersion.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private String retrieveAppEntry() {
+        final Bundle args = getArguments();
+        String packageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
+        if (packageName == null) {
+            Intent intent = (args == null) ?
+                    getActivity().getIntent() : (Intent) args.getParcelable("intent");
+            if (intent != null) {
+                packageName = intent.getData().getSchemeSpecificPart();
+            }
+        }
+        try {
+            mPackageInfo = mPm.getPackageInfo(packageName,
+                    PackageManager.GET_DISABLED_COMPONENTS |
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Exception when retrieving package:" + packageName, e);
+            mPackageInfo = null;
+        }
+
+        return packageName;
+    }
+
+    private boolean refreshUi() {
+        if (mPackageInfo == null) {
+            return false;
+        }
+
+        setAppLabelAndIcon(mPackageInfo);
+
+        Resources res = getActivity().getResources();
+
+        mOperationsSection.removeAllViews();
+        String lastPermGroup = "";
+        for (AppOpsState.OpsTemplate tpl : AppOpsState.ALL_TEMPLATES) {
+            List<AppOpsState.AppOpEntry> entries = mState.buildState(tpl,
+                    mPackageInfo.applicationInfo.uid, mPackageInfo.packageName);
+            for (final AppOpsState.AppOpEntry entry : entries) {
+                final AppOpsManager.OpEntry firstOp = entry.getOpEntry(0);
+                final View view = mInflater.inflate(R.layout.app_ops_details_item,
+                        mOperationsSection, false);
+                mOperationsSection.addView(view);
+                String perm = AppOpsManager.opToPermission(firstOp.getOp());
+                if (perm != null) {
+                    try {
+                        PermissionInfo pi = mPm.getPermissionInfo(perm, 0);
+                        if (pi.group != null && !lastPermGroup.equals(pi.group)) {
+                            lastPermGroup = pi.group;
+                            PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(pi.group, 0);
+                            if (pgi.icon != 0) {
+                                ((ImageView)view.findViewById(R.id.op_icon)).setImageDrawable(
+                                        pgi.loadIcon(mPm));
+                            }
+                        }
+                    } catch (NameNotFoundException e) {
+                    }
+                }
+                ((TextView)view.findViewById(R.id.op_name)).setText(
+                        entry.getSwitchText(mState));
+                ((TextView)view.findViewById(R.id.op_time)).setText(
+                        entry.getTimeText(res, true));
+                Switch sw = (Switch)view.findViewById(R.id.switchWidget);
+                final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp());
+                sw.setChecked(mAppOps.checkOp(switchOp, entry.getPackageOps().getUid(),
+                        entry.getPackageOps().getPackageName()) == AppOpsManager.MODE_ALLOWED);
+                sw.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() {
+                    @Override
+                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                        mAppOps.setMode(switchOp, entry.getPackageOps().getUid(),
+                                entry.getPackageOps().getPackageName(), isChecked
+                                ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+                    }
+                });
+            }
+        }
+
+        return true;
+    }
+
+    private void setIntentAndFinish(boolean finish, boolean appChanged) {
+        Intent intent = new Intent();
+        intent.putExtra(APP_CHG, appChanged);
+        PreferenceActivity pa = (PreferenceActivity)getActivity();
+        pa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
+    }
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mState = new AppOpsState(getActivity());
+        mPm = getActivity().getPackageManager();
+        mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE);
+
+        retrieveAppEntry();
+
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.app_ops_details, container, false);
+        prepareCustomPreferencesList(container, view, view, false);
+
+        mRootView = view;
+        mOperationsSection = (LinearLayout)view.findViewById(R.id.operations_section);
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (!refreshUi()) {
+            setIntentAndFinish(true, true);
+        }
+    }
+
+    private static void prepareCustomPreferencesList(ViewGroup parent, View child, View list, boolean ignoreSidePadding) {
+        final boolean movePadding = list.getScrollBarStyle() == View.SCROLLBARS_OUTSIDE_OVERLAY;
+        if (movePadding && parent instanceof PreferenceFrameLayout) {
+            ((PreferenceFrameLayout.LayoutParams) child.getLayoutParams()).removeBorders = true;
+
+            final Resources res = list.getResources();
+            final int paddingSide = res.getDimensionPixelSize(R.dimen.settings_side_margin);
+            final int paddingBottom = res.getDimensionPixelSize(com.android.internal.R.dimen.preference_fragment_padding_bottom);
+            final int effectivePaddingSide = ignoreSidePadding ? 0 : paddingSide;
+            list.setPaddingRelative(effectivePaddingSide, 0, effectivePaddingSide, paddingBottom);
+        }
+    }
+
+    
+}

src/com/android/seandroid_admin/appops/AppOpsState.java

+/**
+ * Copyright (C) 2013 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.
+ */
+
+package com.android.seandroid_admin.appops;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.DateUtils;
+
+import android.util.Log;
+import android.util.SparseArray;
+import com.android.seandroid_admin.R;
+
+import java.io.File;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+public class AppOpsState {
+    static final String TAG = "AppOpsState";
+    static final boolean DEBUG = false;
+
+    final Context mContext;
+    final AppOpsManager mAppOps;
+    final PackageManager mPm;
+    final CharSequence[] mOpSummaries;
+    final CharSequence[] mOpLabels;
+
+    List<AppOpEntry> mApps;
+
+    public AppOpsState(Context context) {
+        mContext = context;
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        mPm = context.getPackageManager();
+        mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries);
+        mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels);
+    }
+
+    public static class OpsTemplate implements Parcelable {
+        public final int[] ops;
+        public final boolean[] showPerms;
+
+        public OpsTemplate(int[] _ops, boolean[] _showPerms) {
+            ops = _ops;
+            showPerms = _showPerms;
+        }
+
+        OpsTemplate(Parcel src) {
+            ops = src.createIntArray();
+            showPerms = src.createBooleanArray();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeIntArray(ops);
+            dest.writeBooleanArray(showPerms);
+        }
+
+        public static final Creator<OpsTemplate> CREATOR = new Creator<OpsTemplate>() {
+            @Override public OpsTemplate createFromParcel(Parcel source) {
+                return new OpsTemplate(source);
+            }
+
+            @Override public OpsTemplate[] newArray(int size) {
+                return new OpsTemplate[size];
+            }
+        };
+    }
+
+    public static final OpsTemplate LOCATION_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_COARSE_LOCATION,
+                    AppOpsManager.OP_FINE_LOCATION,
+                    AppOpsManager.OP_GPS,
+                    AppOpsManager.OP_WIFI_SCAN,
+                    AppOpsManager.OP_NEIGHBORING_CELLS,
+                    AppOpsManager.OP_MONITOR_LOCATION,
+                    AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION},
+            new boolean[] { true,
+                    true,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false}
+            );
+
+    public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_READ_CONTACTS,
+                    AppOpsManager.OP_WRITE_CONTACTS,
+                    AppOpsManager.OP_READ_CALL_LOG,
+                    AppOpsManager.OP_WRITE_CALL_LOG,
+                    AppOpsManager.OP_READ_CALENDAR,
+                    AppOpsManager.OP_WRITE_CALENDAR,
+                    AppOpsManager.OP_READ_CLIPBOARD,
+                    AppOpsManager.OP_WRITE_CLIPBOARD },
+            new boolean[] { true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    false,
+                    false }
+            );
+
+    public static final OpsTemplate MESSAGING_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_READ_SMS,
+                    AppOpsManager.OP_RECEIVE_SMS,
+                    AppOpsManager.OP_RECEIVE_EMERGECY_SMS,
+                    AppOpsManager.OP_RECEIVE_MMS,
+                    AppOpsManager.OP_RECEIVE_WAP_PUSH,
+                    AppOpsManager.OP_WRITE_SMS,
+                    AppOpsManager.OP_SEND_SMS,
+                    AppOpsManager.OP_READ_ICC_SMS,
+                    AppOpsManager.OP_WRITE_ICC_SMS },
+            new boolean[] { true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true }
+            );
+
+    public static final OpsTemplate MEDIA_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_VIBRATE,
+                    AppOpsManager.OP_CAMERA,
+                    AppOpsManager.OP_RECORD_AUDIO,
+                    AppOpsManager.OP_PLAY_AUDIO,
+                    AppOpsManager.OP_TAKE_MEDIA_BUTTONS,
+                    AppOpsManager.OP_TAKE_AUDIO_FOCUS,
+                    AppOpsManager.OP_AUDIO_MASTER_VOLUME,
+                    AppOpsManager.OP_AUDIO_VOICE_VOLUME,
+                    AppOpsManager.OP_AUDIO_RING_VOLUME,
+                    AppOpsManager.OP_AUDIO_MEDIA_VOLUME,
+                    AppOpsManager.OP_AUDIO_ALARM_VOLUME,
+                    AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME,
+                    AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, },
+            new boolean[] { false,
+                    true,
+                    true,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false,
+                    false }
+            );
+
+    public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_POST_NOTIFICATION,
+                    AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+                    AppOpsManager.OP_CALL_PHONE,
+                    AppOpsManager.OP_WRITE_SETTINGS,
+                    AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+                    AppOpsManager.OP_WAKE_LOCK },
+            new boolean[] { false,
+                    true,
+                    true,
+                    true,
+                    true,
+                    true,  }
+            );
+
+    public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] {
+            LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE,
+            MEDIA_TEMPLATE, DEVICE_TEMPLATE
+    };
+
+    /**
+     * This class holds the per-item data in our Loader.
+     */
+    public static class AppEntry {
+        private final AppOpsState mState;
+        private final ApplicationInfo mInfo;
+        private final File mApkFile;
+        private final SparseArray<AppOpsManager.OpEntry> mOps
+                = new SparseArray<AppOpsManager.OpEntry>();
+        private final SparseArray<AppOpEntry> mOpSwitches
+                = new SparseArray<AppOpEntry>();
+        private String mLabel;
+        private Drawable mIcon;
+        private boolean mMounted;
+
+        public AppEntry(AppOpsState state, ApplicationInfo info) {
+            mState = state;
+            mInfo = info;
+            mApkFile = new File(info.sourceDir);
+        }
+
+        public void addOp(AppOpEntry entry, AppOpsManager.OpEntry op) {
+            mOps.put(op.getOp(), op);
+            mOpSwitches.put(AppOpsManager.opToSwitch(op.getOp()), entry);
+        }
+
+        public boolean hasOp(int op) {
+            return mOps.indexOfKey(op) >= 0;
+        }
+
+        public AppOpEntry getOpSwitch(int op) {
+            return mOpSwitches.get(AppOpsManager.opToSwitch(op));
+        }
+
+        public ApplicationInfo getApplicationInfo() {
+            return mInfo;
+        }
+
+        public String getLabel() {
+            return mLabel;
+        }
+
+        public Drawable getIcon() {
+            if (mIcon == null) {
+                if (mApkFile.exists()) {
+                    mIcon = mInfo.loadIcon(mState.mPm);
+                    return mIcon;
+                } else {
+                    mMounted = false;
+                }
+            } else if (!mMounted) {
+                // If the app wasn't mounted but is now mounted, reload
+                // its icon.
+                if (mApkFile.exists()) {
+                    mMounted = true;
+                    mIcon = mInfo.loadIcon(mState.mPm);
+                    return mIcon;
+                }
+            } else {
+                return mIcon;
+            }