diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ed511efefcb..41db2eb7a10 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2905,6 +2905,17 @@
android:value="com.android.settings.applications.ManageApplications" />
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 54ed1dfd762..baa9547777f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7224,6 +7224,12 @@
No
+
+ Install other apps
+
+ install apps external unknown sources
+
+ Allows to install other apps
@@ -7233,6 +7239,8 @@
%1$d of %2$d apps allowed to modify system settings
+
+ Can install other apps
Can modify system settings
@@ -7249,6 +7257,12 @@
Yes
No
+
+ Yes
+
+ No
+
+ Trust apps from this source
Double twist for camera
diff --git a/res/xml/external_sources_details.xml b/res/xml/external_sources_details.xml
new file mode 100644
index 00000000000..fb443f49977
--- /dev/null
+++ b/res/xml/external_sources_details.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index 4de167a680e..f8a5bf4bb75 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -92,4 +92,15 @@
android:name="classname"
android:value="com.android.settings.Settings$UsageAccessSettingsActivity" />
+
+
+
+
+
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 97e53e5f082..a3934369024 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -138,6 +138,9 @@ public class Settings extends SettingsActivity {
public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ }
public static class AdvancedAppsActivity extends SettingsActivity { /* empty */ }
+ public static class ManageExternalSourcesActivity extends SettingsActivity {
+ /* empty */ }
+
public static class WifiCallingSuggestionActivity extends SettingsActivity { /* empty */ }
public static class ZenModeAutomationSuggestionActivity extends SettingsActivity { /* empty */ }
public static class FingerprintSuggestionActivity extends FingerprintSettings { /* empty */ }
diff --git a/src/com/android/settings/applications/AppStateInstallAppsBridge.java b/src/com/android/settings/applications/AppStateInstallAppsBridge.java
new file mode 100644
index 00000000000..6d22e2572cd
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateInstallAppsBridge.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 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.settings.applications;
+
+import android.Manifest;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.settings.R;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.List;
+
+/**
+ * Connects app op info to the ApplicationsState. Wraps around the generic AppStateBaseBridge
+ * class to tailor to the semantics of {@link AppOpsManager#OP_REQUEST_INSTALL_PACKAGES}
+ * Also provides app filters that can use the info.
+ */
+public class AppStateInstallAppsBridge extends AppStateBaseBridge {
+
+ private static final String TAG = AppStateInstallAppsBridge.class.getSimpleName();
+
+ private final IPackageManager mIpm;
+ private final AppOpsManager mAppOpsManager;
+ private final Context mContext;
+
+ public AppStateInstallAppsBridge(Context context, ApplicationsState appState,
+ Callback callback) {
+ super(appState, callback);
+ mContext = context;
+ mIpm = AppGlobals.getPackageManager();
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String packageName, int uid) {
+ app.extraInfo = createInstallAppsStateFor(packageName, uid);
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ // TODO: consider making this a batch operation with a single binder call
+ final List allApps = mAppSession.getAllApps();
+ for (int i = 0; i < allApps.size(); i++) {
+ AppEntry currentEntry = allApps.get(i);
+ updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid);
+ }
+ }
+
+ private boolean hasRequestedAppOpPermission(String permission, String packageName) {
+ try {
+ String[] packages = mIpm.getAppOpPermissionPackages(permission);
+ return ArrayUtils.contains(packages, packageName);
+ } catch (RemoteException exc) {
+ Log.e(TAG, "PackageManager dead. Cannot get permission info");
+ return false;
+ }
+ }
+
+ private boolean hasPermission(String permission, int uid) {
+ try {
+ int result = mIpm.checkUidPermission(permission, uid);
+ return result == PackageManager.PERMISSION_GRANTED;
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManager dead. Cannot get permission info");
+ return false;
+ }
+ }
+
+ private int getAppOpMode(int appOpCode, int uid, String packageName) {
+ return mAppOpsManager.checkOpNoThrow(appOpCode, uid, packageName);
+ }
+
+ InstallAppsState createInstallAppsStateFor(String packageName, int uid) {
+ final InstallAppsState appState = new InstallAppsState();
+ appState.permissionRequested = hasRequestedAppOpPermission(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES, packageName);
+ appState.permissionGranted = hasPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES,
+ uid);
+ appState.appOpMode = getAppOpMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid,
+ packageName);
+ return appState;
+ }
+
+ /**
+ * Collection of information to be used as {@link AppEntry#extraInfo} objects
+ */
+ public static class InstallAppsState {
+ boolean permissionRequested;
+ boolean permissionGranted;
+ int appOpMode;
+
+ public InstallAppsState() {
+ this.appOpMode = AppOpsManager.MODE_DEFAULT;
+ }
+
+ public boolean canInstallApps() {
+ if (appOpMode == AppOpsManager.MODE_DEFAULT) {
+ return permissionGranted;
+ } else {
+ return appOpMode == AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ public int getSummary() {
+ return canInstallApps() ? R.string.external_source_trusted
+ : R.string.external_source_untrusted;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("[permissionGranted: " + permissionGranted);
+ sb.append(", permissionRequested: " + permissionRequested);
+ sb.append(", appOpMode: " + appOpMode);
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ static final AppFilter FILTER_APP_SOURCES = new AppFilter() {
+
+ @Override
+ public void init() {
+ }
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ if (info.extraInfo == null || !(info.extraInfo instanceof InstallAppsState)) {
+ return false;
+ }
+ InstallAppsState state = (InstallAppsState) info.extraInfo;
+ return (state.appOpMode != AppOpsManager.MODE_DEFAULT) || state.permissionRequested;
+ }
+ };
+}
diff --git a/src/com/android/settings/applications/ExternalSourcesDetails.java b/src/com/android/settings/applications/ExternalSourcesDetails.java
new file mode 100644
index 00000000000..fd8221cb4e8
--- /dev/null
+++ b/src/com/android/settings/applications/ExternalSourcesDetails.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.settings.applications;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.Preference.OnPreferenceChangeListener;
+
+import com.android.settings.R;
+import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState;
+
+public class ExternalSourcesDetails extends AppInfoWithHeader
+ implements OnPreferenceChangeListener {
+
+ private static final String KEY_EXTERNAL_SOURCES_SETTINGS_SWITCH =
+ "external_sources_settings_switch";
+ private static final String KEY_EXTERNAL_SOURCES_SETTINGS_DESC =
+ "external_sources_settings_description";
+
+ private AppStateInstallAppsBridge mAppBridge;
+ private AppOpsManager mAppOpsManager;
+ private SwitchPreference mSwitchPref;
+ private Preference mExternalSourcesSettingsDesc;
+ private InstallAppsState mInstallAppsState;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Context context = getActivity();
+ mAppBridge = new AppStateInstallAppsBridge(context, mState, null);
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+ addPreferencesFromResource(R.xml.external_sources_details);
+ mSwitchPref = (SwitchPreference) findPreference(KEY_EXTERNAL_SOURCES_SETTINGS_SWITCH);
+ mExternalSourcesSettingsDesc = findPreference(KEY_EXTERNAL_SOURCES_SETTINGS_DESC);
+
+ getPreferenceScreen().setTitle(R.string.install_other_apps);
+ mSwitchPref.setTitle(R.string.external_source_switch_title);
+ mExternalSourcesSettingsDesc.setSummary(R.string.install_all_warning);
+
+ mSwitchPref.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean checked = (Boolean) newValue;
+ if (preference == mSwitchPref) {
+ if (mInstallAppsState != null && checked != mInstallAppsState.canInstallApps()) {
+ setCanInstallApps(checked);
+ refreshUi();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void setCanInstallApps(boolean newState) {
+ mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES,
+ mPackageInfo.applicationInfo.uid, mPackageName,
+ newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ }
+
+ @Override
+ protected boolean refreshUi() {
+ mInstallAppsState = mAppBridge.createInstallAppsStateFor(mPackageName,
+ mPackageInfo.applicationInfo.uid);
+
+ final boolean canWrite = mInstallAppsState.canInstallApps();
+ mSwitchPref.setChecked(canWrite);
+ return true;
+ }
+
+ @Override
+ protected AlertDialog createDialog(int id, int errorCode) {
+ return null;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.MANAGE_EXTERNAL_SOURCES;
+ }
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 43bb02a4fdd..a87ba531407 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -58,6 +58,7 @@ import com.android.settings.AppHeader;
import com.android.settings.R;
import com.android.settings.Settings.AllApplicationsActivity;
import com.android.settings.Settings.HighPowerApplicationsActivity;
+import com.android.settings.Settings.ManageExternalSourcesActivity;
import com.android.settings.Settings.NotificationAppListActivity;
import com.android.settings.Settings.OverlaySettingsActivity;
import com.android.settings.Settings.StorageUseActivity;
@@ -66,6 +67,7 @@ import com.android.settings.Settings.WriteSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
+import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState;
import com.android.settings.applications.AppStateUsageBridge.UsageState;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.dashboard.SummaryLoader;
@@ -136,6 +138,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
public static final int FILTER_APPS_USAGE_ACCESS = 8;
public static final int FILTER_APPS_WITH_OVERLAY = 9;
public static final int FILTER_APPS_WRITE_SETTINGS = 10;
+ public static final int FILTER_APPS_INSTALL_SOURCES = 12;
// This is the string labels for the filter modes above, the order must be kept in sync.
public static final int[] FILTER_LABELS = new int[]{
@@ -151,6 +154,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
R.string.filter_all_apps, // Usage access screen, never displayed
R.string.filter_overlay_apps, // Apps with overlay permission
R.string.filter_write_settings_apps, // Apps that can write system settings
+ R.string.filter_install_sources_apps, // Apps that are trusted sources of apks
};
// This is the actual mapping to filters from FILTER_ constants above, the order must
// be kept in sync.
@@ -169,6 +173,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
AppStateOverlayBridge.FILTER_SYSTEM_ALERT_WINDOW, // Apps that can draw overlays
AppStateWriteSettingsBridge.FILTER_WRITE_SETTINGS, // Apps that can write system settings
+ AppStateInstallAppsBridge.FILTER_APP_SOURCES,
};
// sort order
@@ -210,6 +215,7 @@ public class ManageApplications extends InstrumentedPreferenceFragment
public static final int LIST_TYPE_HIGH_POWER = 5;
public static final int LIST_TYPE_OVERLAY = 6;
public static final int LIST_TYPE_WRITE_SETTINGS = 7;
+ public static final int LIST_TYPE_MANAGE_SOURCES = 8;
private View mRootView;
@@ -259,6 +265,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mListType = LIST_TYPE_OVERLAY;
} else if (className.equals(WriteSettingsActivity.class.getName())) {
mListType = LIST_TYPE_WRITE_SETTINGS;
+ } else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
+ mListType = LIST_TYPE_MANAGE_SOURCES;
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -379,6 +387,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
return FILTER_APPS_WITH_OVERLAY;
case LIST_TYPE_WRITE_SETTINGS:
return FILTER_APPS_WRITE_SETTINGS;
+ case LIST_TYPE_MANAGE_SOURCES:
+ return FILTER_APPS_INSTALL_SOURCES;
default:
return FILTER_APPS_ALL;
}
@@ -412,6 +422,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS;
case LIST_TYPE_WRITE_SETTINGS:
return MetricsEvent.SYSTEM_ALERT_WINDOW_APPS;
+ case LIST_TYPE_MANAGE_SOURCES:
+ return MetricsEvent.MANAGE_EXTERNAL_SOURCES;
default:
return MetricsEvent.VIEW_UNKNOWN;
}
@@ -503,6 +515,9 @@ public class ManageApplications extends InstrumentedPreferenceFragment
case LIST_TYPE_WRITE_SETTINGS:
startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
break;
+ case LIST_TYPE_MANAGE_SOURCES:
+ startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps);
+ break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed app.
// Maybe when they load the list of apps that contains managed profile apps.
@@ -796,6 +811,8 @@ public class ManageApplications extends InstrumentedPreferenceFragment
mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
+ } else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) {
+ mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
@@ -1206,6 +1223,11 @@ public class ManageApplications extends InstrumentedPreferenceFragment
holder.entry));
break;
+ case LIST_TYPE_MANAGE_SOURCES:
+ holder.summary
+ .setText(((InstallAppsState) holder.entry.extraInfo).getSummary());
+ break;
+
default:
holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
break;