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;