Port Superuser management UI
Port the feature from our Android 9 implementation, originally from
LineageOS.
Issue: FP2A10-127
Change-Id: Ic93f926b9f00d22380e65c4967685562485505ce
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 842a902..de4296b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1226,6 +1226,21 @@
</intent-filter>
</activity>
+ <activity android:name="Settings$AppOpsSummaryActivity"
+ android:label="@string/privacy_guard_manager_title"
+ android:taskAffinity=""
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <action android:name="android.settings.APP_OPS_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE_LAUNCH" />
+ <category android:name="com.android.settings.SHORTCUT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.appops.AppOpsSummary" />
+ </activity>
+
<activity
android:name="Settings$LocationSettingsActivity"
android:label="@string/location_settings_title"
diff --git a/res/layout/app_ops_summary.xml b/res/layout/app_ops_summary.xml
new file mode 100644
index 0000000..01510f5
--- /dev/null
+++ b/res/layout/app_ops_summary.xml
@@ -0,0 +1,43 @@
+<?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">
+
+ <androidx.viewpager.widget.ViewPager
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <androidx.viewpager.widget.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">
+ </androidx.viewpager.widget.PagerTabStrip>
+ </androidx.viewpager.widget.ViewPager>
+
+</LinearLayout>
diff --git a/res/menu/appops_manager.xml b/res/menu/appops_manager.xml
new file mode 100644
index 0000000..393d827
--- /dev/null
+++ b/res/menu/appops_manager.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The CyanogenMod 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/show_user_apps"
+ android:title="@string/app_ops_show_user_apps"
+ android:checkable="true" />
+ <item android:id="@+id/show_system_apps"
+ android:title="@string/app_ops_show_system_apps"
+ android:checkable="true" />
+</menu>
diff --git a/res/values/cm_strings.xml b/res/values/cm_strings.xml
index af6954f..8e71ea8 100644
--- a/res/values/cm_strings.xml
+++ b/res/values/cm_strings.xml
@@ -16,6 +16,28 @@
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- App ops permissions -->
+ <string name="app_ops_permissions_allowed">Allowed</string>
+ <string name="app_ops_permissions_ignored">Ignored</string>
+ <string name="app_ops_permissions_always_ask">Always ask</string>
+
+ <!-- App ops menu options -->
+ <string name="app_ops_show_user_apps">Show user apps</string>
+ <string name="app_ops_show_system_apps">Show built-in apps</string>
+
+ <!-- Names of categories of app ops tabs - extension of AOSP -->
+ <string name="app_ops_categories_location">Location</string>
+ <string name="app_ops_categories_personal">Personal</string>
+ <string name="app_ops_categories_messaging">Messaging</string>
+ <string name="app_ops_categories_media">Media</string>
+ <string name="app_ops_categories_device">Device</string>
+ <string name="app_ops_categories_run_in_background">Run in background</string>
+ <string name="app_ops_categories_bootup">Bootup</string>
+ <string name="app_ops_categories_su">Root access</string>
+ <string name="app_ops_categories_other">Other</string>
+
+ <string name="app_ops_labels_su">Get root access</string>
+
<!-- Manual provisioning support -->
<string name="sim_enabler_summary"><xliff:g id="displayName">%1$s</xliff:g> is <xliff:g id="status" example="disabled">%2$s</xliff:g></string>
<string name="sim_disabled">disabled</string>
@@ -39,4 +61,8 @@
<string name="root_access_apps">Apps only</string>
<string name="root_access_adb">ADB only</string>
<string name="root_access_all">Apps and ADB</string>
+
+ <!-- Preference link for root appops -->
+ <string name="root_appops_title">Manage root accesses</string>
+ <string name="root_appops_summary">View and control the root rules</string>
</resources>
diff --git a/res/values/lineage_arrays.xml b/res/values/lineage_arrays.xml
index 8e51bee..a0a2caf 100644
--- a/res/values/lineage_arrays.xml
+++ b/res/values/lineage_arrays.xml
@@ -40,4 +40,9 @@
<item>0</item>
<item>2</item>
</string-array>
+
+ <!-- Names of categories of app ops tabs - extension of AOSP -->
+ <string-array name="app_ops_categories_lineage" translatable="false">
+ <item>@string/app_ops_categories_su</item>
+ </string-array>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c95c69f..eeb8573 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11447,4 +11447,7 @@
<!-- Label for button to not allow grant the permission for remote devices. [CHAR_LIMIT=50] -->
<string name="request_manage_bluetooth_permission_dont_allow">Don\u2019t allow</string>
+
+ <!-- Privacy Guard -->
+ <string name="privacy_guard_manager_title">Privacy Guard</string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d3d3199..c311805 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -197,6 +197,8 @@
<item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Small</item>
</style>
+ <style name="TextAppearance.PagerTabs" parent="@android:style/TextAppearance.Material.Widget.TabWidget" />
+
<style name="TextAppearance" parent="android:TextAppearance.DeviceDefault"/>
<style name="TextAppearance.info_label">
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index b142383..c7b92c1 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -136,6 +136,10 @@
android:title="@string/root_access"
android:persistent="false" />
+ <Preference
+ android:key="root_appops"
+ android:title="@string/root_appops_title"
+ android:summary="@string/root_appops_summary" />
</PreferenceCategory>
<PreferenceCategory
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index e63edd7..46e7b5f 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -18,6 +18,7 @@
import android.os.Bundle;
+import com.android.settings.applications.appops.AppOpsSummary;
import com.android.settings.enterprise.EnterprisePrivacySettings;
/**
@@ -62,6 +63,15 @@
public static class ManageApplicationsActivity extends SettingsActivity { /* empty */ }
public static class ManageAssistActivity extends SettingsActivity { /* empty */ }
public static class HighPowerApplicationsActivity extends SettingsActivity { /* empty */ }
+ public static class AppOpsSummaryActivity extends SettingsActivity {
+ @Override
+ public boolean isValidFragment(String className) {
+ if (AppOpsSummary.class.getName().equals(className)) {
+ return true;
+ }
+ return super.isValidFragment(className);
+ }
+ }
public static class BackgroundCheckSummaryActivity extends SettingsActivity { /* empty */ }
public static class StorageUseActivity extends SettingsActivity { /* empty */ }
public static class DevelopmentSettingsDashboardActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/applications/appops/AppOpsCategory.java b/src/com/android/settings/applications/appops/AppOpsCategory.java
index 57d7dc4..5482cae 100644
--- a/src/com/android/settings/applications/appops/AppOpsCategory.java
+++ b/src/com/android/settings/applications/appops/AppOpsCategory.java
@@ -16,9 +16,11 @@
package com.android.settings.applications.appops;
+import android.app.AlertDialog;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
@@ -33,6 +35,7 @@
import android.widget.ListView;
import android.widget.Switch;
import android.widget.TextView;
+import android.widget.Toast;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
@@ -288,14 +291,21 @@
}
AppOpEntry item = getItem(position);
+
+ AppOpsManager.OpEntry op = item.getOpEntry(0);
+ final int switchOp = AppOpsManager.opToSwitch(op.getOp());
+ final int mode = mState.getAppOpsManager().checkOpNoThrow(switchOp,
+ item.getAppEntry().getApplicationInfo().uid,
+ item.getAppEntry().getApplicationInfo().packageName);
+ ((TextView) view.findViewById(R.id.op_name)).setText(
+ MODE_STRING_RES[modeToPosition(mode)]);
+
((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(
+ ((TextView) view.findViewById(R.id.op_time)).setText(
item.getTimeText(mResources, false));
- view.findViewById(R.id.op_time).setVisibility(View.GONE);
- ((Switch) view.findViewById(R.id.op_switch)).setChecked(
- item.getPrimaryOpMode() == AppOpsManager.MODE_ALLOWED);
+ view.findViewById(R.id.op_switch).setVisibility(View.GONE);
return view;
}
@@ -331,17 +341,45 @@
@Override public void onListItemClick(ListView l, View v, int position, long id) {
AppOpEntry entry = mAdapter.getItem(position);
if (entry != null) {
- // We treat this as tapping on the check box, toggling the app op state.
- Switch sw = v.findViewById(R.id.op_switch);
- boolean checked = !sw.isChecked();
- sw.setChecked(checked);
AppOpsManager.OpEntry op = entry.getOpEntry(0);
- int mode = checked ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
- mState.getAppOpsManager().setMode(op.getOp(),
+
+ final int switchOp = AppOpsManager.opToSwitch(op.getOp());
+ final int mode = mState.getAppOpsManager().checkOpNoThrow(switchOp,
entry.getAppEntry().getApplicationInfo().uid,
- entry.getAppEntry().getApplicationInfo().packageName,
- mode);
- entry.overridePrimaryOpMode(mode);
+ entry.getAppEntry().getApplicationInfo().packageName);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.app_ops_labels_su);
+
+ String[] listItems = {
+ getActivity().getString(MODE_STRING_RES[MODE_ALLOWED]),
+ getActivity().getString(MODE_STRING_RES[MODE_IGNORED]),
+ getActivity().getString(MODE_STRING_RES[MODE_ASK]),
+ };
+
+ builder.setSingleChoiceItems(listItems, modeToPosition(mode), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int mode = positionToMode(which);
+ mState.getAppOpsManager().setMode(op.getOp(),
+ entry.getAppEntry().getApplicationInfo().uid,
+ entry.getAppEntry().getApplicationInfo().packageName,
+ mode);
+ entry.overridePrimaryOpMode(mode);
+ mAdapter.notifyDataSetChanged();
+ dialog.dismiss();
+ }
+ });
+
+ builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
}
}
@@ -370,4 +408,40 @@
// Clear the data in the adapter.
mAdapter.setData(null);
}
+
+ private static final int MODE_ALLOWED = 0;
+ private static final int MODE_IGNORED = 1;
+ private static final int MODE_ASK = 2;
+
+ private static final int[] MODE_STRING_RES = {
+ R.string.app_ops_permissions_allowed,
+ R.string.app_ops_permissions_ignored,
+ R.string.app_ops_permissions_always_ask,
+ };
+
+ private static int modeToPosition(int mode) {
+ switch (mode) {
+ case AppOpsManager.MODE_ALLOWED:
+ return MODE_ALLOWED;
+ case AppOpsManager.MODE_IGNORED:
+ return MODE_IGNORED;
+ case AppOpsManager.MODE_ASK:
+ return MODE_ASK;
+ default:
+ return MODE_IGNORED;
+ }
+ }
+
+ private static int positionToMode(int position) {
+ switch (position) {
+ case MODE_ALLOWED:
+ return AppOpsManager.MODE_ALLOWED;
+ case MODE_IGNORED:
+ return AppOpsManager.MODE_IGNORED;
+ case MODE_ASK:
+ return AppOpsManager.MODE_ASK;
+ default:
+ return AppOpsManager.MODE_IGNORED;
+ }
+ }
}
diff --git a/src/com/android/settings/applications/appops/AppOpsState.java b/src/com/android/settings/applications/appops/AppOpsState.java
index 3c8d647..001074d 100644
--- a/src/com/android/settings/applications/appops/AppOpsState.java
+++ b/src/com/android/settings/applications/appops/AppOpsState.java
@@ -16,6 +16,7 @@
package com.android.settings.applications.appops;
+import android.app.Activity;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -23,6 +24,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -50,12 +52,15 @@
final CharSequence[] mOpSummaries;
final CharSequence[] mOpLabels;
+ private SharedPreferences mPreferences;
+
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);
+ mPreferences = context.getSharedPreferences("appops_manager", Activity.MODE_PRIVATE);
}
public static class OpsTemplate implements Parcelable {
@@ -211,9 +216,15 @@
new boolean[] { false }
);
+ public static final OpsTemplate SU_TEMPLATE = new OpsTemplate(
+ new int[] { AppOpsManager.OP_SU },
+ new boolean[] { false }
+ );
+
public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] {
LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE,
- MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE
+ MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE,
+ SU_TEMPLATE
};
/**
@@ -501,19 +512,34 @@
}
private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries,
- final String packageName, ApplicationInfo appInfo) {
+ final String packageName, ApplicationInfo appInfo, boolean applyFilters) {
+
+ if (appInfo == null) {
+ try {
+ appInfo = mPm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to find info for package " + packageName);
+ return null;
+ }
+ }
+
+ if (applyFilters) {
+ // Hide user apps if needed
+ if (!shouldShowUserApps() &&
+ (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return null;
+ }
+ // Hide system apps if needed
+ if (!shouldShowSystemApps() &&
+ (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return null;
+ }
+ }
+
AppEntry appEntry = appEntries.get(packageName);
if (appEntry == null) {
- if (appInfo == null) {
- try {
- appInfo = mPm.getApplicationInfo(packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_ANY_USER);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Unable to find info for package " + packageName);
- return null;
- }
- }
appEntry = new AppEntry(this, appInfo);
appEntry.loadLabel(context);
appEntries.put(packageName, appEntry);
@@ -521,6 +547,14 @@
return appEntry;
}
+ private boolean shouldShowUserApps() {
+ return mPreferences.getBoolean("show_user_apps", true);
+ }
+
+ private boolean shouldShowSystemApps() {
+ return mPreferences.getBoolean("show_system_apps", true);
+ }
+
public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) {
return buildState(tpl, uid, packageName, RECENCY_COMPARATOR);
}
@@ -546,6 +580,9 @@
}
}
+ // Whether to apply hide user / system app filters
+ final boolean applyFilters = (packageName == null);
+
List<AppOpsManager.PackageOps> pkgs;
if (packageName != null) {
pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops);
@@ -556,7 +593,8 @@
if (pkgs != null) {
for (int i=0; i<pkgs.size(); i++) {
AppOpsManager.PackageOps pkgOps = pkgs.get(i);
- AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null);
+ AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null,
+ applyFilters);
if (appEntry == null) {
continue;
}
@@ -584,7 +622,7 @@
for (int i=0; i<apps.size(); i++) {
PackageInfo appInfo = apps.get(i);
AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName,
- appInfo.applicationInfo);
+ appInfo.applicationInfo, applyFilters);
if (appEntry == null) {
continue;
}
diff --git a/src/com/android/settings/applications/appops/AppOpsSummary.java b/src/com/android/settings/applications/appops/AppOpsSummary.java
new file mode 100644
index 0000000..ed8a16d
--- /dev/null
+++ b/src/com/android/settings/applications/appops/AppOpsSummary.java
@@ -0,0 +1,244 @@
+/**
+ * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2016 The CyanogenMod 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.settings.applications.appops;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceFrameLayout;
+import android.util.Pair;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.viewpager.widget.PagerTabStrip;
+import androidx.viewpager.widget.ViewPager;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.core.InstrumentedPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.settings.development.RootAccessPreferenceController;
+import com.android.settings.R;
+
+public class AppOpsSummary extends InstrumentedPreferenceFragment {
+ // layout inflater object used to inflate views
+ private LayoutInflater mInflater;
+
+ private ViewGroup mContentContainer;
+ private View mRootView;
+ private ViewPager mViewPager;
+
+ private MyPagerAdapter mAdapter;
+
+ private Activity mActivity;
+ private SharedPreferences mPreferences;
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.APP_OPS_SUMMARY;
+ }
+
+ static class MyPagerAdapter extends FragmentPagerAdapter
+ implements ViewPager.OnPageChangeListener {
+ private List<Pair<CharSequence, AppOpsState.OpsTemplate>> mPageData;
+ private int mCurPos;
+
+ public MyPagerAdapter(FragmentManager fm,
+ List<Pair<CharSequence, AppOpsState.OpsTemplate>> data) {
+ super(fm);
+ mPageData = data;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ return new AppOpsCategory(mPageData.get(position).second);
+ }
+
+ @Override
+ public int getCount() {
+ return mPageData.size();
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mPageData.get(position).first;
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ mCurPos = position;
+ }
+
+ public int getCurrentPage() {
+ return mCurPos;
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ if (state == ViewPager.SCROLL_STATE_IDLE) {
+ //updateCurrentTab(mCurPos);
+ }
+ }
+ }
+
+ private void resetAdapter() {
+ // trigger adapter load, preserving the selected page
+ int curPos = mAdapter.getCurrentPage();
+ mViewPager.setAdapter(mAdapter);
+ mViewPager.setOnPageChangeListener(mAdapter);
+ mViewPager.setCurrentItem(curPos);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // initialize the inflater
+ mInflater = inflater;
+
+ View rootView = mInflater.inflate(R.layout.app_ops_summary,
+ container, false);
+ mContentContainer = container;
+ mRootView = rootView;
+
+ CharSequence[] pageNames = getResources().getTextArray(R.array.app_ops_categories_lineage);
+ // FP2: modified to only show superuser
+ AppOpsState.OpsTemplate[] templates = new AppOpsState.OpsTemplate[] { AppOpsState.SU_TEMPLATE };
+ assert(pageNames.length == templates.length);
+
+ int specificTab = -1;
+ Bundle bundle = getArguments();
+ if (bundle != null) {
+ specificTab = Arrays.asList(pageNames).indexOf(bundle.getString("appops_tab", ""));
+ }
+
+ List<Pair<CharSequence, AppOpsState.OpsTemplate>> pageData = new ArrayList<>();
+ for (int i = 0; i < pageNames.length; i++) {
+ pageData.add(Pair.create(pageNames[i], templates[i]));
+ }
+ filterPageData(pageData, specificTab);
+
+ mViewPager = (ViewPager) rootView.findViewById(R.id.pager);
+ mAdapter = new MyPagerAdapter(getChildFragmentManager(), pageData);
+ mViewPager.setAdapter(mAdapter);
+ mViewPager.setOnPageChangeListener(mAdapter);
+ PagerTabStrip tabs = (PagerTabStrip) rootView.findViewById(R.id.tabs);
+
+ // HACK - https://code.google.com/p/android/issues/detail?id=213359
+ ((ViewPager.LayoutParams) tabs.getLayoutParams()).isDecor = true;
+
+ Resources.Theme theme = tabs.getContext().getTheme();
+ TypedValue typedValue = new TypedValue();
+ theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ final int colorAccent = getContext().getColor(typedValue.resourceId);
+ tabs.setTabIndicatorColor(colorAccent);
+
+ // We have to do this now because PreferenceFrameLayout looks at it
+ // only when the view is added.
+ if (container instanceof PreferenceFrameLayout) {
+ ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true;
+ }
+
+ mActivity = getActivity();
+
+ return rootView;
+ }
+
+ private void filterPageData(List<Pair<CharSequence, AppOpsState.OpsTemplate>> data, int tab) {
+ if (tab >= 0 && tab < data.size()) {
+ Pair<CharSequence, AppOpsState.OpsTemplate> item = data.get(tab);
+ data.clear();
+ data.add(item);
+ } else if (!RootAccessPreferenceController.isRootForAppsEnabled()) {
+ for (Pair<CharSequence, AppOpsState.OpsTemplate> item : data) {
+ if (item.second == AppOpsState.SU_TEMPLATE) {
+ data.remove(item);
+ return;
+ }
+ }
+ }
+ }
+
+ private boolean shouldShowUserApps() {
+ return mPreferences.getBoolean("show_user_apps", true);
+ }
+
+ private boolean shouldShowSystemApps() {
+ return mPreferences.getBoolean("show_system_apps", true);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // get shared preferences
+ mPreferences = mActivity.getSharedPreferences("appops_manager", Activity.MODE_PRIVATE);
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.appops_manager, menu);
+ menu.findItem(R.id.show_user_apps).setChecked(shouldShowUserApps());
+ menu.findItem(R.id.show_system_apps).setChecked(shouldShowSystemApps());
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.show_user_apps:
+ final String prefNameUserApps = "show_user_apps";
+ // set the menu checkbox and save it in shared preference
+ item.setChecked(!item.isChecked());
+ mPreferences.edit().putBoolean(prefNameUserApps, item.isChecked()).commit();
+ // reload content
+ resetAdapter();
+ return true;
+ case R.id.show_system_apps:
+ final String prefNameSysApps = "show_system_apps";
+ // set the menu checkbox and save it in shared preference
+ item.setChecked(!item.isChecked());
+ mPreferences.edit().putBoolean(prefNameSysApps, item.isChecked()).commit();
+ // reload view content
+ resetAdapter();
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 9915680..b5eeb87 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -499,6 +499,7 @@
controllers.add(new BubbleGlobalPreferenceController(context));
controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
controllers.add(new RootAccessPreferenceController(context, fragment));
+ controllers.add(new RootAppOpsPreferenceController(context));
controllers.add(new DefaultLaunchPreferenceController(context, "running_apps"));
controllers.add(new DefaultLaunchPreferenceController(context, "demo_mode"));
controllers.add(new DefaultLaunchPreferenceController(context, "quick_settings_tiles"));
diff --git a/src/com/android/settings/development/RootAppOpsPreferenceController.java b/src/com/android/settings/development/RootAppOpsPreferenceController.java
new file mode 100644
index 0000000..89c6dc2
--- /dev/null
+++ b/src/com/android/settings/development/RootAppOpsPreferenceController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The LineageOS 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.settings.development;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.Settings.AppOpsSummaryActivity;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
+
+public class RootAppOpsPreferenceController extends DeveloperOptionsPreferenceController
+ implements Preference.OnPreferenceClickListener, PreferenceControllerMixin {
+
+ private static final String TAG = "RootAppOpsPreferenceController";
+ private static final String PREF_KEY = "root_appops";
+
+ public RootAppOpsPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ // User builds don't get root, and eng always gets root
+ return Build.IS_DEBUGGABLE || "eng".equals(Build.TYPE);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference.setOnPreferenceClickListener(this);
+
+ if (!RootAccessPreferenceController.isRootForAppsEnabled() || !isAdminUser()) {
+ mPreference.setEnabled(false);
+ }
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ if (isAdminUser()) {
+ mPreference.setEnabled(true);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ Bundle args = new Bundle();
+ args.putString("appops_tab",
+ mContext.getResources().getString(R.string.app_ops_categories_su));
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
+ intent.setClass(mContext, AppOpsSummaryActivity.class);
+ mContext.startActivity(intent);
+ return true;
+ }
+
+ @VisibleForTesting
+ boolean isAdminUser() {
+ return ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).isAdminUser();
+ }
+}