Merge "Respect DISALLOW_UNIFIED_PASSWORD in Settings."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6ccb8d1..0418d51 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3312,13 +3312,13 @@
</intent-filter>
</activity>
- <provider android:name=".SettingsSliceProvider"
+ <provider android:name=".slices.SettingsSliceProvider"
android:authorities="com.android.settings.slices"
android:exported="true">
</provider>
<receiver
- android:name=".SliceBroadcastReceiver" >
+ android:name=".slices.SliceBroadcastReceiver" >
<intent-filter>
<action android:name="com.android.settings.slice.action.WIFI_CHANGED"/>
</intent-filter>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 2ba7919..e3fa070 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -97,6 +97,7 @@
<!-- For Search -->
<declare-styleable name="Preference">
<attr name="keywords" format="string" />
+ <attr name="controller" format="string" />
</declare-styleable>
<!-- For DotsPageIndicator -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7331d72..694a1b0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -393,6 +393,8 @@
<string name="bluetooth_paired_device_title">Your devices</string>
<!-- Title for pairing bluetooth device page [CHAR LIMIT=none] -->
<string name="bluetooth_pairing_page_title">Pair new device</string>
+ <!-- Summary for bluetooth item in connection detail page -->
+ <string name="bluetooth_pref_summary">Allow device to pair and connect to bluetooth devices</string>
<!-- Title for connected device group [CHAR LIMIT=none]-->
<string name="connected_device_connected_title">Currently connected</string>
@@ -6726,14 +6728,14 @@
<!-- Do not disturb: Subtitle for DND behavior indicating no sound will get past DND. [CHAR LIMIT=30] -->
<string name="zen_mode_behavior_no_sound">No sound</string>
- <!-- Do not disturb: Subtitle for DND behavior indicating no sound will get past DND due to user and/or API-invoked Total Silence mode. [CHAR LIMIT=40] -->
- <string name="zen_mode_behavior_total_silence">No sound (Total Silence)</string>
+ <!-- Do not disturb: Subtitle for DND behavior indicating no sound will get past DND. [CHAR LIMIT=40] -->
+ <string name="zen_mode_behavior_total_silence">Total Silence</string>
<!-- Do not disturb: Used before specifying which sounds can bypass DND (ie: No sound except alarms and reminders). [CHAR LIMIT=40] -->
<string name="zen_mode_behavior_no_sound_except">No sound except <xliff:g id="categories" example="alarms, media and system feedback">%1$s</xliff:g></string>
- <!-- Do not disturb: Specifies sounds that can bypass DND in user and/or API-invoked Alarms Only mode. [CHAR LIMIT=100] -->
- <string name="zen_mode_behavior_alarms_only">No sound except alarms, media and system feedback (Alarms only)</string>
+ <!-- Do not disturb: Specifies alarms and media can bypass DND. [CHAR LIMIT=100] -->
+ <string name="zen_mode_behavior_alarms_only">No sound except alarms and media</string>
<!-- Do not disturb: Title for the zen mode automation option in Settings. [CHAR LIMIT=40] -->
<string name="zen_mode_automation_settings_title">Turn on automatically</string>
@@ -7313,8 +7315,11 @@
<!-- [CHAR LIMIT=50] Zen mode settings: Alarms option -->
<string name="zen_mode_alarms">Alarms</string>
- <!-- [CHAR LIMIT=50] Zen mode settings: Media and system sounds option -->
- <string name="zen_mode_media_system_other">Media and system feedback</string>
+ <!-- [CHAR LIMIT=50] Zen mode settings: Media option -->
+ <string name="zen_mode_media_system_other">Media</string>
+
+ <!-- [CHAR LIMIT=120] Zen mode settings: Media secondary text explaining sounds include system feedback such as system tapping sounds, haptic feedback, etc. -->
+ <string name="zen_mode_media_system_other_secondary_text">Includes system feedback like touch and charging sounds</string>
<!-- [CHAR LIMIT=50] Zen mode settings: Reminders option -->
<string name="zen_mode_reminders">Reminders</string>
@@ -7334,6 +7339,9 @@
<!-- [CHAR LIMIT=200] Zen mode settings: Repeat callers option summary -->
<string name="zen_mode_repeat_callers_summary">If the same person calls a second time within a <xliff:g id="minutes">%d</xliff:g> minute period</string>
+ <!-- [CHAR LIMIT=50] Zen mode settings dnd beahvior description: when a user has chosen custom sounds to bypass DND -->
+ <string name="zen_mode_behavior_summary_custom">Custom</string>
+
<!-- [CHAR LIMIT=20] Zen mode settings: When option -->
<string name="zen_mode_when">Automatically turn on</string>
@@ -9124,4 +9132,9 @@
<!-- Note displayed when certain features are not available on low ram devices. [CHAR LIMIT=NONE] -->
<string name="disabled_low_ram_device">This feature is not available on this device</string>
+ <!-- UI debug setting: enable gnss raw meas full tracking [CHAR LIMIT=25] -->
+ <string name="enable_gnss_raw_meas_full_tracking">Force Full GnssMeasurement</string>
+ <!-- UI debug setting: enable gpu debug layers summary [CHAR LIMIT=50] -->
+ <string name="enable_gnss_raw_meas_full_tracking_summary">Disable GNSS duty-cycling, track all constellations and frequencies.</string>
+
</resources>
diff --git a/res/xml/app_info_settings.xml b/res/xml/app_info_settings.xml
index 664210b..99c76b8 100644
--- a/res/xml/app_info_settings.xml
+++ b/res/xml/app_info_settings.xml
@@ -132,6 +132,17 @@
</PreferenceCategory>
+ <!-- App installer info -->
+ <PreferenceCategory
+ android:key="app_installer"
+ android:title="@string/app_install_details_group_title">
+
+ <Preference
+ android:key="app_info_store"
+ android:title="@string/app_install_details_title" />
+
+ </PreferenceCategory>
+
<Preference
android:key="app_version"
android:selectable="false"
diff --git a/res/xml/bluetooth_pairing_detail.xml b/res/xml/bluetooth_pairing_detail.xml
index e60da75..e654a3c 100644
--- a/res/xml/bluetooth_pairing_detail.xml
+++ b/res/xml/bluetooth_pairing_detail.xml
@@ -19,7 +19,9 @@
android:title="@string/bluetooth_pairing_pref_title">
<Preference
- android:key="device_name"/>
+ android:key="bt_pair_rename_devices"
+ android:title="@string/bluetooth_device_name"
+ android:summary="@string/summary_placeholder" />
<com.android.settings.bluetooth.BluetoothProgressCategory
android:key="available_devices"
diff --git a/res/xml/connected_devices_advanced.xml b/res/xml/connected_devices_advanced.xml
index 946151f..57a2580 100644
--- a/res/xml/connected_devices_advanced.xml
+++ b/res/xml/connected_devices_advanced.xml
@@ -19,10 +19,11 @@
android:key="connected_devices_screen"
android:title="@string/connected_devices_dashboard_title">
- <com.android.settings.widget.MasterSwitchPreference
- android:key="toggle_bluetooth"
+ <SwitchPreference
+ android:key="toggle_bluetooth_switch"
android:title="@string/bluetooth_settings_title"
android:icon="@drawable/ic_settings_bluetooth"
+ android:summary="@string/bluetooth_pref_summary"
android:order="-7"/>
<SwitchPreference
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index d1eb366..60efcab 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -133,6 +133,11 @@
android:title="@string/mock_location_app" />
<SwitchPreference
+ android:key="enable_gnss_raw_meas_full_tracking"
+ android:title="@string/enable_gnss_raw_meas_full_tracking"
+ android:summary="@string/enable_gnss_raw_meas_full_tracking_summary"/>
+
+ <SwitchPreference
android:key="debug_view_attributes"
android:title="@string/debug_view_attributes" />
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index f9f5d2b..333fd2a 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -25,8 +25,9 @@
<Preference
android:key="brightness"
android:title="@string/brightness"
- settings:keywords="@string/keywords_display_brightness_level">
- <intent android:action="com.android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
+ settings:keywords="@string/keywords_display_brightness_level"
+ settings:controller="com.android.settings.display.AutoBrightnessPreferenceController">
+ <intent android:action="com.android.intent.action.SHOW_BRIGHTNESS_DIALOG" />
</Preference>
<com.android.settings.display.NightDisplayPreference
diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml
index 8b9d0ca..1dfa6fb 100644
--- a/res/xml/system_dashboard_fragment.xml
+++ b/res/xml/system_dashboard_fragment.xml
@@ -26,7 +26,8 @@
android:title="@string/gesture_preference_title"
android:icon="@drawable/ic_settings_gestures"
android:order="-250"
- android:fragment="com.android.settings.gestures.GestureSettings" />
+ android:fragment="com.android.settings.gestures.GestureSettings"
+ settings:controller="com.android.settings.gestures.GesturesSettingPreferenceController"/>
<!-- Backup -->
<Preference
@@ -34,7 +35,8 @@
android:title="@string/privacy_settings_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_settings_backup"
- android:order="-60">
+ android:order="-60"
+ settings:controller="com.android.settings.backup.BackupSettingsActivityPreferenceController">
<intent android:action="android.settings.BACKUP_AND_RESET_SETTINGS" />
</Preference>
@@ -44,14 +46,16 @@
android:title="@string/system_update_settings_list_item_title"
android:summary="@string/summary_placeholder"
android:icon="@drawable/ic_system_update"
- android:order="-30">
+ android:order="-30"
+ settings:controller="com.android.settings.deviceinfo.SystemUpdatePreferenceController">
<intent android:action="android.settings.SYSTEM_UPDATE_SETTINGS" />
</Preference>
<Preference
android:key="additional_system_update_settings"
android:title="@string/additional_system_update_settings_list_item_title"
- android:order="-31">
+ android:order="-31"
+ settings:controller="com.android.settings.deviceinfo.AdditionalSystemUpdatePreferenceController">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="@string/additional_system_update"
android:targetClass="@string/additional_system_update_menu" />
diff --git a/res/xml/zen_mode_behavior_settings.xml b/res/xml/zen_mode_behavior_settings.xml
index c0849da..6aeebe6 100644
--- a/res/xml/zen_mode_behavior_settings.xml
+++ b/res/xml/zen_mode_behavior_settings.xml
@@ -34,7 +34,8 @@
<!-- Media -->
<SwitchPreference
android:key="zen_mode_media"
- android:title="@string/zen_mode_media_system_other"/>
+ android:title="@string/zen_mode_media_system_other"
+ android:summary="@string/zen_mode_media_system_other_secondary_text"/>
<!-- Reminders -->
<SwitchPreference
diff --git a/src/com/android/settings/DeviceAdminAdd.java b/src/com/android/settings/DeviceAdminAdd.java
index 2fd769b..0ad882d 100644
--- a/src/com/android/settings/DeviceAdminAdd.java
+++ b/src/com/android/settings/DeviceAdminAdd.java
@@ -69,6 +69,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
public class DeviceAdminAdd extends Activity {
static final String TAG = "DeviceAdminAdd";
@@ -145,18 +146,14 @@
DevicePolicyManager.EXTRA_DEVICE_ADMIN);
if (who == null) {
String packageName = getIntent().getStringExtra(EXTRA_DEVICE_ADMIN_PACKAGE_NAME);
- for (ComponentName component : mDPM.getActiveAdmins()) {
- if (component.getPackageName().equals(packageName)) {
- who = component;
- mUninstalling = true;
- break;
- }
- }
- if (who == null) {
+ Optional<ComponentName> installedAdmin = findAdminWithPackageName(packageName);
+ if (!installedAdmin.isPresent()) {
Log.w(TAG, "No component specified in " + action);
finish();
return;
}
+ who = installedAdmin.get();
+ mUninstalling = true;
}
if (action != null && action.equals(DevicePolicyManager.ACTION_SET_PROFILE_OWNER)) {
@@ -692,6 +689,18 @@
return info != null ? info.isManagedProfile() : false;
}
+ /**
+ * @return an {@link Optional} containing the admin with a given package name, if it exists,
+ * or {@link Optional#empty()} otherwise.
+ */
+ private Optional<ComponentName> findAdminWithPackageName(String packageName) {
+ List<ComponentName> admins = mDPM.getActiveAdmins();
+ if (admins == null) {
+ return Optional.empty();
+ }
+ return admins.stream().filter(i -> i.getPackageName().equals(packageName)).findAny();
+ }
+
private boolean isAdminUninstallable() {
// System apps can't be uninstalled.
return !mDeviceAdmin.getActivityInfo().applicationInfo.isSystemApp();
diff --git a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
index 544999a..f5f3017 100644
--- a/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
+++ b/src/com/android/settings/PreviewSeekBarPreferenceFragment.java
@@ -58,6 +58,7 @@
private DotsPageIndicator mPageIndicator;
private TextView mLabel;
+ private LabeledSeekBar mSeekBar;
private View mLarger;
private View mSmaller;
@@ -110,19 +111,17 @@
// seek bar.
final int max = Math.max(1, mEntries.length - 1);
- final LabeledSeekBar seekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar);
- seekBar.setLabels(mEntries);
- seekBar.setMax(max);
- seekBar.setProgress(mInitialIndex);
- seekBar.setOnSeekBarChangeListener(new onPreviewSeekBarChangeListener());
+ mSeekBar = (LabeledSeekBar) content.findViewById(R.id.seek_bar);
+ mSeekBar.setLabels(mEntries);
+ mSeekBar.setMax(max);
mSmaller = content.findViewById(R.id.smaller);
mSmaller.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- final int progress = seekBar.getProgress();
+ final int progress = mSeekBar.getProgress();
if (progress > 0) {
- seekBar.setProgress(progress - 1, true);
+ mSeekBar.setProgress(progress - 1, true);
}
}
});
@@ -131,9 +130,9 @@
mLarger.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- final int progress = seekBar.getProgress();
- if (progress < seekBar.getMax()) {
- seekBar.setProgress(progress + 1, true);
+ final int progress = mSeekBar.getProgress();
+ if (progress < mSeekBar.getMax()) {
+ mSeekBar.setProgress(progress + 1, true);
}
}
});
@@ -141,7 +140,7 @@
if (mEntries.length == 1) {
// The larger and smaller buttons will be disabled when we call
// setPreviewLayer() later in this method.
- seekBar.setEnabled(false);
+ mSeekBar.setEnabled(false);
}
final Context context = getContext();
@@ -172,6 +171,21 @@
return root;
}
+ @Override
+ public void onStart() {
+ super.onStart();
+ // Set SeekBar listener here to avoid onProgressChanged() is called
+ // during onRestoreInstanceState().
+ mSeekBar.setProgress(mCurrentIndex);
+ mSeekBar.setOnSeekBarChangeListener(new onPreviewSeekBarChangeListener());
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mSeekBar.setOnSeekBarChangeListener(null);
+ }
+
/**
* Creates new configuration based on the current position of the SeekBar.
*/
diff --git a/src/com/android/settings/applications/AppInfoDashboardFragment.java b/src/com/android/settings/applications/AppInfoDashboardFragment.java
deleted file mode 100755
index d7d91f3..0000000
--- a/src/com/android/settings/applications/AppInfoDashboardFragment.java
+++ /dev/null
@@ -1,1166 +0,0 @@
-/*
- * 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 static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.support.annotation.VisibleForTesting;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceCategory;
-import android.support.v7.preference.PreferenceScreen;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.webkit.IWebViewUpdateService;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.DeviceAdminAdd;
-import com.android.settings.R;
-import com.android.settings.SettingsActivity;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
-import com.android.settings.applications.appinfo.AppBatteryPreferenceController;
-import com.android.settings.applications.appinfo.AppDataUsagePreferenceController;
-import com.android.settings.applications.appinfo.AppMemoryPreferenceController;
-import com.android.settings.applications.appinfo.AppNotificationPreferenceController;
-import com.android.settings.applications.appinfo.AppOpenByDefaultPreferenceController;
-import com.android.settings.applications.appinfo.AppPermissionPreferenceController;
-import com.android.settings.applications.appinfo.AppStoragePreferenceController;
-import com.android.settings.applications.appinfo.AppVersionPreferenceController;
-import com.android.settings.applications.appinfo.DefaultBrowserShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultEmergencyShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultHomeShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultPhoneShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DefaultSmsShortcutPreferenceController;
-import com.android.settings.applications.appinfo.DrawOverlayDetailPreferenceController;
-import com.android.settings.applications.appinfo.ExternalSourceDetailPreferenceController;
-import com.android.settings.applications.appinfo.PictureInPictureDetailPreferenceController;
-import com.android.settings.applications.appinfo.WriteSystemSettingsPreferenceController;
-import com.android.settings.applications.instantapps.InstantAppButtonsController;
-import com.android.settings.applications.manageapplications.ManageApplications;
-import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.widget.ActionButtonPreference;
-import com.android.settings.widget.EntityHeaderController;
-import com.android.settings.widget.PreferenceCategoryController;
-import com.android.settings.wrapper.DevicePolicyManagerWrapper;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.applications.AppUtils;
-import com.android.settingslib.applications.ApplicationsState;
-import com.android.settingslib.applications.ApplicationsState.AppEntry;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Dashboard fragment to display application information from Settings. This activity presents
- * extended information associated with a package like code, data, total size, permissions
- * used by the application and also the set of default launchable activities.
- * For system applications, an option to clear user data is displayed only if data size is > 0.
- * System applications that do not want clear user data do not have this option.
- * For non-system applications, there is no option to clear data. Instead there is an option to
- * uninstall the application.
- */
-public class AppInfoDashboardFragment extends DashboardFragment
- implements ApplicationsState.Callbacks {
-
- private static final String TAG = "AppInfoDashboard";
-
- // Menu identifiers
- public static final int UNINSTALL_ALL_USERS_MENU = 1;
- public static final int UNINSTALL_UPDATES = 2;
-
- // Result code identifiers
- public static final int REQUEST_UNINSTALL = 0;
- private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
-
- public static final int SUB_INFO_FRAGMENT = 1;
-
- public static final int LOADER_CHART_DATA = 2;
- public static final int LOADER_STORAGE = 3;
- @VisibleForTesting
- public static final int LOADER_BATTERY = 4;
-
- // Dialog identifiers used in showDialog
- private static final int DLG_BASE = 0;
- private static final int DLG_FORCE_STOP = DLG_BASE + 1;
- private static final int DLG_DISABLE = DLG_BASE + 2;
- private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
- private static final String KEY_HEADER = "header_view";
- private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons";
- private static final String KEY_ACTION_BUTTONS = "action_buttons";
- private static final String KEY_INSTANT_APP_SUPPORTED_LINKS =
- "instant_app_launch_supported_domain_urls";
-
- public static final String ARG_PACKAGE_NAME = "package";
- public static final String ARG_PACKAGE_UID = "uid";
-
- protected static final boolean localLOGV = false;
- private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info";
-
- private EnforcedAdmin mAppsControlDisallowedAdmin;
- private boolean mAppsControlDisallowedBySystem;
-
- private ApplicationFeatureProvider mApplicationFeatureProvider;
- private ApplicationsState mState;
- private ApplicationsState.Session mSession;
- private ApplicationsState.AppEntry mAppEntry;
- private PackageInfo mPackageInfo;
- private int mUserId;
- private String mPackageName;
-
- private DevicePolicyManagerWrapper mDpm;
- private UserManager mUserManager;
- private PackageManager mPm;
-
- private boolean mFinishing;
- private boolean mListeningToPackageRemove;
-
-
- private final HashSet<String> mHomePackages = new HashSet<>();
-
- private boolean mInitialized;
- private boolean mShowUninstalled;
- private LayoutPreference mHeader;
- private boolean mUpdatedSysApp = false;
- private AppDomainsPreference mInstantAppDomainsPreference;
- private boolean mDisableAfterUninstall;
-
- private List<Callback> mCallbacks = new ArrayList<>();
-
- @VisibleForTesting
- ActionButtonPreference mActionButtons;
-
- private InstantAppButtonsController mInstantAppButtonsController;
-
- /**
- * Callback to invoke when app info has been changed.
- */
- public interface Callback {
- void refreshUi();
- }
-
- @VisibleForTesting
- boolean handleDisableable() {
- boolean disableable = false;
- // Try to prevent the user from bricking their phone
- // by not allowing disabling of apps signed with the
- // system cert and any launcher app in the system.
- if (mHomePackages.contains(mAppEntry.info.packageName)
- || Utils.isSystemPackage(getContext().getResources(), mPm, mPackageInfo)) {
- // Disable button for core system applications.
- mActionButtons
- .setButton1Text(R.string.disable_text)
- .setButton1Positive(false);
- } else if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
- mActionButtons
- .setButton1Text(R.string.disable_text)
- .setButton1Positive(false);
- disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
- .contains(mAppEntry.info.packageName);
- } else {
- mActionButtons
- .setButton1Text(R.string.enable_text)
- .setButton1Positive(true);
- disableable = true;
- }
-
- return disableable;
- }
-
- private boolean isDisabledUntilUsed() {
- return mAppEntry.info.enabledSetting
- == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
- }
-
- private void initUninstallButtons() {
- final boolean isBundled = (mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- boolean enabled;
- if (isBundled) {
- enabled = handleDisableable();
- } else {
- enabled = initUninstallButtonForUserApp();
- }
- // If this is a device admin, it can't be uninstalled or disabled.
- // We do this here so the text of the button is still set correctly.
- if (isBundled && mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
- enabled = false;
- }
-
- // We don't allow uninstalling DO/PO on *any* users, because if it's a system app,
- // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
- // will clear data on all users.
- if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mPackageInfo.packageName)) {
- enabled = false;
- }
-
- // Don't allow uninstalling the device provisioning package.
- if (Utils.isDeviceProvisioningPackage(getResources(), mAppEntry.info.packageName)) {
- enabled = false;
- }
-
- // If the uninstall intent is already queued, disable the uninstall button
- if (mDpm.isUninstallInQueue(mPackageName)) {
- enabled = false;
- }
-
- // Home apps need special handling. Bundled ones we don't risk downgrading
- // because that can interfere with home-key resolution. Furthermore, we
- // can't allow uninstallation of the only home app, and we don't want to
- // allow uninstallation of an explicitly preferred one -- the user can go
- // to Home settings and pick a different one, after which we'll permit
- // uninstallation of the now-not-default one.
- if (enabled && mHomePackages.contains(mPackageInfo.packageName)) {
- if (isBundled) {
- enabled = false;
- } else {
- ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
- ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
- if (currentDefaultHome == null) {
- // No preferred default, so permit uninstall only when
- // there is more than one candidate
- enabled = (mHomePackages.size() > 1);
- } else {
- // There is an explicit default home app -- forbid uninstall of
- // that one, but permit it for installed-but-inactive ones.
- enabled = !mPackageInfo.packageName.equals(currentDefaultHome.getPackageName());
- }
- }
- }
-
- if (mAppsControlDisallowedBySystem) {
- enabled = false;
- }
-
- try {
- IWebViewUpdateService webviewUpdateService =
- IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
- if (webviewUpdateService.isFallbackPackage(mAppEntry.info.packageName)) {
- enabled = false;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
-
- mActionButtons.setButton1Enabled(enabled);
- if (enabled) {
- // Register listener
- mActionButtons.setButton1OnClickListener(v -> handleUninstallButtonClick());
- }
- }
-
- @VisibleForTesting
- boolean initUninstallButtonForUserApp() {
- boolean enabled = true;
- if ((mPackageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
- && mUserManager.getUsers().size() >= 2) {
- // When we have multiple users, there is a separate menu
- // to uninstall for all users.
- enabled = false;
- } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
- enabled = false;
- mActionButtons.setButton1Visible(false);
- }
- mActionButtons.setButton1Text(R.string.uninstall_text).setButton1Positive(false);
- return enabled;
- }
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- mFinishing = false;
- final Activity activity = getActivity();
- mApplicationFeatureProvider = FeatureFactory.getFactory(activity)
- .getApplicationFeatureProvider(activity);
- mDpm = new DevicePolicyManagerWrapper(
- (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE));
- mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
- mPm = activity.getPackageManager();
-
- retrieveAppEntry();
- startListeningToPackageRemove();
-
- if (!ensurePackageInfoAvailable(activity)) {
- return;
- }
-
- setHasOptionsMenu(true);
-
- addDynamicPrefs();
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
- UserManager.DISALLOW_APPS_CONTROL, mUserId);
- mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
- UserManager.DISALLOW_APPS_CONTROL, mUserId);
-
- if (!refreshUi()) {
- setIntentAndFinish(true, true);
- }
- }
-
- @Override
- protected int getPreferenceScreenResId() {
- return R.xml.app_info_settings;
- }
-
- @Override
- protected String getLogTag() {
- return TAG;
- }
-
- @Override
- protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
- final String packageName = getPackageName();
- final List<AbstractPreferenceController> controllers = new ArrayList<>();
- final Lifecycle lifecycle = getLifecycle();
-
- // The following are controllers for preferences that needs to refresh the preference state
- // when app state changes.
- controllers.add(new AppStoragePreferenceController(context, this, lifecycle));
- controllers.add(new AppDataUsagePreferenceController(context, this, lifecycle));
- controllers.add(new AppNotificationPreferenceController(context, this));
- controllers.add(new AppOpenByDefaultPreferenceController(context, this));
- controllers.add(new AppPermissionPreferenceController(context, this, packageName));
- controllers.add(new AppVersionPreferenceController(context, this));
-
- for (AbstractPreferenceController controller : controllers) {
- mCallbacks.add((Callback) controller);
- }
-
- // The following are controllers for preferences that don't need to refresh the preference
- // state when app state changes.
- controllers.add(new AppBatteryPreferenceController(context, this, packageName, lifecycle));
- controllers.add(new AppMemoryPreferenceController(context, this, lifecycle));
- controllers.add(new DefaultHomeShortcutPreferenceController(context, packageName));
- controllers.add(new DefaultBrowserShortcutPreferenceController(context, packageName));
- controllers.add(new DefaultPhoneShortcutPreferenceController(context, packageName));
- controllers.add(new DefaultEmergencyShortcutPreferenceController(context, packageName));
- controllers.add(new DefaultSmsShortcutPreferenceController(context, packageName));
-
- final List<AbstractPreferenceController> advancedAppInfoControllers = new ArrayList<>();
- advancedAppInfoControllers.add(new DrawOverlayDetailPreferenceController(context, this));
- advancedAppInfoControllers.add(new WriteSystemSettingsPreferenceController(context, this));
- advancedAppInfoControllers.add(
- new PictureInPictureDetailPreferenceController(context, this, packageName));
- advancedAppInfoControllers.add(
- new ExternalSourceDetailPreferenceController(context, this, packageName));
- controllers.addAll(advancedAppInfoControllers);
- controllers.add(new PreferenceCategoryController(
- context, KEY_ADVANCED_APP_INFO_CATEGORY, advancedAppInfoControllers));
-
- return controllers;
- }
-
- public ApplicationsState.AppEntry getAppEntry() {
- if (mAppEntry == null) {
- retrieveAppEntry();
- }
- return mAppEntry;
- }
-
- public PackageInfo getPackageInfo() {
- if (mAppEntry == null) {
- retrieveAppEntry();
- }
- return mPackageInfo;
- }
-
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- if (mFinishing) {
- return;
- }
- final Activity activity = getActivity();
- mHeader = (LayoutPreference) findPreference(KEY_HEADER);
- mActionButtons = ((ActionButtonPreference) findPreference(KEY_ACTION_BUTTONS))
- .setButton2Text(R.string.force_stop)
- .setButton2Positive(false)
- .setButton2Enabled(false);
- EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header))
- .setRecyclerView(getListView(), getLifecycle())
- .setPackageName(mPackageName)
- .setHasAppInfoLink(false)
- .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
- EntityHeaderController.ActionType.ACTION_NONE)
- .styleActionBar(activity)
- .bindHeaderButtons();
-
- mInstantAppDomainsPreference =
- (AppDomainsPreference) findPreference(KEY_INSTANT_APP_SUPPORTED_LINKS);
- }
-
- @Override
- public void onPackageSizeChanged(String packageName) {
- if (!TextUtils.equals(packageName, mPackageName)) {
- Log.d(TAG, "Package change irrelevant, skipping");
- return;
- }
- refreshUi();
- }
-
- /**
- * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment
- * will finish.
- *
- * @return true if packageInfo is available.
- */
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- boolean ensurePackageInfoAvailable(Activity activity) {
- if (mPackageInfo == null) {
- mFinishing = true;
- Log.w(TAG, "Package info not available. Is this package already uninstalled?");
- activity.finishAndRemoveTask();
- return false;
- }
- return true;
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- if (mFinishing) {
- return;
- }
- menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
- mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
- uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem);
- if (uninstallUpdatesItem.isVisible()) {
- RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
- uninstallUpdatesItem, mAppsControlDisallowedAdmin);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case UNINSTALL_ALL_USERS_MENU:
- uninstallPkg(mAppEntry.info.packageName, true, false);
- return true;
- case UNINSTALL_UPDATES:
- uninstallPkg(mAppEntry.info.packageName, false, false);
- return true;
- }
- return false;
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- switch (requestCode) {
- case REQUEST_UNINSTALL:
- // Refresh option menu
- getActivity().invalidateOptionsMenu();
-
- if (mDisableAfterUninstall) {
- mDisableAfterUninstall = false;
- new DisableChanger(this, mAppEntry.info,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
- .execute((Object)null);
- }
- // continue with following operations
- case REQUEST_REMOVE_DEVICE_ADMIN:
- if (!refreshUi()) {
- setIntentAndFinish(true, true);
- } else {
- startListeningToPackageRemove();
- }
- break;
- }
- }
-
- /**
- * Utility method to hide and show specific preferences based on whether the app being displayed
- * is an Instant App or an installed app.
- */
- @VisibleForTesting
- void prepareInstantAppPrefs() {
- final boolean isInstant = AppUtils.isInstant(mPackageInfo.applicationInfo);
- if (isInstant) {
- Set<String> handledDomainSet = Utils.getHandledDomains(mPm, mPackageInfo.packageName);
- String[] handledDomains = handledDomainSet.toArray(new String[handledDomainSet.size()]);
- mInstantAppDomainsPreference.setTitles(handledDomains);
- // Dummy values, unused in the implementation
- mInstantAppDomainsPreference.setValues(new int[handledDomains.length]);
- } else {
- getPreferenceScreen().removePreference(mInstantAppDomainsPreference);
- }
- }
-
- // Utility method to set application label and icon.
- private void setAppLabelAndIcon(PackageInfo pkgInfo) {
- final View appSnippet = mHeader.findViewById(R.id.entity_header);
- mState.ensureIcon(mAppEntry);
- final Activity activity = getActivity();
- final boolean isInstantApp = AppUtils.isInstant(mPackageInfo.applicationInfo);
- final CharSequence summary =
- isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info));
- EntityHeaderController.newInstance(activity, this, appSnippet)
- .setLabel(mAppEntry)
- .setIcon(mAppEntry)
- .setSummary(summary)
- .setIsInstantApp(isInstantApp)
- .done(activity, false /* rebindActions */);
- }
-
- @VisibleForTesting
- boolean shouldShowUninstallForAll(AppEntry appEntry) {
- boolean showIt = true;
- if (mUpdatedSysApp) {
- showIt = false;
- } else if (appEntry == null) {
- showIt = false;
- } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- showIt = false;
- } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
- showIt = false;
- } else if (UserHandle.myUserId() != 0) {
- showIt = false;
- } else if (mUserManager.getUsers().size() < 2) {
- showIt = false;
- } else if (PackageUtil.countPackageInUsers(mPm, mUserManager, mPackageName) < 2
- && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
- showIt = false;
- } else if (AppUtils.isInstant(appEntry.info)) {
- showIt = false;
- }
- return showIt;
- }
-
- private boolean signaturesMatch(String pkg1, String pkg2) {
- if (pkg1 != null && pkg2 != null) {
- try {
- final int match = mPm.checkSignatures(pkg1, pkg2);
- if (match >= PackageManager.SIGNATURE_MATCH) {
- return true;
- }
- } catch (Exception e) {
- // e.g. named alternate package not found during lookup;
- // this is an expected case sometimes
- }
- }
- return false;
- }
-
- @VisibleForTesting
- boolean refreshUi() {
- retrieveAppEntry();
- if (mAppEntry == null) {
- return false; // onCreate must have failed, make sure to exit
- }
-
- if (mPackageInfo == null) {
- return false; // onCreate must have failed, make sure to exit
- }
-
- // Get list of "home" apps and trace through any meta-data references
- List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
- mPm.getHomeActivities(homeActivities);
- mHomePackages.clear();
- for (int i = 0; i< homeActivities.size(); i++) {
- ResolveInfo ri = homeActivities.get(i);
- final String activityPkg = ri.activityInfo.packageName;
- mHomePackages.add(activityPkg);
-
- // Also make sure to include anything proxying for the home app
- final Bundle metadata = ri.activityInfo.metaData;
- if (metadata != null) {
- final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
- if (signaturesMatch(metaPkg, activityPkg)) {
- mHomePackages.add(metaPkg);
- }
- }
- }
-
- checkForceStop();
- setAppLabelAndIcon(mPackageInfo);
- initUninstallButtons();
- prepareInstantAppPrefs();
-
- // Update the preference summaries.
- Activity context = getActivity();
- for (Callback callback : mCallbacks) {
- callback.refreshUi();
- }
-
- if (!mInitialized) {
- // First time init: are we displaying an uninstalled app?
- mInitialized = true;
- mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0;
- } else {
- // All other times: if the app no longer exists then we want
- // to go away.
- try {
- ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
- mAppEntry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_ANY_USER);
- if (!mShowUninstalled) {
- // If we did not start out with the app uninstalled, then
- // it transitioning to the uninstalled state for the current
- // user means we should go away as well.
- return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
- }
- } catch (NameNotFoundException e) {
- return false;
- }
- }
-
- return true;
- }
-
- protected AlertDialog createDialog(int id, int errorCode) {
- switch (id) {
- case DLG_DISABLE:
- return new AlertDialog.Builder(getActivity())
- .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
- .setPositiveButton(R.string.app_disable_dlg_positive,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Disable the app
- mMetricsFeatureProvider.action(getContext(),
- MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
- new DisableChanger(AppInfoDashboardFragment.this, mAppEntry.info,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
- .execute((Object)null);
- }
- })
- .setNegativeButton(R.string.dlg_cancel, null)
- .create();
- case DLG_SPECIAL_DISABLE:
- return new AlertDialog.Builder(getActivity())
- .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
- .setPositiveButton(R.string.app_disable_dlg_positive,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Disable the app and ask for uninstall
- mMetricsFeatureProvider.action(getContext(),
- MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
- uninstallPkg(mAppEntry.info.packageName,
- false, true);
- }
- })
- .setNegativeButton(R.string.dlg_cancel, null)
- .create();
- case DLG_FORCE_STOP:
- return new AlertDialog.Builder(getActivity())
- .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
- .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
- .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Force stop
- forceStopPackage(mAppEntry.info.packageName);
- }
- })
- .setNegativeButton(R.string.dlg_cancel, null)
- .create();
- }
- if (mInstantAppButtonsController != null) {
- return mInstantAppButtonsController.createDialog(id);
- }
- return null;
- }
-
- private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
- stopListeningToPackageRemove();
- // Create new intent to launch Uninstaller activity
- Uri packageURI = Uri.parse("package:"+packageName);
- Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
- uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
- mMetricsFeatureProvider.action(
- getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
- startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
- mDisableAfterUninstall = andDisable;
- }
-
- private void forceStopPackage(String pkgName) {
- mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
- ActivityManager am = (ActivityManager) getActivity().getSystemService(
- Context.ACTIVITY_SERVICE);
- Log.d(TAG, "Stopping package " + pkgName);
- am.forceStopPackage(pkgName);
- int userId = UserHandle.getUserId(mAppEntry.info.uid);
- mState.invalidatePackage(pkgName, userId);
- AppEntry newEnt = mState.getEntry(pkgName, userId);
- if (newEnt != null) {
- mAppEntry = newEnt;
- }
- checkForceStop();
- }
-
- private void updateForceStopButton(boolean enabled) {
- mActionButtons
- .setButton2Enabled(mAppsControlDisallowedBySystem ? false : enabled)
- .setButton2OnClickListener(mAppsControlDisallowedBySystem
- ? null : v -> handleForceStopButtonClick());
- }
-
- @VisibleForTesting
- void checkForceStop() {
- if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
- // User can't force stop device admin.
- Log.w(TAG, "User can't force stop device admin");
- updateForceStopButton(false);
- } else if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
- updateForceStopButton(false);
- mActionButtons.setButton2Visible(false);
- } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
- // If the app isn't explicitly stopped, then always show the
- // force stop button.
- Log.w(TAG, "App is not explicitly stopped");
- updateForceStopButton(true);
- } else {
- Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
- Uri.fromParts("package", mAppEntry.info.packageName, null));
- intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppEntry.info.packageName });
- intent.putExtra(Intent.EXTRA_UID, mAppEntry.info.uid);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mAppEntry.info.uid));
- Log.d(TAG, "Sending broadcast to query restart status for "
- + mAppEntry.info.packageName);
- getActivity().sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
- mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
- }
- }
-
- public static void startAppInfoFragment(Class<?> fragment, int title,
- SettingsPreferenceFragment caller, AppEntry appEntry) {
- // start new fragment to display extended information
- Bundle args = new Bundle();
- args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName);
- args.putInt(ARG_PACKAGE_UID, appEntry.info.uid);
-
- SettingsActivity sa = (SettingsActivity) caller.getActivity();
- sa.startPreferencePanel(caller, fragment.getName(), args, title, null, caller,
- SUB_INFO_FRAGMENT);
- }
-
- private void handleUninstallButtonClick() {
- if (mAppEntry == null) {
- setIntentAndFinish(true, true);
- return;
- }
- final String packageName = mAppEntry.info.packageName;
- if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
- stopListeningToPackageRemove();
- Activity activity = getActivity();
- Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
- uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
- mPackageName);
- mMetricsFeatureProvider.action(
- activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN);
- activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN);
- return;
- }
- EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
- packageName, mUserId);
- boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
- RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId);
- if (admin != null && !uninstallBlockedBySystem) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
- } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
- // If the system app has an update and this is the only user on the device,
- // then offer to downgrade the app, otherwise only offer to disable the
- // app for this user.
- if (mUpdatedSysApp && isSingleUser()) {
- showDialogInner(DLG_SPECIAL_DISABLE, 0);
- } else {
- showDialogInner(DLG_DISABLE, 0);
- }
- } else {
- mMetricsFeatureProvider.action(
- getActivity(),
- MetricsEvent.ACTION_SETTINGS_ENABLE_APP);
- new DisableChanger(this, mAppEntry.info,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
- .execute((Object) null);
- }
- } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
- uninstallPkg(packageName, true, false);
- } else {
- uninstallPkg(packageName, false, false);
- }
- }
-
- private void handleForceStopButtonClick() {
- if (mAppEntry == null) {
- setIntentAndFinish(true, true);
- return;
- }
- if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
- getActivity(), mAppsControlDisallowedAdmin);
- } else {
- showDialogInner(DLG_FORCE_STOP, 0);
- //forceStopPackage(mAppInfo.packageName);
- }
- }
-
- /** Returns whether there is only one user on this device, not including the system-only user */
- private boolean isSingleUser() {
- final int userCount = mUserManager.getUserCount();
- return userCount == 1
- || (mUserManager.isSplitSystemUser() && userCount == 2);
- }
-
- private void addDynamicPrefs() {
- if (UserManager.get(getContext()).isManagedProfile()) {
- return;
- }
- addAppInstallerInfoPref(getPreferenceScreen());
- maybeAddInstantAppButtons();
- }
-
- private void addAppInstallerInfoPref(PreferenceScreen screen) {
- String installerPackageName =
- AppStoreUtil.getInstallerPackageName(getContext(), mPackageName);
-
- final CharSequence installerLabel = Utils.getApplicationLabel(getContext(),
- installerPackageName);
- if (installerLabel == null) {
- return;
- }
- final int detailsStringId = AppUtils.isInstant(mPackageInfo.applicationInfo)
- ? R.string.instant_app_details_summary
- : R.string.app_install_details_summary;
- PreferenceCategory category = new PreferenceCategory(getPrefContext());
- category.setTitle(R.string.app_install_details_group_title);
- screen.addPreference(category);
- Preference pref = new Preference(getPrefContext());
- pref.setTitle(R.string.app_install_details_title);
- pref.setKey("app_info_store");
- pref.setSummary(getString(detailsStringId, installerLabel));
-
- Intent intent =
- AppStoreUtil.getAppStoreLink(getContext(), installerPackageName, mPackageName);
- if (intent != null) {
- pref.setIntent(intent);
- } else {
- pref.setEnabled(false);
- }
- category.addPreference(pref);
- }
-
- @VisibleForTesting
- void maybeAddInstantAppButtons() {
- if (AppUtils.isInstant(mPackageInfo.applicationInfo)) {
- LayoutPreference buttons = (LayoutPreference) findPreference(KEY_INSTANT_APP_BUTTONS);
- mInstantAppButtonsController = mApplicationFeatureProvider
- .newInstantAppButtonsController(this,
- buttons.findViewById(R.id.instant_app_button_container),
- id -> showDialogInner(id, 0))
- .setPackageName(mPackageName)
- .show();
- }
- }
-
- private void onPackageRemoved() {
- getActivity().finishActivity(SUB_INFO_FRAGMENT);
- getActivity().finishAndRemoveTask();
- }
-
- /**
- * Elicit this class for testing. Test cannot be done in robolectric because it
- * invokes the new API.
- */
- @VisibleForTesting
- public static class PackageUtil {
- /**
- * Count how many users in device have installed package {@paramref packageName}
- */
- public static int countPackageInUsers(PackageManager packageManager, UserManager
- userManager, String packageName) {
- final List<UserInfo> userInfos = userManager.getUsers(true);
- int count = 0;
-
- for (final UserInfo userInfo : userInfos) {
- try {
- // Use this API to check whether user has this package
- final ApplicationInfo info = packageManager.getApplicationInfoAsUser(
- packageName, PackageManager.GET_META_DATA, userInfo.id);
- if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
- count++;
- }
- } catch(NameNotFoundException e) {
- Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
- }
- }
-
- return count;
- }
- }
-
- private static class DisableChanger extends AsyncTask<Object, Object, Object> {
- final PackageManager mPm;
- final WeakReference<AppInfoDashboardFragment> mActivity;
- final ApplicationInfo mInfo;
- final int mState;
-
- DisableChanger(AppInfoDashboardFragment activity, ApplicationInfo info, int state) {
- mPm = activity.mPm;
- mActivity = new WeakReference<AppInfoDashboardFragment>(activity);
- mInfo = info;
- mState = state;
- }
-
- @Override
- protected Object doInBackground(Object... params) {
- mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
- return null;
- }
- }
-
- private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
- Log.d(TAG, "Got broadcast response: Restart status for "
- + mAppEntry.info.packageName + " " + enabled);
- updateForceStopButton(enabled);
- }
- };
-
- private String getPackageName() {
- if (mPackageName != null) {
- return mPackageName;
- }
- final Bundle args = getArguments();
- mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
- if (mPackageName == null) {
- Intent intent = (args == null) ?
- getActivity().getIntent() : (Intent) args.getParcelable("intent");
- if (intent != null) {
- mPackageName = intent.getData().getSchemeSpecificPart();
- }
- }
- return mPackageName;
- }
-
- private void retrieveAppEntry() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
- if (mState == null) {
- mState = ApplicationsState.getInstance(activity.getApplication());
- mSession = mState.newSession(this, getLifecycle());
- }
- mUserId = UserHandle.myUserId();
- mAppEntry = mState.getEntry(getPackageName(), UserHandle.myUserId());
- if (mAppEntry != null) {
- // Get application info again to refresh changed properties of application
- try {
- mPackageInfo = activity.getPackageManager().getPackageInfo(
- mAppEntry.info.packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS |
- PackageManager.MATCH_ANY_USER |
- PackageManager.GET_SIGNATURES |
- PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
- }
- } else {
- Log.w(TAG, "Missing AppEntry; maybe reinstalling?");
- mPackageInfo = null;
- }
- }
-
- private void setIntentAndFinish(boolean finish, boolean appChanged) {
- if (localLOGV) Log.i(TAG, "appChanged="+appChanged);
- Intent intent = new Intent();
- intent.putExtra(ManageApplications.APP_CHG, appChanged);
- SettingsActivity sa = (SettingsActivity)getActivity();
- sa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
- mFinishing = true;
- }
-
- private void showDialogInner(int id, int moveErrorCode) {
- DialogFragment newFragment =
- AppInfoBase.MyAlertDialogFragment.newInstance(id, moveErrorCode);
- newFragment.setTargetFragment(this, 0);
- newFragment.show(getFragmentManager(), "dialog " + id);
- }
-
- @Override
- public void onRunningStateChanged(boolean running) {
- // No op.
- }
-
- @Override
- public void onRebuildComplete(ArrayList<AppEntry> apps) {
- // No op.
- }
-
- @Override
- public void onPackageIconChanged() {
- // No op.
- }
-
- @Override
- public void onAllSizesComputed() {
- // No op.
- }
-
- @Override
- public void onLauncherInfoChanged() {
- // No op.
- }
-
- @Override
- public void onLoadEntriesCompleted() {
- // No op.
- }
-
- @Override
- public void onPackageListChanged() {
- if (!refreshUi()) {
- setIntentAndFinish(true, true);
- }
- }
-
- public static void startAppInfoFragment(Class<?> fragment, int titleRes,
- String pkg, int uid, Fragment source, int request, int sourceMetricsCategory) {
- startAppInfoFragment(fragment, titleRes, pkg, uid, source.getActivity(), request,
- sourceMetricsCategory);
- }
-
- public static void startAppInfoFragment(Class<?> fragment, int titleRes,
- String pkg, int uid, Activity source, int request, int sourceMetricsCategory) {
- Bundle args = new Bundle();
- args.putString(AppInfoBase.ARG_PACKAGE_NAME, pkg);
- args.putInt(AppInfoBase.ARG_PACKAGE_UID, uid);
-
- Intent intent = Utils.onBuildStartFragmentIntent(source, fragment.getName(),
- args, null, titleRes, null, false, sourceMetricsCategory);
- source.startActivityForResultAsUser(intent, request,
- new UserHandle(UserHandle.getUserId(uid)));
- }
-
- public static class MyAlertDialogFragment extends InstrumentedDialogFragment {
-
- private static final String ARG_ID = "id";
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.DIALOG_APP_INFO_ACTION;
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- int id = getArguments().getInt(ARG_ID);
- int errorCode = getArguments().getInt("moveError");
- Dialog dialog = ((AppInfoBase) getTargetFragment()).createDialog(id, errorCode);
- if (dialog == null) {
- throw new IllegalArgumentException("unknown id " + id);
- }
- return dialog;
- }
-
- public static AppInfoBase.MyAlertDialogFragment newInstance(int id, int errorCode) {
- AppInfoBase.MyAlertDialogFragment
- dialogFragment = new AppInfoBase.MyAlertDialogFragment();
- Bundle args = new Bundle();
- args.putInt(ARG_ID, id);
- args.putInt("moveError", errorCode);
- dialogFragment.setArguments(args);
- return dialogFragment;
- }
- }
-
- private void startListeningToPackageRemove() {
- if (mListeningToPackageRemove) {
- return;
- }
- mListeningToPackageRemove = true;
- final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- getContext().registerReceiver(mPackageRemovedReceiver, filter);
- }
-
- private void stopListeningToPackageRemove() {
- if (!mListeningToPackageRemove) {
- return;
- }
- mListeningToPackageRemove = false;
- getContext().unregisterReceiver(mPackageRemovedReceiver);
- }
-
- private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String packageName = intent.getData().getSchemeSpecificPart();
- if (!mFinishing && (mAppEntry == null || mAppEntry.info == null
- || TextUtils.equals(mAppEntry.info.packageName, packageName))) {
- onPackageRemoved();
- }
- }
- };
-
-}
diff --git a/src/com/android/settings/applications/AppStoreUtil.java b/src/com/android/settings/applications/AppStoreUtil.java
index f9b95b0..13e5516 100644
--- a/src/com/android/settings/applications/AppStoreUtil.java
+++ b/src/com/android/settings/applications/AppStoreUtil.java
@@ -16,11 +16,9 @@
package com.android.settings.applications;
-
import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
import android.util.Log;
// This class provides methods that help dealing with app stores.
@@ -43,9 +41,6 @@
} catch (IllegalArgumentException e) {
Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
}
- if (installerPackageName == null) {
- return null;
- }
return installerPackageName;
}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 2098bd6..f78459f 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -75,6 +75,7 @@
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.PictureInPictureDetails;
diff --git a/src/com/android/settings/applications/InstalledAppDetailsTop.java b/src/com/android/settings/applications/InstalledAppDetailsTop.java
index 174a86a..8090de0 100644
--- a/src/com/android/settings/applications/InstalledAppDetailsTop.java
+++ b/src/com/android/settings/applications/InstalledAppDetailsTop.java
@@ -20,6 +20,7 @@
import android.util.FeatureFlagUtils;
import com.android.settings.SettingsActivity;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
public class InstalledAppDetailsTop extends SettingsActivity {
diff --git a/src/com/android/settings/applications/RecentAppsPreferenceController.java b/src/com/android/settings/applications/RecentAppsPreferenceController.java
index c613a7b..ee954ac 100644
--- a/src/com/android/settings/applications/RecentAppsPreferenceController.java
+++ b/src/com/android/settings/applications/RecentAppsPreferenceController.java
@@ -40,6 +40,7 @@
import com.android.settings.R;
import com.android.settings.Utils;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.widget.AppPreference;
diff --git a/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
new file mode 100644
index 0000000..b10d06c
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceController.java
@@ -0,0 +1,318 @@
+/*
+ * 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.appinfo;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
+import android.webkit.IWebViewUpdateService;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.ActionButtonPreference;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class AppActionButtonPreferenceController extends BasePreferenceController
+ implements AppInfoDashboardFragment.Callback {
+
+ private static final String TAG = "AppActionButtonControl";
+ private static final String KEY_ACTION_BUTTONS = "action_buttons";
+
+ @VisibleForTesting
+ ActionButtonPreference mActionButtons;
+ private final AppInfoDashboardFragment mParent;
+ private final String mPackageName;
+ private final HashSet<String> mHomePackages = new HashSet<>();
+ private final ApplicationFeatureProvider mApplicationFeatureProvider;
+
+ private int mUserId;
+ private DevicePolicyManagerWrapper mDpm;
+ private UserManager mUserManager;
+ private PackageManager mPm;
+
+ private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean enabled = getResultCode() != Activity.RESULT_CANCELED;
+ Log.d(TAG, "Got broadcast response: Restart status for "
+ + mParent.getAppEntry().info.packageName + " " + enabled);
+ updateForceStopButton(enabled);
+ }
+ };
+
+ public AppActionButtonPreferenceController(Context context, AppInfoDashboardFragment parent,
+ String packageName) {
+ super(context, KEY_ACTION_BUTTONS);
+ mParent = parent;
+ mPackageName = packageName;
+ mUserId = UserHandle.myUserId();
+ mApplicationFeatureProvider = FeatureFactory.getFactory(context)
+ .getApplicationFeatureProvider(context);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mActionButtons = ((ActionButtonPreference) screen.findPreference(KEY_ACTION_BUTTONS))
+ .setButton2Text(R.string.force_stop)
+ .setButton2Positive(false)
+ .setButton2Enabled(false);
+ }
+
+ @Override
+ public void refreshUi() {
+ if (mPm == null) {
+ mPm = mContext.getPackageManager();
+ }
+ if (mDpm == null) {
+ mDpm = new DevicePolicyManagerWrapper(
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ }
+ if (mUserManager == null) {
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+ final AppEntry appEntry = mParent.getAppEntry();
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+
+ // Get list of "home" apps and trace through any meta-data references
+ final List<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ mPm.getHomeActivities(homeActivities);
+ mHomePackages.clear();
+ for (int i = 0; i< homeActivities.size(); i++) {
+ final ResolveInfo ri = homeActivities.get(i);
+ final String activityPkg = ri.activityInfo.packageName;
+ mHomePackages.add(activityPkg);
+
+ // Also make sure to include anything proxying for the home app
+ final Bundle metadata = ri.activityInfo.metaData;
+ if (metadata != null) {
+ final String metaPkg = metadata.getString(ActivityManager.META_HOME_ALTERNATE);
+ if (signaturesMatch(metaPkg, activityPkg)) {
+ mHomePackages.add(metaPkg);
+ }
+ }
+ }
+
+ checkForceStop(appEntry, packageInfo);
+ initUninstallButtons(appEntry, packageInfo);
+ }
+
+ @VisibleForTesting
+ void initUninstallButtons(AppEntry appEntry, PackageInfo packageInfo) {
+ final boolean isBundled = (appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ boolean enabled;
+ if (isBundled) {
+ enabled = handleDisableable(appEntry, packageInfo);
+ } else {
+ enabled = initUninstallButtonForUserApp();
+ }
+ // If this is a device admin, it can't be uninstalled or disabled.
+ // We do this here so the text of the button is still set correctly.
+ if (isBundled && mDpm.packageHasActiveAdmins(packageInfo.packageName)) {
+ enabled = false;
+ }
+
+ // We don't allow uninstalling DO/PO on *any* users, because if it's a system app,
+ // "uninstall" is actually "downgrade to the system version + disable", and "downgrade"
+ // will clear data on all users.
+ if (Utils.isProfileOrDeviceOwner(mUserManager, mDpm, packageInfo.packageName)) {
+ enabled = false;
+ }
+
+ // Don't allow uninstalling the device provisioning package.
+ if (Utils.isDeviceProvisioningPackage(mContext.getResources(), appEntry.info.packageName)) {
+ enabled = false;
+ }
+
+ // If the uninstall intent is already queued, disable the uninstall button
+ if (mDpm.isUninstallInQueue(mPackageName)) {
+ enabled = false;
+ }
+
+ // Home apps need special handling. Bundled ones we don't risk downgrading
+ // because that can interfere with home-key resolution. Furthermore, we
+ // can't allow uninstallation of the only home app, and we don't want to
+ // allow uninstallation of an explicitly preferred one -- the user can go
+ // to Home settings and pick a different one, after which we'll permit
+ // uninstallation of the now-not-default one.
+ if (enabled && mHomePackages.contains(packageInfo.packageName)) {
+ if (isBundled) {
+ enabled = false;
+ } else {
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
+ ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+ if (currentDefaultHome == null) {
+ // No preferred default, so permit uninstall only when
+ // there is more than one candidate
+ enabled = (mHomePackages.size() > 1);
+ } else {
+ // There is an explicit default home app -- forbid uninstall of
+ // that one, but permit it for installed-but-inactive ones.
+ enabled = !packageInfo.packageName.equals(currentDefaultHome.getPackageName());
+ }
+ }
+ }
+
+ if (RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId)) {
+ enabled = false;
+ }
+
+ try {
+ final IWebViewUpdateService webviewUpdateService =
+ IWebViewUpdateService.Stub.asInterface(
+ ServiceManager.getService("webviewupdate"));
+ if (webviewUpdateService.isFallbackPackage(appEntry.info.packageName)) {
+ enabled = false;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ mActionButtons.setButton1Enabled(enabled);
+ if (enabled) {
+ // Register listener
+ mActionButtons.setButton1OnClickListener(v -> mParent.handleUninstallButtonClick());
+ }
+ }
+
+ @VisibleForTesting
+ boolean initUninstallButtonForUserApp() {
+ boolean enabled = true;
+ final PackageInfo packageInfo = mParent.getPackageInfo();
+ if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0
+ && mUserManager.getUsers().size() >= 2) {
+ // When we have multiple users, there is a separate menu
+ // to uninstall for all users.
+ enabled = false;
+ } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
+ enabled = false;
+ mActionButtons.setButton1Visible(false);
+ }
+ mActionButtons.setButton1Text(R.string.uninstall_text).setButton1Positive(false);
+ return enabled;
+ }
+
+ @VisibleForTesting
+ boolean handleDisableable(AppEntry appEntry, PackageInfo packageInfo) {
+ boolean disableable = false;
+ // Try to prevent the user from bricking their phone
+ // by not allowing disabling of apps signed with the
+ // system cert and any launcher app in the system.
+ if (mHomePackages.contains(appEntry.info.packageName)
+ || Utils.isSystemPackage(mContext.getResources(), mPm, packageInfo)) {
+ // Disable button for core system applications.
+ mActionButtons
+ .setButton1Text(R.string.disable_text)
+ .setButton1Positive(false);
+ } else if (appEntry.info.enabled && appEntry.info.enabledSetting
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ mActionButtons
+ .setButton1Text(R.string.disable_text)
+ .setButton1Positive(false);
+ disableable = !mApplicationFeatureProvider.getKeepEnabledPackages()
+ .contains(appEntry.info.packageName);
+ } else {
+ mActionButtons
+ .setButton1Text(R.string.enable_text)
+ .setButton1Positive(true);
+ disableable = true;
+ }
+
+ return disableable;
+ }
+
+ private void updateForceStopButton(boolean enabled) {
+ final boolean disallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(
+ mContext, UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ mActionButtons
+ .setButton2Enabled(disallowedBySystem ? false : enabled)
+ .setButton2OnClickListener(
+ disallowedBySystem ? null : v -> mParent.handleForceStopButtonClick());
+ }
+
+ void checkForceStop(AppEntry appEntry, PackageInfo packageInfo) {
+ if (mDpm.packageHasActiveAdmins(packageInfo.packageName)) {
+ // User can't force stop device admin.
+ Log.w(TAG, "User can't force stop device admin");
+ updateForceStopButton(false);
+ } else if (AppUtils.isInstant(packageInfo.applicationInfo)) {
+ updateForceStopButton(false);
+ mActionButtons.setButton2Visible(false);
+ } else if ((appEntry.info.flags & ApplicationInfo.FLAG_STOPPED) == 0) {
+ // If the app isn't explicitly stopped, then always show the
+ // force stop button.
+ Log.w(TAG, "App is not explicitly stopped");
+ updateForceStopButton(true);
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+ Uri.fromParts("package", appEntry.info.packageName, null));
+ intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { appEntry.info.packageName });
+ intent.putExtra(Intent.EXTRA_UID, appEntry.info.uid);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(appEntry.info.uid));
+ Log.d(TAG, "Sending broadcast to query restart status for "
+ + appEntry.info.packageName);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
+ mCheckKillProcessesReceiver, null, Activity.RESULT_CANCELED, null, null);
+ }
+ }
+
+ private boolean signaturesMatch(String pkg1, String pkg2) {
+ if (pkg1 != null && pkg2 != null) {
+ try {
+ return mPm.checkSignatures(pkg1, pkg2) >= PackageManager.SIGNATURE_MATCH;
+ } catch (Exception e) {
+ // e.g. named alternate package not found during lookup;
+ // this is an expected case sometimes
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
index 017afe7..ffe2bf3 100644
--- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java
@@ -32,7 +32,6 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.BatteryEntry;
diff --git a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
index 61f3e46..669bc5a 100644
--- a/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceController.java
@@ -34,7 +34,6 @@
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.datausage.AppDataUsage;
import com.android.settings.datausage.DataUsageList;
import com.android.settings.datausage.DataUsageUtils;
diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
new file mode 100755
index 0000000..57e2b0c
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java
@@ -0,0 +1,831 @@
+/*
+ * 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.appinfo;
+
+import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.DeviceAdminAdd;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.applications.manageapplications.ManageApplications;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settings.widget.PreferenceCategoryController;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Dashboard fragment to display application information from Settings. This activity presents
+ * extended information associated with a package like code, data, total size, permissions
+ * used by the application and also the set of default launchable activities.
+ * For system applications, an option to clear user data is displayed only if data size is > 0.
+ * System applications that do not want clear user data do not have this option.
+ * For non-system applications, there is no option to clear data. Instead there is an option to
+ * uninstall the application.
+ */
+public class AppInfoDashboardFragment extends DashboardFragment
+ implements ApplicationsState.Callbacks {
+
+ private static final String TAG = "AppInfoDashboard";
+
+ // Menu identifiers
+ private static final int UNINSTALL_ALL_USERS_MENU = 1;
+ private static final int UNINSTALL_UPDATES = 2;
+
+ // Result code identifiers
+ @VisibleForTesting
+ static final int REQUEST_UNINSTALL = 0;
+ private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
+
+ static final int SUB_INFO_FRAGMENT = 1;
+
+ static final int LOADER_CHART_DATA = 2;
+ static final int LOADER_STORAGE = 3;
+ static final int LOADER_BATTERY = 4;
+
+ // Dialog identifiers used in showDialog
+ private static final int DLG_BASE = 0;
+ private static final int DLG_FORCE_STOP = DLG_BASE + 1;
+ private static final int DLG_DISABLE = DLG_BASE + 2;
+ private static final int DLG_SPECIAL_DISABLE = DLG_BASE + 3;
+
+ private static final String KEY_HEADER = "header_view";
+ private static final String KEY_ADVANCED_APP_INFO_CATEGORY = "advanced_app_info";
+
+ public static final String ARG_PACKAGE_NAME = "package";
+ public static final String ARG_PACKAGE_UID = "uid";
+
+ private static final boolean localLOGV = false;
+
+ private EnforcedAdmin mAppsControlDisallowedAdmin;
+ private boolean mAppsControlDisallowedBySystem;
+
+ private ApplicationsState mState;
+ private ApplicationsState.Session mSession;
+ private ApplicationsState.AppEntry mAppEntry;
+ private PackageInfo mPackageInfo;
+ private int mUserId;
+ private String mPackageName;
+
+ private DevicePolicyManagerWrapper mDpm;
+ private UserManager mUserManager;
+ private PackageManager mPm;
+
+ private boolean mFinishing;
+ private boolean mListeningToPackageRemove;
+
+
+ private boolean mInitialized;
+ private boolean mShowUninstalled;
+ private LayoutPreference mHeader;
+ private boolean mUpdatedSysApp = false;
+ private boolean mDisableAfterUninstall;
+
+ private List<Callback> mCallbacks = new ArrayList<>();
+
+ private InstantAppButtonsPreferenceController mInstantAppButtonPreferenceController;
+ private AppActionButtonPreferenceController mAppActionButtonPreferenceController;
+
+ /**
+ * Callback to invoke when app info has been changed.
+ */
+ public interface Callback {
+ void refreshUi();
+ }
+
+ private boolean isDisabledUntilUsed() {
+ return mAppEntry.info.enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mFinishing = false;
+ final Activity activity = getActivity();
+ mDpm = new DevicePolicyManagerWrapper(
+ (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE));
+ mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
+ mPm = activity.getPackageManager();
+
+ retrieveAppEntry();
+ startListeningToPackageRemove();
+
+ if (!ensurePackageInfoAvailable(activity)) {
+ return;
+ }
+
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.APPLICATIONS_INSTALLED_APP_DETAILS;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mAppsControlDisallowedAdmin = RestrictedLockUtils.checkIfRestrictionEnforced(getActivity(),
+ UserManager.DISALLOW_APPS_CONTROL, mUserId);
+ mAppsControlDisallowedBySystem = RestrictedLockUtils.hasBaseUserRestriction(getActivity(),
+ UserManager.DISALLOW_APPS_CONTROL, mUserId);
+
+ if (!refreshUi()) {
+ setIntentAndFinish(true, true);
+ }
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.app_info_settings;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
+ final String packageName = getPackageName();
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ final Lifecycle lifecycle = getLifecycle();
+
+ // The following are controllers for preferences that needs to refresh the preference state
+ // when app state changes.
+ controllers.add(new AppStoragePreferenceController(context, this, lifecycle));
+ controllers.add(new AppDataUsagePreferenceController(context, this, lifecycle));
+ controllers.add(new AppNotificationPreferenceController(context, this));
+ controllers.add(new AppOpenByDefaultPreferenceController(context, this));
+ controllers.add(new AppPermissionPreferenceController(context, this, packageName));
+ controllers.add(new AppVersionPreferenceController(context, this));
+ controllers.add(new InstantAppDomainsPreferenceController(context, this));
+ final AppInstallerInfoPreferenceController appInstallerInfoPreferenceController =
+ new AppInstallerInfoPreferenceController(context, this, packageName);
+ controllers.add(appInstallerInfoPreferenceController);
+ mAppActionButtonPreferenceController =
+ new AppActionButtonPreferenceController(context, this, packageName);
+ controllers.add(mAppActionButtonPreferenceController);
+
+ for (AbstractPreferenceController controller : controllers) {
+ mCallbacks.add((Callback) controller);
+ }
+
+ // The following are controllers for preferences that don't need to refresh the preference
+ // state when app state changes.
+ mInstantAppButtonPreferenceController =
+ new InstantAppButtonsPreferenceController(context, this, packageName);
+ controllers.add(mInstantAppButtonPreferenceController);
+ controllers.add(new AppBatteryPreferenceController(context, this, packageName, lifecycle));
+ controllers.add(new AppMemoryPreferenceController(context, this, lifecycle));
+ controllers.add(new DefaultHomeShortcutPreferenceController(context, packageName));
+ controllers.add(new DefaultBrowserShortcutPreferenceController(context, packageName));
+ controllers.add(new DefaultPhoneShortcutPreferenceController(context, packageName));
+ controllers.add(new DefaultEmergencyShortcutPreferenceController(context, packageName));
+ controllers.add(new DefaultSmsShortcutPreferenceController(context, packageName));
+
+ final List<AbstractPreferenceController> advancedAppInfoControllers = new ArrayList<>();
+ advancedAppInfoControllers.add(new DrawOverlayDetailPreferenceController(context, this));
+ advancedAppInfoControllers.add(new WriteSystemSettingsPreferenceController(context, this));
+ advancedAppInfoControllers.add(
+ new PictureInPictureDetailPreferenceController(context, this, packageName));
+ advancedAppInfoControllers.add(
+ new ExternalSourceDetailPreferenceController(context, this, packageName));
+ controllers.addAll(advancedAppInfoControllers);
+ controllers.add(new PreferenceCategoryController(
+ context, KEY_ADVANCED_APP_INFO_CATEGORY, advancedAppInfoControllers));
+
+ controllers.add(new AppInstallerPreferenceCategoryController(
+ context, Arrays.asList(appInstallerInfoPreferenceController)));
+
+ return controllers;
+ }
+
+ ApplicationsState.AppEntry getAppEntry() {
+ if (mAppEntry == null) {
+ retrieveAppEntry();
+ }
+ return mAppEntry;
+ }
+
+ PackageInfo getPackageInfo() {
+ if (mAppEntry == null) {
+ retrieveAppEntry();
+ }
+ return mPackageInfo;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (mFinishing) {
+ return;
+ }
+ final Activity activity = getActivity();
+ mHeader = (LayoutPreference) findPreference(KEY_HEADER);
+ EntityHeaderController.newInstance(activity, this, mHeader.findViewById(R.id.entity_header))
+ .setRecyclerView(getListView(), getLifecycle())
+ .setPackageName(mPackageName)
+ .setHasAppInfoLink(false)
+ .setButtonActions(EntityHeaderController.ActionType.ACTION_APP_PREFERENCE,
+ EntityHeaderController.ActionType.ACTION_NONE)
+ .styleActionBar(activity)
+ .bindHeaderButtons();
+
+ }
+
+ @Override
+ public void onPackageSizeChanged(String packageName) {
+ if (!TextUtils.equals(packageName, mPackageName)) {
+ Log.d(TAG, "Package change irrelevant, skipping");
+ return;
+ }
+ refreshUi();
+ }
+
+ /**
+ * Ensures the {@link PackageInfo} is available to proceed. If it's not available, the fragment
+ * will finish.
+ *
+ * @return true if packageInfo is available.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean ensurePackageInfoAvailable(Activity activity) {
+ if (mPackageInfo == null) {
+ mFinishing = true;
+ Log.w(TAG, "Package info not available. Is this package already uninstalled?");
+ activity.finishAndRemoveTask();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ menu.add(0, UNINSTALL_UPDATES, 0, R.string.app_factory_reset)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+ if (mFinishing) {
+ return;
+ }
+ menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry));
+ mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES);
+ uninstallUpdatesItem.setVisible(mUpdatedSysApp && !mAppsControlDisallowedBySystem);
+ if (uninstallUpdatesItem.isVisible()) {
+ RestrictedLockUtils.setMenuItemAsDisabledByAdmin(getActivity(),
+ uninstallUpdatesItem, mAppsControlDisallowedAdmin);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case UNINSTALL_ALL_USERS_MENU:
+ uninstallPkg(mAppEntry.info.packageName, true, false);
+ return true;
+ case UNINSTALL_UPDATES:
+ uninstallPkg(mAppEntry.info.packageName, false, false);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case REQUEST_UNINSTALL:
+ // Refresh option menu
+ getActivity().invalidateOptionsMenu();
+
+ if (mDisableAfterUninstall) {
+ mDisableAfterUninstall = false;
+ new DisableChanger(this, mAppEntry.info,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
+ .execute((Object) null);
+ }
+ // continue with following operations
+ case REQUEST_REMOVE_DEVICE_ADMIN:
+ if (!refreshUi()) {
+ setIntentAndFinish(true, true);
+ } else {
+ startListeningToPackageRemove();
+ }
+ break;
+ }
+ }
+
+ // Utility method to set application label and icon.
+ private void setAppLabelAndIcon(PackageInfo pkgInfo) {
+ final View appSnippet = mHeader.findViewById(R.id.entity_header);
+ mState.ensureIcon(mAppEntry);
+ final Activity activity = getActivity();
+ final boolean isInstantApp = AppUtils.isInstant(mPackageInfo.applicationInfo);
+ final CharSequence summary =
+ isInstantApp ? null : getString(Utils.getInstallationStatus(mAppEntry.info));
+ EntityHeaderController.newInstance(activity, this, appSnippet)
+ .setLabel(mAppEntry)
+ .setIcon(mAppEntry)
+ .setSummary(summary)
+ .setIsInstantApp(isInstantApp)
+ .done(activity, false /* rebindActions */);
+ }
+
+ @VisibleForTesting
+ boolean shouldShowUninstallForAll(AppEntry appEntry) {
+ boolean showIt = true;
+ if (mUpdatedSysApp) {
+ showIt = false;
+ } else if (appEntry == null) {
+ showIt = false;
+ } else if ((appEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ showIt = false;
+ } else if (mPackageInfo == null || mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
+ showIt = false;
+ } else if (UserHandle.myUserId() != 0) {
+ showIt = false;
+ } else if (mUserManager.getUsers().size() < 2) {
+ showIt = false;
+ } else if (getNumberOfUserWithPackageInstalled(mPackageName) < 2
+ && (appEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
+ showIt = false;
+ } else if (AppUtils.isInstant(appEntry.info)) {
+ showIt = false;
+ }
+ return showIt;
+ }
+
+ @VisibleForTesting
+ boolean refreshUi() {
+ retrieveAppEntry();
+ if (mAppEntry == null) {
+ return false; // onCreate must have failed, make sure to exit
+ }
+
+ if (mPackageInfo == null) {
+ return false; // onCreate must have failed, make sure to exit
+ }
+
+
+ setAppLabelAndIcon(mPackageInfo);
+
+ // Update the preference summaries.
+ final Activity context = getActivity();
+ for (Callback callback : mCallbacks) {
+ callback.refreshUi();
+ }
+
+ if (!mInitialized) {
+ // First time init: are we displaying an uninstalled app?
+ mInitialized = true;
+ mShowUninstalled = (mAppEntry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0;
+ } else {
+ // All other times: if the app no longer exists then we want
+ // to go away.
+ try {
+ final ApplicationInfo ainfo = context.getPackageManager().getApplicationInfo(
+ mAppEntry.info.packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_ANY_USER);
+ if (!mShowUninstalled) {
+ // If we did not start out with the app uninstalled, then
+ // it transitioning to the uninstalled state for the current
+ // user means we should go away as well.
+ return (ainfo.flags&ApplicationInfo.FLAG_INSTALLED) != 0;
+ }
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @VisibleForTesting
+ AlertDialog createDialog(int id, int errorCode) {
+ switch (id) {
+ case DLG_DISABLE:
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
+ .setPositiveButton(R.string.app_disable_dlg_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Disable the app
+ mMetricsFeatureProvider.action(getContext(),
+ MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
+ new DisableChanger(AppInfoDashboardFragment.this, mAppEntry.info,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER)
+ .execute((Object)null);
+ }
+ })
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ case DLG_SPECIAL_DISABLE:
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(getActivity().getText(R.string.app_disable_dlg_text))
+ .setPositiveButton(R.string.app_disable_dlg_positive,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Disable the app and ask for uninstall
+ mMetricsFeatureProvider.action(getContext(),
+ MetricsEvent.ACTION_SETTINGS_DISABLE_APP);
+ uninstallPkg(mAppEntry.info.packageName,
+ false, true);
+ }
+ })
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ case DLG_FORCE_STOP:
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(getActivity().getText(R.string.force_stop_dlg_title))
+ .setMessage(getActivity().getText(R.string.force_stop_dlg_text))
+ .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Force stop
+ forceStopPackage(mAppEntry.info.packageName);
+ }
+ })
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ }
+ return mInstantAppButtonPreferenceController.createDialog(id);
+ }
+
+ private void uninstallPkg(String packageName, boolean allUsers, boolean andDisable) {
+ stopListeningToPackageRemove();
+ // Create new intent to launch Uninstaller activity
+ final Uri packageURI = Uri.parse("package:"+packageName);
+ final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
+ uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, allUsers);
+ mMetricsFeatureProvider.action(
+ getContext(), MetricsEvent.ACTION_SETTINGS_UNINSTALL_APP);
+ startActivityForResult(uninstallIntent, REQUEST_UNINSTALL);
+ mDisableAfterUninstall = andDisable;
+ }
+
+ private void forceStopPackage(String pkgName) {
+ mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_APP_FORCE_STOP, pkgName);
+ final ActivityManager am = (ActivityManager) getActivity().getSystemService(
+ Context.ACTIVITY_SERVICE);
+ Log.d(TAG, "Stopping package " + pkgName);
+ am.forceStopPackage(pkgName);
+ final int userId = UserHandle.getUserId(mAppEntry.info.uid);
+ mState.invalidatePackage(pkgName, userId);
+ final AppEntry newEnt = mState.getEntry(pkgName, userId);
+ if (newEnt != null) {
+ mAppEntry = newEnt;
+ }
+ mAppActionButtonPreferenceController.checkForceStop(mAppEntry, mPackageInfo);
+ }
+
+ public static void startAppInfoFragment(Class<?> fragment, int title,
+ SettingsPreferenceFragment caller, AppEntry appEntry) {
+ // start new fragment to display extended information
+ final Bundle args = new Bundle();
+ args.putString(ARG_PACKAGE_NAME, appEntry.info.packageName);
+ args.putInt(ARG_PACKAGE_UID, appEntry.info.uid);
+
+ final SettingsActivity sa = (SettingsActivity) caller.getActivity();
+ sa.startPreferencePanel(caller, fragment.getName(), args, title, null, caller,
+ SUB_INFO_FRAGMENT);
+ }
+
+ void handleUninstallButtonClick() {
+ if (mAppEntry == null) {
+ setIntentAndFinish(true, true);
+ return;
+ }
+ final String packageName = mAppEntry.info.packageName;
+ if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) {
+ stopListeningToPackageRemove();
+ final Activity activity = getActivity();
+ final Intent uninstallDAIntent = new Intent(activity, DeviceAdminAdd.class);
+ uninstallDAIntent.putExtra(DeviceAdminAdd.EXTRA_DEVICE_ADMIN_PACKAGE_NAME,
+ mPackageName);
+ mMetricsFeatureProvider.action(
+ activity, MetricsEvent.ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN);
+ activity.startActivityForResult(uninstallDAIntent, REQUEST_REMOVE_DEVICE_ADMIN);
+ return;
+ }
+ final EnforcedAdmin admin = RestrictedLockUtils.checkIfUninstallBlocked(getActivity(),
+ packageName, mUserId);
+ final boolean uninstallBlockedBySystem = mAppsControlDisallowedBySystem ||
+ RestrictedLockUtils.hasBaseUserRestriction(getActivity(), packageName, mUserId);
+ if (admin != null && !uninstallBlockedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), admin);
+ } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if (mAppEntry.info.enabled && !isDisabledUntilUsed()) {
+ // If the system app has an update and this is the only user on the device,
+ // then offer to downgrade the app, otherwise only offer to disable the
+ // app for this user.
+ if (mUpdatedSysApp && isSingleUser()) {
+ showDialogInner(DLG_SPECIAL_DISABLE, 0);
+ } else {
+ showDialogInner(DLG_DISABLE, 0);
+ }
+ } else {
+ mMetricsFeatureProvider.action(
+ getActivity(),
+ MetricsEvent.ACTION_SETTINGS_ENABLE_APP);
+ new DisableChanger(this, mAppEntry.info,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ .execute((Object) null);
+ }
+ } else if ((mAppEntry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ uninstallPkg(packageName, true, false);
+ } else {
+ uninstallPkg(packageName, false, false);
+ }
+ }
+
+ void handleForceStopButtonClick() {
+ if (mAppEntry == null) {
+ setIntentAndFinish(true, true);
+ return;
+ }
+ if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ getActivity(), mAppsControlDisallowedAdmin);
+ } else {
+ showDialogInner(DLG_FORCE_STOP, 0);
+ //forceStopPackage(mAppInfo.packageName);
+ }
+ }
+
+ /** Returns whether there is only one user on this device, not including the system-only user */
+ private boolean isSingleUser() {
+ final int userCount = mUserManager.getUserCount();
+ return userCount == 1 || (mUserManager.isSplitSystemUser() && userCount == 2);
+ }
+
+ private void onPackageRemoved() {
+ getActivity().finishActivity(SUB_INFO_FRAGMENT);
+ getActivity().finishAndRemoveTask();
+ }
+
+ @VisibleForTesting
+ int getNumberOfUserWithPackageInstalled(String packageName) {
+ final List<UserInfo> userInfos = mUserManager.getUsers(true);
+ int count = 0;
+
+ for (final UserInfo userInfo : userInfos) {
+ try {
+ // Use this API to check whether user has this package
+ final ApplicationInfo info = mPm.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userInfo.id);
+ if ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
+ count++;
+ }
+ } catch(NameNotFoundException e) {
+ Log.e(TAG, "Package: " + packageName + " not found for user: " + userInfo.id);
+ }
+ }
+
+ return count;
+ }
+
+ private static class DisableChanger extends AsyncTask<Object, Object, Object> {
+ final PackageManager mPm;
+ final WeakReference<AppInfoDashboardFragment> mActivity;
+ final ApplicationInfo mInfo;
+ final int mState;
+
+ DisableChanger(AppInfoDashboardFragment activity, ApplicationInfo info, int state) {
+ mPm = activity.mPm;
+ mActivity = new WeakReference<AppInfoDashboardFragment>(activity);
+ mInfo = info;
+ mState = state;
+ }
+
+ @Override
+ protected Object doInBackground(Object... params) {
+ mPm.setApplicationEnabledSetting(mInfo.packageName, mState, 0);
+ return null;
+ }
+ }
+
+ private String getPackageName() {
+ if (mPackageName != null) {
+ return mPackageName;
+ }
+ final Bundle args = getArguments();
+ mPackageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
+ if (mPackageName == null) {
+ final Intent intent = (args == null) ?
+ getActivity().getIntent() : (Intent) args.getParcelable("intent");
+ if (intent != null) {
+ mPackageName = intent.getData().getSchemeSpecificPart();
+ }
+ }
+ return mPackageName;
+ }
+
+ private void retrieveAppEntry() {
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ if (mState == null) {
+ mState = ApplicationsState.getInstance(activity.getApplication());
+ mSession = mState.newSession(this, getLifecycle());
+ }
+ mUserId = UserHandle.myUserId();
+ mAppEntry = mState.getEntry(getPackageName(), UserHandle.myUserId());
+ if (mAppEntry != null) {
+ // Get application info again to refresh changed properties of application
+ try {
+ mPackageInfo = activity.getPackageManager().getPackageInfo(
+ mAppEntry.info.packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS |
+ PackageManager.MATCH_ANY_USER |
+ PackageManager.GET_SIGNATURES |
+ PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Exception when retrieving package:" + mAppEntry.info.packageName, e);
+ }
+ } else {
+ Log.w(TAG, "Missing AppEntry; maybe reinstalling?");
+ mPackageInfo = null;
+ }
+ }
+
+ private void setIntentAndFinish(boolean finish, boolean appChanged) {
+ if (localLOGV) Log.i(TAG, "appChanged="+appChanged);
+ final Intent intent = new Intent();
+ intent.putExtra(ManageApplications.APP_CHG, appChanged);
+ final SettingsActivity sa = (SettingsActivity)getActivity();
+ sa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
+ mFinishing = true;
+ }
+
+ void showDialogInner(int id, int moveErrorCode) {
+ final DialogFragment newFragment = MyAlertDialogFragment.newInstance(id, moveErrorCode);
+ newFragment.setTargetFragment(this, 0);
+ newFragment.show(getFragmentManager(), "dialog " + id);
+ }
+
+ @Override
+ public void onRunningStateChanged(boolean running) {
+ // No op.
+ }
+
+ @Override
+ public void onRebuildComplete(ArrayList<AppEntry> apps) {
+ // No op.
+ }
+
+ @Override
+ public void onPackageIconChanged() {
+ // No op.
+ }
+
+ @Override
+ public void onAllSizesComputed() {
+ // No op.
+ }
+
+ @Override
+ public void onLauncherInfoChanged() {
+ // No op.
+ }
+
+ @Override
+ public void onLoadEntriesCompleted() {
+ // No op.
+ }
+
+ @Override
+ public void onPackageListChanged() {
+ if (!refreshUi()) {
+ setIntentAndFinish(true, true);
+ }
+ }
+
+ public static class MyAlertDialogFragment extends InstrumentedDialogFragment {
+
+ private static final String ARG_ID = "id";
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DIALOG_APP_INFO_ACTION;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final int id = getArguments().getInt(ARG_ID);
+ final int errorCode = getArguments().getInt("moveError");
+ final Dialog dialog =
+ ((AppInfoDashboardFragment) getTargetFragment()).createDialog(id, errorCode);
+ if (dialog == null) {
+ throw new IllegalArgumentException("unknown id " + id);
+ }
+ return dialog;
+ }
+
+ public static MyAlertDialogFragment newInstance(int id, int errorCode) {
+ final MyAlertDialogFragment dialogFragment = new MyAlertDialogFragment();
+ final Bundle args = new Bundle();
+ args.putInt(ARG_ID, id);
+ args.putInt("moveError", errorCode);
+ dialogFragment.setArguments(args);
+ return dialogFragment;
+ }
+ }
+
+ private void startListeningToPackageRemove() {
+ if (mListeningToPackageRemove) {
+ return;
+ }
+ mListeningToPackageRemove = true;
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ getContext().registerReceiver(mPackageRemovedReceiver, filter);
+ }
+
+ private void stopListeningToPackageRemove() {
+ if (!mListeningToPackageRemove) {
+ return;
+ }
+ mListeningToPackageRemove = false;
+ getContext().unregisterReceiver(mPackageRemovedReceiver);
+ }
+
+ private final BroadcastReceiver mPackageRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String packageName = intent.getData().getSchemeSpecificPart();
+ if (!mFinishing && (mAppEntry == null || mAppEntry.info == null
+ || TextUtils.equals(mAppEntry.info.packageName, packageName))) {
+ onPackageRemoved();
+ }
+ }
+ };
+
+}
diff --git a/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java b/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java
index eac0a0c..105a01e 100644
--- a/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java
+++ b/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBase.java
@@ -22,7 +22,6 @@
import android.text.TextUtils;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.core.BasePreferenceController;
/*
diff --git a/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
new file mode 100644
index 0000000..1fdc690
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceController.java
@@ -0,0 +1,68 @@
+/*
+ * 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.appinfo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.AppStoreUtil;
+import com.android.settingslib.applications.AppUtils;
+
+public class AppInstallerInfoPreferenceController extends AppInfoPreferenceControllerBase {
+
+ private static final String KEY_APP_INSTALLER_INFO = "app_info_store";
+
+ private final String mPackageName;
+ private final String mInstallerPackage;
+ private final CharSequence mInstallerLabel;
+
+ public AppInstallerInfoPreferenceController(Context context, AppInfoDashboardFragment parent,
+ String packageName) {
+ super(context, parent, KEY_APP_INSTALLER_INFO);
+ mPackageName = packageName;
+ mInstallerPackage = AppStoreUtil.getInstallerPackageName(mContext, mPackageName);
+ mInstallerLabel = Utils.getApplicationLabel(mContext, mInstallerPackage);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (UserManager.get(mContext).isManagedProfile()) {
+ return DISABLED_FOR_USER;
+ }
+ return mInstallerLabel!= null ? AVAILABLE : DISABLED_FOR_USER;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final int detailsStringId = AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
+ ? R.string.instant_app_details_summary
+ : R.string.app_install_details_summary;
+ preference.setSummary(mContext.getString(detailsStringId, mInstallerLabel));
+
+ Intent intent = AppStoreUtil.getAppStoreLink(mContext, mInstallerPackage, mPackageName);
+ if (intent != null) {
+ preference.setIntent(intent);
+ } else {
+ preference.setEnabled(false);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/applications/appinfo/AppInstallerPreferenceCategoryController.java b/src/com/android/settings/applications/appinfo/AppInstallerPreferenceCategoryController.java
new file mode 100644
index 0000000..0e6ffe8
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/AppInstallerPreferenceCategoryController.java
@@ -0,0 +1,35 @@
+/*
+ * 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.appinfo;
+
+import android.content.Context;
+
+import com.android.settings.widget.PreferenceCategoryController;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.List;
+
+public class AppInstallerPreferenceCategoryController extends PreferenceCategoryController {
+
+ private static final String KEY_APP_INSTALLER_INFO_CATEGORY = "app_installer";
+
+ public AppInstallerPreferenceCategoryController(Context context,
+ List<AbstractPreferenceController> childrenControllers) {
+ super(context, KEY_APP_INSTALLER_INFO_CATEGORY, childrenControllers);
+ }
+
+}
diff --git a/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java
index 3943041..7b497a9 100644
--- a/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppMemoryPreferenceController.java
@@ -26,7 +26,6 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.ProcStatsData;
import com.android.settings.applications.ProcStatsEntry;
import com.android.settings.applications.ProcStatsPackageEntry;
diff --git a/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
index 7eef370..1f19504 100644
--- a/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppNotificationPreferenceController.java
@@ -20,7 +20,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
index a56e3fb..3f20381 100644
--- a/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceController.java
@@ -26,7 +26,6 @@
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.AppLaunchSettings;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java
index 815e8d8..b844f78 100644
--- a/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppPermissionPreferenceController.java
@@ -26,7 +26,6 @@
import android.util.Log;
import com.android.settings.R;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settingslib.applications.PermissionsSummaryHelper;
import java.util.ArrayList;
diff --git a/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java b/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java
index d737288..86383cb 100644
--- a/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppStoragePreferenceController.java
@@ -28,7 +28,6 @@
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.AppStorageSettings;
import com.android.settings.applications.FetchPackageStorageAsyncLoader;
import com.android.settingslib.applications.StorageStatsSource;
diff --git a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
index 82719f7..0cfeb008 100644
--- a/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/AppVersionPreferenceController.java
@@ -21,7 +21,6 @@
import android.text.BidiFormatter;
import com.android.settings.R;
-import com.android.settings.applications.AppInfoDashboardFragment;
public class AppVersionPreferenceController extends AppInfoPreferenceControllerBase {
diff --git a/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java
index 314d799..37a9edf 100644
--- a/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceController.java
@@ -25,7 +25,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
public class DrawOverlayDetailPreferenceController extends AppInfoPreferenceControllerBase {
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java
index 4ac67ed..6fb6dc3 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceController.java
@@ -22,7 +22,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.AppStateInstallAppsBridge;
public class ExternalSourceDetailPreferenceController extends AppInfoPreferenceControllerBase {
diff --git a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
index 0400066..87e5fdb 100644
--- a/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/appinfo/ExternalSourcesDetails.java
@@ -107,6 +107,9 @@
@Override
protected boolean refreshUi() {
+ if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
+ return false;
+ }
if (mUserManager.hasBaseUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
UserHandle.of(UserHandle.myUserId()))) {
mSwitchPref.setChecked(false);
diff --git a/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java
new file mode 100644
index 0000000..b9fe003
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * 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.appinfo;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.applications.instantapps.InstantAppButtonsController;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.applications.AppUtils;
+
+public class InstantAppButtonsPreferenceController extends BasePreferenceController {
+
+ private static final String KEY_INSTANT_APP_BUTTONS = "instant_app_buttons";
+
+ private final AppInfoDashboardFragment mParent;
+ private final String mPackageName;
+ private InstantAppButtonsController mInstantAppButtonsController;
+
+ public InstantAppButtonsPreferenceController(Context context, AppInfoDashboardFragment parent,
+ String packageName) {
+ super(context, KEY_INSTANT_APP_BUTTONS);
+ mParent = parent;
+ mPackageName = packageName;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
+ ? AVAILABLE : DISABLED_FOR_USER;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ LayoutPreference buttons =
+ (LayoutPreference) screen.findPreference(KEY_INSTANT_APP_BUTTONS);
+ mInstantAppButtonsController = getApplicationFeatureProvider()
+ .newInstantAppButtonsController(mParent,
+ buttons.findViewById(R.id.instant_app_button_container),
+ id -> mParent.showDialogInner(id, 0))
+ .setPackageName(mPackageName)
+ .show();
+ }
+
+ public AlertDialog createDialog(int id) {
+ return mInstantAppButtonsController.createDialog(id);
+ }
+
+ @VisibleForTesting
+ ApplicationFeatureProvider getApplicationFeatureProvider() {
+ return FeatureFactory.getFactory(mContext).getApplicationFeatureProvider(mContext);
+ }
+}
diff --git a/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java b/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java
new file mode 100644
index 0000000..d89c538
--- /dev/null
+++ b/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceController.java
@@ -0,0 +1,59 @@
+/*
+ * 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.appinfo;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.Utils;
+import com.android.settings.applications.AppDomainsPreference;
+import com.android.settingslib.applications.AppUtils;
+
+import java.util.Set;
+
+public class InstantAppDomainsPreferenceController extends AppInfoPreferenceControllerBase {
+
+ private static final String KEY_INSTANT_APP_SUPPORTED_LINKS =
+ "instant_app_launch_supported_domain_urls";
+
+ private PackageManager mPackageManager;
+
+ public InstantAppDomainsPreferenceController(Context context, AppInfoDashboardFragment parent) {
+ super(context, parent, KEY_INSTANT_APP_SUPPORTED_LINKS);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AppUtils.isInstant(mParent.getPackageInfo().applicationInfo)
+ ? AVAILABLE : DISABLED_FOR_USER;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final AppDomainsPreference instantAppDomainsPreference = (AppDomainsPreference) preference;
+ final Set<String> handledDomainSet =
+ Utils.getHandledDomains(mPackageManager, mParent.getPackageInfo().packageName);
+ final String[] handledDomains =
+ handledDomainSet.toArray(new String[handledDomainSet.size()]);
+ instantAppDomainsPreference.setTitles(handledDomains);
+ // Dummy values, unused in the implementation
+ instantAppDomainsPreference.setValues(new int[handledDomains.length]);
+ }
+
+}
diff --git a/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java
index aea6bae..1873683 100644
--- a/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceController.java
@@ -26,7 +26,6 @@
import android.util.Log;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
public class PictureInPictureDetailPreferenceController extends AppInfoPreferenceControllerBase {
diff --git a/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java b/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java
index 55b181a..2a88d2f 100644
--- a/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java
+++ b/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceController.java
@@ -25,7 +25,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.applications.AppInfoDashboardFragment;
public class WriteSystemSettingsPreferenceController extends AppInfoPreferenceControllerBase {
diff --git a/src/com/android/settings/applications/instantapps/InstantAppButtonsController.java b/src/com/android/settings/applications/instantapps/InstantAppButtonsController.java
index 28e612c..42474a8 100644
--- a/src/com/android/settings/applications/instantapps/InstantAppButtonsController.java
+++ b/src/com/android/settings/applications/instantapps/InstantAppButtonsController.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.view.View;
import android.widget.Button;
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 067e167..7371294 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -93,7 +93,7 @@
import com.android.settings.applications.InstalledAppDetails;
import com.android.settings.applications.NotificationApps;
import com.android.settings.applications.UsageAccessDetails;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.WriteSettingsDetails;
diff --git a/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java b/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java
index afc13b4..7a7530c 100644
--- a/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java
+++ b/src/com/android/settings/backup/BackupSettingsActivityPreferenceController.java
@@ -22,31 +22,29 @@
import android.support.v7.preference.Preference;
import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
-public class BackupSettingsActivityPreferenceController extends
- AbstractPreferenceController implements PreferenceControllerMixin {
+public class BackupSettingsActivityPreferenceController extends BasePreferenceController {
+ private static final String TAG = "BackupSettingActivityPC";
+
private static final String KEY_BACKUP_SETTINGS = "backup_settings";
- private static final String TAG = "BackupSettingActivityPC" ;
private final UserManager mUm;
private final BackupManager mBackupManager;
public BackupSettingsActivityPreferenceController(Context context) {
- super(context);
+ super(context, KEY_BACKUP_SETTINGS);
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
mBackupManager = new BackupManager(context);
}
@Override
- public boolean isAvailable() {
- return mUm.isAdminUser();
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_BACKUP_SETTINGS;
+ public int getAvailabilityStatus() {
+ return mUm.isAdminUser()
+ ? AVAILABLE
+ : DISABLED_UNSUPPORTED;
}
@Override
@@ -57,4 +55,4 @@
? R.string.accessibility_feature_state_on
: R.string.accessibility_feature_state_off);
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
index 8b07bcb..2d0ce60 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceNamePreferenceController.java
@@ -29,10 +29,9 @@
import android.util.Log;
import com.android.settings.R;
-import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
@@ -41,8 +40,8 @@
/**
* Controller that shows and updates the bluetooth device name
*/
-public class BluetoothDeviceNamePreferenceController extends AbstractPreferenceController
- implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop {
+public class BluetoothDeviceNamePreferenceController extends BasePreferenceController implements
+ LifecycleObserver, OnStart, OnStop {
private static final String TAG = "BluetoothNamePrefCtrl";
public static final String KEY_DEVICE_NAME = "device_name";
@@ -62,12 +61,22 @@
return;
}
mLocalAdapter = mLocalManager.getBluetoothAdapter();
- lifecycle.addObserver(this);
+
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ /**
+ * Constructor exclusively used for Slice.
+ */
+ public BluetoothDeviceNamePreferenceController(Context context) {
+ this(context, (Lifecycle) null);
}
@VisibleForTesting
BluetoothDeviceNamePreferenceController(Context context, LocalBluetoothAdapter localAdapter) {
- super(context);
+ super(context, KEY_DEVICE_NAME);
mLocalAdapter = localAdapter;
}
@@ -89,8 +98,8 @@
}
@Override
- public boolean isAvailable() {
- return mLocalAdapter != null;
+ public int getAvailabilityStatus() {
+ return mLocalAdapter != null ? AVAILABLE : DISABLED_UNSUPPORTED;
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java
index b64da26..69eefcf 100644
--- a/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceController.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
+import android.text.TextUtils;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
@@ -30,29 +31,39 @@
public class BluetoothDeviceRenamePreferenceController extends
BluetoothDeviceNamePreferenceController {
- public static final String PREF_KEY = "bt_rename_device";
-
private final Fragment mFragment;
+ private String mPrefKey;
private MetricsFeatureProvider mMetricsFeatureProvider;
- public BluetoothDeviceRenamePreferenceController(Context context, Fragment fragment,
- Lifecycle lifecycle) {
+ public BluetoothDeviceRenamePreferenceController(Context context, String prefKey,
+ Fragment fragment, Lifecycle lifecycle) {
super(context, lifecycle);
+ mPrefKey = prefKey;
mFragment = fragment;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
}
+ /**
+ * Constructor exclusively used for Slice.
+ */
+ public BluetoothDeviceRenamePreferenceController(Context context, String prefKey) {
+ super(context, (Lifecycle) null);
+ mPrefKey = prefKey;
+ mFragment = null;
+ }
+
@VisibleForTesting
- BluetoothDeviceRenamePreferenceController(Context context, Fragment fragment,
+ BluetoothDeviceRenamePreferenceController(Context context, String prefKey, Fragment fragment,
LocalBluetoothAdapter localAdapter) {
super(context, localAdapter);
+ mPrefKey = prefKey;
mFragment = fragment;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
}
@Override
public String getPreferenceKey() {
- return PREF_KEY;
+ return mPrefKey;
}
@Override
@@ -62,7 +73,7 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
- if (PREF_KEY.equals(preference.getKey())) {
+ if (TextUtils.equals(mPrefKey, preference.getKey()) && mFragment != null) {
mMetricsFeatureProvider.action(mContext,
MetricsProto.MetricsEvent.ACTION_BLUETOOTH_RENAME);
LocalDeviceNameDialogFragment.newInstance()
diff --git a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
index d1492e4..331907b 100644
--- a/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothMasterSwitchPreferenceController.java
@@ -38,6 +38,7 @@
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+//TODO(b/69926683): remove this controller in Android P.
public class BluetoothMasterSwitchPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin, OnSummaryChangeListener, LifecycleObserver, OnResume,
OnPause, OnStart, OnStop {
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
index a9756a6..5e003fe 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
@@ -46,10 +46,9 @@
static final String KEY_AVAIL_DEVICES = "available_devices";
@VisibleForTesting
static final String KEY_FOOTER_PREF = "footer_preference";
+ private static final String KEY_RENAME_DEVICES = "bt_pair_rename_devices";
@VisibleForTesting
- BluetoothDeviceNamePreferenceController mDeviceNamePrefController;
- @VisibleForTesting
BluetoothProgressCategory mAvailableDevicesCategory;
@VisibleForTesting
FooterPreference mFooterPreference;
@@ -195,10 +194,10 @@
@Override
protected List<AbstractPreferenceController> getPreferenceControllers(Context context) {
- List<AbstractPreferenceController> controllers = new ArrayList<>();
- mDeviceNamePrefController = new BluetoothDeviceNamePreferenceController(context,
- getLifecycle());
- controllers.add(mDeviceNamePrefController);
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(
+ new BluetoothDeviceRenamePreferenceController(context, KEY_RENAME_DEVICES, this,
+ getLifecycle()));
return controllers;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothSettings.java b/src/com/android/settings/bluetooth/BluetoothSettings.java
index 72d8023..3acd477 100644
--- a/src/com/android/settings/bluetooth/BluetoothSettings.java
+++ b/src/com/android/settings/bluetooth/BluetoothSettings.java
@@ -73,6 +73,7 @@
static final String KEY_PAIRED_DEVICES = "paired_devices";
@VisibleForTesting
static final String KEY_FOOTER_PREF = "footer_preference";
+ private static final String KEY_RENAME_DEVICES = "bt_rename_device";
@VisibleForTesting
PreferenceGroup mPairedDevicesCategory;
@@ -369,7 +370,9 @@
controllers.add(mDeviceNamePrefController);
controllers.add(mPairingPrefController);
controllers.add(new BluetoothFilesPreferenceController(context));
- controllers.add(new BluetoothDeviceRenamePreferenceController(context, this, lifecycle));
+ controllers.add(
+ new BluetoothDeviceRenamePreferenceController(context, KEY_RENAME_DEVICES, this,
+ lifecycle));
return controllers;
}
diff --git a/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java
new file mode 100644
index 0000000..3482ee2
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceController.java
@@ -0,0 +1,161 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.SwitchWidgetController;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+/**
+ * PreferenceController to update of bluetooth {@link SwitchPreference}. It will
+ *
+ * 1. Invoke the user toggle
+ * 2. Listen to the update from {@link LocalBluetoothManager}
+ */
+public class BluetoothSwitchPreferenceController extends TogglePreferenceController
+ implements LifecycleObserver, OnStart, OnStop {
+
+ public static final String KEY_TOGGLE_BLUETOOTH = "toggle_bluetooth_switch";
+
+ private LocalBluetoothManager mBluetoothManager;
+ private SwitchPreference mBtPreference;
+ private BluetoothEnabler mBluetoothEnabler;
+ private RestrictionUtils mRestrictionUtils;
+ @VisibleForTesting
+ LocalBluetoothAdapter mBluetoothAdapter;
+
+ public BluetoothSwitchPreferenceController(Context context) {
+ this(context, Utils.getLocalBtManager(context), new RestrictionUtils());
+ }
+
+ @VisibleForTesting
+ public BluetoothSwitchPreferenceController(Context context,
+ LocalBluetoothManager bluetoothManager, RestrictionUtils restrictionUtils) {
+ super(context, KEY_TOGGLE_BLUETOOTH);
+ mBluetoothManager = bluetoothManager;
+ mRestrictionUtils = restrictionUtils;
+
+ if (mBluetoothManager != null) {
+ mBluetoothAdapter = mBluetoothManager.getBluetoothAdapter();
+ }
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mBtPreference = (SwitchPreference) screen.findPreference(KEY_TOGGLE_BLUETOOTH);
+ mBluetoothEnabler = new BluetoothEnabler(mContext,
+ new SwitchController(mBtPreference),
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(), mBluetoothManager,
+ MetricsEvent.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE,
+ mRestrictionUtils);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mBluetoothAdapter != null ? AVAILABLE : DISABLED_UNSUPPORTED;
+ }
+
+ @Override
+ public void onStart() {
+ mBluetoothEnabler.resume(mContext);
+ }
+
+ @Override
+ public void onStop() {
+ mBluetoothEnabler.pause();
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mBluetoothAdapter != null ? mBluetoothAdapter.isEnabled() : false;
+ }
+
+ @Override
+ public void setChecked(boolean isChecked) {
+ if (mBluetoothAdapter != null) {
+ mBluetoothAdapter.setBluetoothEnabled(isChecked);
+ }
+ }
+
+ /**
+ * Control the switch inside {@link SwitchPreference}
+ */
+ @VisibleForTesting
+ class SwitchController extends SwitchWidgetController implements
+ Preference.OnPreferenceChangeListener {
+ private SwitchPreference mSwitchPreference;
+
+ public SwitchController(SwitchPreference switchPreference) {
+ mSwitchPreference = switchPreference;
+ }
+
+ @Override
+ public void updateTitle(boolean isChecked) {
+ }
+
+ @Override
+ public void startListening() {
+ mSwitchPreference.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void stopListening() {
+ mSwitchPreference.setOnPreferenceChangeListener(null);
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ mSwitchPreference.setChecked(checked);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return mSwitchPreference.isChecked();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mSwitchPreference.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mListener != null) {
+ return mListener.onSwitchToggled((Boolean) newValue);
+ }
+ return false;
+ }
+
+ @Override
+ public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ mBtPreference.setEnabled(admin == null);
+ }
+ }
+}
diff --git a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
old mode 100755
new mode 100644
index eae2f29..d13a85f
--- a/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
+++ b/src/com/android/settings/bluetooth/DeviceProfilesSettings.java
@@ -172,7 +172,11 @@
mProfileContainer.removeAllViews();
for (LocalBluetoothProfile profile : mCachedDevice.getConnectableProfiles()) {
CheckBox pref = createProfilePreference(profile);
- mProfileContainer.addView(pref);
+ // MAP and PBAP profiles would be added based on permission access
+ if (!((profile instanceof PbapServerProfile) ||
+ (profile instanceof MapProfile))) {
+ mProfileContainer.addView(pref);
+ }
if (profile instanceof A2dpProfile) {
BluetoothDevice device = mCachedDevice.getDevice();
@@ -191,6 +195,7 @@
}
final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
+ Log.d(TAG, "addPreferencesForProfiles: pbapPermission = " + pbapPermission);
// Only provide PBAP cabability if the client device has requested PBAP.
if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
final PbapServerProfile psp = mManager.getProfileManager().getPbapProfile();
@@ -200,6 +205,7 @@
final MapProfile mapProfile = mManager.getProfileManager().getMapProfile();
final int mapPermission = mCachedDevice.getMessagePermissionChoice();
+ Log.d(TAG, "addPreferencesForProfiles: mapPermission = " + mapPermission);
if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN) {
CheckBox mapPreference = createProfilePreference(mapProfile);
mProfileContainer.addView(mapPreference);
@@ -251,15 +257,6 @@
private void onProfileClicked(LocalBluetoothProfile profile, CheckBox profilePref) {
BluetoothDevice device = mCachedDevice.getDevice();
- if (KEY_PBAP_SERVER.equals(profilePref.getTag())) {
- final int newPermission = mCachedDevice.getPhonebookPermissionChoice()
- == CachedBluetoothDevice.ACCESS_ALLOWED ? CachedBluetoothDevice.ACCESS_REJECTED
- : CachedBluetoothDevice.ACCESS_ALLOWED;
- mCachedDevice.setPhonebookPermissionChoice(newPermission);
- profilePref.setChecked(newPermission == CachedBluetoothDevice.ACCESS_ALLOWED);
- return;
- }
-
if (!profilePref.isChecked()) {
// Recheck it, until the dialog is done.
profilePref.setChecked(true);
@@ -268,6 +265,12 @@
if (profile instanceof MapProfile) {
mCachedDevice.setMessagePermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
}
+ if (profile instanceof PbapServerProfile) {
+ mCachedDevice.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_ALLOWED);
+ refreshProfilePreference(profilePref, profile);
+ // PBAP server is not preffered profile and cannot initiate connection, so return
+ return;
+ }
if (profile.isPreferred(device)) {
// profile is preferred but not connected: disable auto-connect
if (profile instanceof PanProfile) {
@@ -301,10 +304,17 @@
DialogInterface.OnClickListener disconnectListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
- device.disconnect(profile);
- profile.setPreferred(device.getDevice(), false);
- if (profile instanceof MapProfile) {
- device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
+
+ // Disconnect only when user has selected OK otherwise ignore
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ device.disconnect(profile);
+ profile.setPreferred(device.getDevice(), false);
+ if (profile instanceof MapProfile) {
+ device.setMessagePermissionChoice(BluetoothDevice.ACCESS_REJECTED);
+ }
+ if (profile instanceof PbapServerProfile) {
+ device.setPhonebookPermissionChoice(BluetoothDevice.ACCESS_REJECTED);
+ }
}
refreshProfilePreference(findProfile(profile.toString()), profile);
}
@@ -342,6 +352,19 @@
for (LocalBluetoothProfile profile : mCachedDevice.getRemovedProfiles()) {
CheckBox profilePref = findProfile(profile.toString());
if (profilePref != null) {
+
+ if (profile instanceof PbapServerProfile) {
+ final int pbapPermission = mCachedDevice.getPhonebookPermissionChoice();
+ Log.d(TAG, "refreshProfiles: pbapPermission = " + pbapPermission);
+ if (pbapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN)
+ continue;
+ }
+ if (profile instanceof MapProfile) {
+ final int mapPermission = mCachedDevice.getMessagePermissionChoice();
+ Log.d(TAG, "refreshProfiles: mapPermission = " + mapPermission);
+ if (mapPermission != CachedBluetoothDevice.ACCESS_UNKNOWN)
+ continue;
+ }
Log.d(TAG, "Removing " + profile.toString() + " from profile list");
mProfileContainer.removeView(profilePref);
}
diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
index ea93fef..a4f6e5c 100644
--- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
@@ -24,6 +24,7 @@
import com.android.settings.SettingsActivity;
import com.android.settings.bluetooth.BluetoothFilesPreferenceController;
import com.android.settings.bluetooth.BluetoothMasterSwitchPreferenceController;
+import com.android.settings.bluetooth.BluetoothSwitchPreferenceController;
import com.android.settings.bluetooth.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.UsbBackend;
@@ -83,10 +84,8 @@
mUsbPrefController = new UsbModePreferenceController(context, new UsbBackend(context));
lifecycle.addObserver(mUsbPrefController);
controllers.add(mUsbPrefController);
- final BluetoothMasterSwitchPreferenceController bluetoothPreferenceController =
- new BluetoothMasterSwitchPreferenceController(
- context, Utils.getLocalBtManager(context), this,
- (SettingsActivity) getActivity());
+ final BluetoothSwitchPreferenceController bluetoothPreferenceController =
+ new BluetoothSwitchPreferenceController(context);
lifecycle.addObserver(bluetoothPreferenceController);
controllers.add(bluetoothPreferenceController);
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
index a0b5cb8..3cccc15 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
@@ -19,6 +19,7 @@
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
+
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
@@ -42,26 +43,31 @@
@VisibleForTesting
PreferenceGroup mPreferenceGroup;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
+ private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
public ConnectedDeviceGroupController(DashboardFragment fragment, Lifecycle lifecycle) {
super(fragment.getContext());
- init(lifecycle, new ConnectedBluetoothDeviceUpdater(fragment, this));
+ init(lifecycle, new ConnectedBluetoothDeviceUpdater(fragment, this),
+ new ConnectedUsbDeviceUpdater(fragment.getContext(), this));
}
@VisibleForTesting
ConnectedDeviceGroupController(DashboardFragment fragment, Lifecycle lifecycle,
- BluetoothDeviceUpdater bluetoothDeviceUpdater) {
+ BluetoothDeviceUpdater bluetoothDeviceUpdater,
+ ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater) {
super(fragment.getContext());
- init(lifecycle, bluetoothDeviceUpdater);
+ init(lifecycle, bluetoothDeviceUpdater, connectedUsbDeviceUpdater);
}
@Override
public void onStart() {
mBluetoothDeviceUpdater.registerCallback();
+ mConnectedUsbDeviceUpdater.registerCallback();
}
@Override
public void onStop() {
+ mConnectedUsbDeviceUpdater.unregisterCallback();
mBluetoothDeviceUpdater.unregisterCallback();
}
@@ -70,8 +76,10 @@
super.displayPreference(screen);
mPreferenceGroup = (PreferenceGroup) screen.findPreference(KEY);
mPreferenceGroup.setVisible(false);
+
mBluetoothDeviceUpdater.setPrefContext(screen.getContext());
mBluetoothDeviceUpdater.forceUpdate();
+ mConnectedUsbDeviceUpdater.initUsbPreference(screen.getContext());
}
@Override
@@ -100,10 +108,12 @@
}
}
- private void init(Lifecycle lifecycle, BluetoothDeviceUpdater bluetoothDeviceUpdater) {
+ private void init(Lifecycle lifecycle, BluetoothDeviceUpdater bluetoothDeviceUpdater,
+ ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater) {
if (lifecycle != null) {
lifecycle.addObserver(this);
}
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
+ mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
}
}
diff --git a/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java b/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java
new file mode 100644
index 0000000..0468b0f
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdater.java
@@ -0,0 +1,92 @@
+/*
+ * 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.connecteddevice;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.deviceinfo.UsbBackend;
+import com.android.settings.deviceinfo.UsbModeChooserActivity;
+import com.android.settings.widget.GearPreference;
+
+/**
+ * Controller to maintain connected usb device
+ */
+public class ConnectedUsbDeviceUpdater {
+ private Context mContext;
+ private UsbBackend mUsbBackend;
+ private DevicePreferenceCallback mDevicePreferenceCallback;
+ @VisibleForTesting
+ GearPreference mUsbPreference;
+ @VisibleForTesting
+ UsbConnectionBroadcastReceiver mUsbReceiver;
+
+ private UsbConnectionBroadcastReceiver.UsbConnectionListener mUsbConnectionListener =
+ (connected) -> {
+ if (connected) {
+ mUsbPreference.setSummary(
+ UsbModePreferenceController.getSummary(mUsbBackend.getCurrentMode()));
+ mDevicePreferenceCallback.onDeviceAdded(mUsbPreference);
+ } else {
+ mDevicePreferenceCallback.onDeviceRemoved(mUsbPreference);
+ }
+ };
+
+ public ConnectedUsbDeviceUpdater(Context context,
+ DevicePreferenceCallback devicePreferenceCallback) {
+ this(context, devicePreferenceCallback, new UsbBackend(context));
+ }
+
+ @VisibleForTesting
+ ConnectedUsbDeviceUpdater(Context context, DevicePreferenceCallback devicePreferenceCallback,
+ UsbBackend usbBackend) {
+ mContext = context;
+ mDevicePreferenceCallback = devicePreferenceCallback;
+ mUsbBackend = usbBackend;
+ mUsbReceiver = new UsbConnectionBroadcastReceiver(context, mUsbConnectionListener);
+ }
+
+ public void registerCallback() {
+ // This method could handle multiple register
+ mUsbReceiver.register();
+ }
+
+ public void unregisterCallback() {
+ mUsbReceiver.unregister();
+ }
+
+ public void initUsbPreference(Context context) {
+ mUsbPreference = new GearPreference(context, null /* AttributeSet */);
+ mUsbPreference.setTitle(R.string.usb_pref);
+ mUsbPreference.setIcon(R.drawable.ic_usb);
+ mUsbPreference.setSelectable(false);
+ mUsbPreference.setOnGearClickListener((GearPreference p) -> {
+ final Intent intent = new Intent(mContext, UsbModeChooserActivity.class);
+ mContext.startActivity(intent);
+ });
+
+ forceUpdate();
+ }
+
+ private void forceUpdate() {
+ // Register so we can get the connection state from sticky intent.
+ //TODO(b/70336520): Use an API to get data instead of sticky intent
+ mUsbReceiver.register();
+ mUsbConnectionListener.onUsbConnectionChanged(mUsbReceiver.isConnected());
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java b/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java
new file mode 100644
index 0000000..07a7691
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiver.java
@@ -0,0 +1,76 @@
+/*
+ * 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.connecteddevice;
+
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+
+/**
+ * Receiver to receive usb update and use {@link UsbConnectionListener} to invoke callback
+ */
+public class UsbConnectionBroadcastReceiver extends BroadcastReceiver {
+ private Context mContext;
+ private UsbConnectionListener mUsbConnectionListener;
+ private boolean mListeningToUsbEvents;
+ private boolean mConnected;
+
+ public UsbConnectionBroadcastReceiver(Context context,
+ UsbConnectionListener usbConnectionListener) {
+ mContext = context;
+ mUsbConnectionListener = usbConnectionListener;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mConnected = intent != null
+ && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
+ if (mUsbConnectionListener != null) {
+ mUsbConnectionListener.onUsbConnectionChanged(mConnected);
+ }
+ }
+
+ public void register() {
+ if (!mListeningToUsbEvents) {
+ final IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE);
+ final Intent intent = mContext.registerReceiver(this, intentFilter);
+ mConnected = intent != null
+ && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
+ mListeningToUsbEvents = true;
+ }
+ }
+
+ public void unregister() {
+ if (mListeningToUsbEvents) {
+ mContext.unregisterReceiver(this);
+ mListeningToUsbEvents = false;
+ }
+ }
+
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when usb connection is changed.
+ */
+ interface UsbConnectionListener {
+ void onUsbConnectionChanged(boolean connected);
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java b/src/com/android/settings/connecteddevice/UsbModePreferenceController.java
index a6cb9be..8693520 100644
--- a/src/com/android/settings/connecteddevice/UsbModePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/UsbModePreferenceController.java
@@ -15,17 +15,12 @@
*/
package com.android.settings.connecteddevice;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
-import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
-import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.R;
+import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.deviceinfo.UsbBackend;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -44,19 +39,21 @@
public UsbModePreferenceController(Context context, UsbBackend usbBackend) {
super(context);
mUsbBackend = usbBackend;
- mUsbReceiver = new UsbConnectionBroadcastReceiver();
+ mUsbReceiver = new UsbConnectionBroadcastReceiver(mContext, (connected) -> {
+ updateSummary(mUsbPreference);
+ });
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mUsbPreference = screen.findPreference(KEY_USB_MODE);
- updataSummary(mUsbPreference);
+ updateSummary(mUsbPreference);
}
@Override
public void updateState(Preference preference) {
- updataSummary(preference);
+ updateSummary(preference);
}
@Override
@@ -79,8 +76,7 @@
mUsbReceiver.register();
}
- @VisibleForTesting
- int getSummary(int mode) {
+ public static int getSummary(int mode) {
switch (mode) {
case UsbBackend.MODE_POWER_SINK | UsbBackend.MODE_DATA_NONE:
return R.string.usb_summary_charging_only;
@@ -96,11 +92,11 @@
return 0;
}
- private void updataSummary(Preference preference) {
- updataSummary(preference, mUsbBackend.getCurrentMode());
+ private void updateSummary(Preference preference) {
+ updateSummary(preference, mUsbBackend.getCurrentMode());
}
- private void updataSummary(Preference preference, int mode) {
+ private void updateSummary(Preference preference, int mode) {
if (preference != null) {
if (mUsbReceiver.isConnected()) {
preference.setEnabled(true);
@@ -112,40 +108,4 @@
}
}
- private class UsbConnectionBroadcastReceiver extends BroadcastReceiver {
- private boolean mListeningToUsbEvents;
- private boolean mConnected;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- boolean connected = intent != null
- && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
- if (connected != mConnected) {
- mConnected = connected;
- updataSummary(mUsbPreference);
- }
- }
-
- public void register() {
- if (!mListeningToUsbEvents) {
- IntentFilter intentFilter = new IntentFilter(UsbManager.ACTION_USB_STATE);
- Intent intent = mContext.registerReceiver(this, intentFilter);
- mConnected = intent != null
- && intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
- mListeningToUsbEvents = true;
- }
- }
-
- public void unregister() {
- if (mListeningToUsbEvents) {
- mContext.unregisterReceiver(this);
- mListeningToUsbEvents = false;
- }
- }
-
- public boolean isConnected() {
- return mConnected;
- }
- }
-
}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index acb20d5..b7c73f3 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -47,7 +47,7 @@
import com.android.settings.applications.ProcessStatsUi;
import com.android.settings.applications.UsageAccessDetails;
import com.android.settings.applications.VrListenerSettings;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.applications.appinfo.DrawOverlayDetails;
import com.android.settings.applications.appinfo.ExternalSourcesDetails;
import com.android.settings.applications.appinfo.PictureInPictureDetails;
@@ -265,6 +265,7 @@
Settings.SoundSettingsActivity.class.getName(),
Settings.StorageDashboardActivity.class.getName(),
Settings.PowerUsageSummaryActivity.class.getName(),
+ Settings.PowerUsageSummaryLegacyActivity.class.getName(),
Settings.UserAndAccountDashboardActivity.class.getName(),
Settings.SecuritySettingsActivity.class.getName(),
Settings.AccessibilitySettingsActivity.class.getName(),
diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
index 5b55ada..e8a7bbf 100644
--- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java
+++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
@@ -32,7 +32,7 @@
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.AppStateBaseBridge;
import com.android.settings.applications.InstalledAppDetails;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
import com.android.settings.overlay.FeatureFactory;
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 8f114fc..e736798 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -446,6 +446,7 @@
controllers.add(new ResizableActivityPreferenceController(context));
controllers.add(new FreeformWindowsPreferenceController(context));
controllers.add(new ShortcutManagerThrottlingPreferenceController(context));
+ controllers.add(new EnableGnssRawMeasFullTrackingPreferenceController(context));
return controllers;
}
diff --git a/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceController.java b/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceController.java
new file mode 100644
index 0000000..09770f6
--- /dev/null
+++ b/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * 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.development;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+public class EnableGnssRawMeasFullTrackingPreferenceController extends
+ DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+ PreferenceControllerMixin {
+
+ private static final String ENABLE_GNSS_RAW_MEAS_FULL_TRACKING_KEY =
+ "enable_gnss_raw_meas_full_tracking";
+
+ static final int SETTING_VALUE_ON = 1;
+ static final int SETTING_VALUE_OFF = 0;
+
+ private SwitchPreference mPreference;
+
+ public EnableGnssRawMeasFullTrackingPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return ENABLE_GNSS_RAW_MEAS_FULL_TRACKING_KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mPreference = (SwitchPreference) screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean isEnabled = (Boolean) newValue;
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
+ isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final int enableGnssRawMeasFullTrackingMode =
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, SETTING_VALUE_OFF);
+ mPreference.setChecked(enableGnssRawMeasFullTrackingMode != SETTING_VALUE_OFF);
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ mPreference.setEnabled(true);
+ }
+
+ @Override
+ protected void onDeveloperOptionsSwitchDisabled() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, SETTING_VALUE_OFF);
+ mPreference.setEnabled(false);
+ mPreference.setChecked(false);
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java b/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java
index 06bdb3f..f91ed4e 100644
--- a/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java
+++ b/src/com/android/settings/deviceinfo/AdditionalSystemUpdatePreferenceController.java
@@ -17,26 +17,23 @@
import android.content.Context;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
-public class AdditionalSystemUpdatePreferenceController extends
- AbstractPreferenceController implements PreferenceControllerMixin {
+public class AdditionalSystemUpdatePreferenceController extends BasePreferenceController {
private static final String KEY_UPDATE_SETTING = "additional_system_update_settings";
public AdditionalSystemUpdatePreferenceController(Context context) {
- super(context);
+ super(context, KEY_UPDATE_SETTING);
}
@Override
- public boolean isAvailable() {
+ public int getAvailabilityStatus() {
return mContext.getResources().getBoolean(
- com.android.settings.R.bool.config_additional_system_update_setting_enable);
+ com.android.settings.R.bool.config_additional_system_update_setting_enable)
+ ? AVAILABLE
+ : DISABLED_UNSUPPORTED;
}
-
- @Override
- public String getPreferenceKey() {
- return KEY_UPDATE_SETTING;
- }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java b/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java
index d8a64a8..92c33d8 100644
--- a/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java
+++ b/src/com/android/settings/deviceinfo/SystemUpdatePreferenceController.java
@@ -30,11 +30,9 @@
import com.android.settings.R;
import com.android.settings.Utils;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settings.core.BasePreferenceController;
-public class SystemUpdatePreferenceController extends AbstractPreferenceController implements
- PreferenceControllerMixin {
+public class SystemUpdatePreferenceController extends BasePreferenceController {
private static final String TAG = "SysUpdatePrefContr";
@@ -42,19 +40,16 @@
private final UserManager mUm;
- public SystemUpdatePreferenceController(Context context, UserManager um) {
- super(context);
- mUm = um;
+ public SystemUpdatePreferenceController(Context context) {
+ super(context, KEY_SYSTEM_UPDATE_SETTINGS);
+ mUm = UserManager.get(context);
}
@Override
- public boolean isAvailable() {
- return mUm.isAdminUser();
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_SYSTEM_UPDATE_SETTINGS;
+ public int getAvailabilityStatus() {
+ return mUm.isAdminUser()
+ ? AVAILABLE
+ : DISABLED_UNSUPPORTED;
}
@Override
@@ -62,14 +57,14 @@
super.displayPreference(screen);
if (isAvailable()) {
Utils.updatePreferenceToSpecificActivityOrRemove(mContext, screen,
- KEY_SYSTEM_UPDATE_SETTINGS,
+ getPreferenceKey(),
Utils.UPDATE_PREFERENCE_FLAG_SET_TITLE_TO_MATCHING_ACTIVITY);
}
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
- if (KEY_SYSTEM_UPDATE_SETTINGS.equals(preference.getKey())) {
+ if (TextUtils.equals(getPreferenceKey(), preference.getKey())) {
CarrierConfigManager configManager =
(CarrierConfigManager) mContext.getSystemService(CARRIER_CONFIG_SERVICE);
PersistableBundle b = configManager.getConfig();
@@ -108,4 +103,4 @@
mContext.getApplicationContext().sendBroadcast(intent);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
index 15ca87b..35b8bd1 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogController.java
@@ -68,6 +68,8 @@
@VisibleForTesting
final static int SERVICE_STATE_VALUE_ID = R.id.service_state_value;
@VisibleForTesting
+ final static int SIGNAL_STRENGTH_LABEL_ID = R.id.signal_strength_label;
+ @VisibleForTesting
final static int SIGNAL_STRENGTH_VALUE_ID = R.id.signal_strength_value;
@VisibleForTesting
final static int CELLULAR_NETWORK_TYPE_VALUE_ID = R.id.network_type_value;
@@ -262,6 +264,21 @@
}
private void updateSignalStrength(SignalStrength signalStrength) {
+ final int subscriptionId = mSubscriptionInfo.getSubscriptionId();
+ final PersistableBundle carrierConfig =
+ mCarrierConfigManager.getConfigForSubId(subscriptionId);
+ // by default we show the signal strength
+ boolean showSignalStrength = true;
+ if (carrierConfig != null) {
+ showSignalStrength = carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL);
+ }
+ if (!showSignalStrength) {
+ mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_LABEL_ID);
+ mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_VALUE_ID);
+ return;
+ }
+
final int state = getCurrentServiceState().getState();
if ((ServiceState.STATE_OUT_OF_SERVICE == state) ||
@@ -327,9 +344,14 @@
private void updateIccidNumber() {
final int subscriptionId = mSubscriptionInfo.getSubscriptionId();
- final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subscriptionId);
- final boolean showIccId = carrierConfig.getBoolean(
- CarrierConfigManager.KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL);
+ final PersistableBundle carrierConfig =
+ mCarrierConfigManager.getConfigForSubId(subscriptionId);
+ // do not show iccid by default
+ boolean showIccId = false;
+ if (carrierConfig != null) {
+ showIccId = carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL);
+ }
if (!showIccId) {
mDialog.removeSettingFromScreen(ICCID_INFO_LABEL_ID);
mDialog.removeSettingFromScreen(ICCID_INFO_VALUE_ID);
diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
index f7a2b9a..d0f4080 100644
--- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
@@ -20,16 +20,18 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.PowerManager;
import android.support.annotation.VisibleForTesting;
import com.android.settings.Utils;
/**
* Use this broadcastReceiver to listen to the battery change, and it will invoke
- * {@link OnBatteryChangedListener} if any of the following happens:
+ * {@link OnBatteryChangedListener} if any of the followings has been changed:
*
- * 1. Battery level has been changed
- * 2. Battery status has been changed
+ * 1. Battery level(e.g. 100%->99%)
+ * 2. Battery status(e.g. plugged->unplugged)
+ * 3. Battery saver(e.g. off->on)
*/
public class BatteryBroadcastReceiver extends BroadcastReceiver {
@@ -58,8 +60,11 @@
}
public void register() {
- final Intent intent = mContext.registerReceiver(this,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+
+ final Intent intent = mContext.registerReceiver(this, intentFilter);
updateBatteryStatus(intent, true /* forceUpdate */);
}
@@ -68,15 +73,18 @@
}
private void updateBatteryStatus(Intent intent, boolean forceUpdate) {
- if (intent != null && mBatteryListener != null && Intent.ACTION_BATTERY_CHANGED.equals(
- intent.getAction())) {
- String batteryLevel = Utils.getBatteryPercentage(intent);
- String batteryStatus = Utils.getBatteryStatus(
- mContext.getResources(), intent);
- if (forceUpdate || !batteryLevel.equals(mBatteryLevel) || !batteryStatus.equals(
- mBatteryStatus)) {
- mBatteryLevel = batteryLevel;
- mBatteryStatus = batteryStatus;
+ if (intent != null && mBatteryListener != null) {
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ final String batteryLevel = Utils.getBatteryPercentage(intent);
+ final String batteryStatus = Utils.getBatteryStatus(
+ mContext.getResources(), intent);
+ if (forceUpdate || !batteryLevel.equals(mBatteryLevel) || !batteryStatus.equals(
+ mBatteryStatus)) {
+ mBatteryLevel = batteryLevel;
+ mBatteryStatus = batteryStatus;
+ mBatteryListener.onBatteryChanged();
+ }
+ } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
mBatteryListener.onBatteryChanged();
}
}
diff --git a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
index d1b47b2..819b128 100644
--- a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
+++ b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java
@@ -23,27 +23,26 @@
import com.android.internal.hardware.AmbientDisplayConfiguration;
import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import java.util.List;
-public class GesturesSettingPreferenceController extends AbstractPreferenceController
- implements PreferenceControllerMixin {
-
- private static final String KEY_GESTURES_SETTINGS = "gesture_settings";
-
+public class GesturesSettingPreferenceController extends BasePreferenceController {
private final AssistGestureFeatureProvider mFeatureProvider;
private List<AbstractPreferenceController> mGestureControllers;
+ private static final String KEY_GESTURES_SETTINGS = "gesture_settings";
+
public GesturesSettingPreferenceController(Context context) {
- super(context);
+ super(context, KEY_GESTURES_SETTINGS);
mFeatureProvider = FeatureFactory.getFactory(context).getAssistGestureFeatureProvider();
}
@Override
- public boolean isAvailable() {
+ public int getAvailabilityStatus() {
if (mGestureControllers == null) {
mGestureControllers = GestureSettings.buildPreferenceControllers(mContext,
null /* lifecycle */, new AmbientDisplayConfiguration(mContext));
@@ -52,12 +51,9 @@
for (AbstractPreferenceController controller : mGestureControllers) {
isAvailable = isAvailable || controller.isAvailable();
}
- return isAvailable;
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_GESTURES_SETTINGS;
+ return isAvailable
+ ? AVAILABLE
+ : DISABLED_UNSUPPORTED;
}
@Override
@@ -83,5 +79,4 @@
}
preference.setSummary(summary);
}
-
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/location/LocationMode.java b/src/com/android/settings/location/LocationMode.java
index 34f082b..5931f9e 100644
--- a/src/com/android/settings/location/LocationMode.java
+++ b/src/com/android/settings/location/LocationMode.java
@@ -95,6 +95,11 @@
}
@Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return context.getResources().getBoolean(R.bool.config_location_mode_available);
+ }
+
+ @Override
public List<AbstractPreferenceController> getPreferenceControllers(Context
context) {
return buildPreferenceControllers(context, null /* lifecycle */);
diff --git a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java
index 8c4fa579..461f6e3 100644
--- a/src/com/android/settings/location/RecentLocationRequestPreferenceController.java
+++ b/src/com/android/settings/location/RecentLocationRequestPreferenceController.java
@@ -25,7 +25,7 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.applications.InstalledAppDetails;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.widget.AppPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java
index f8408fc..1ee20d3 100644
--- a/src/com/android/settings/notification/ZenModeSettings.java
+++ b/src/com/android/settings/notification/ZenModeSettings.java
@@ -97,23 +97,21 @@
enabledCategories = getEnabledCategories(policy);
}
+ // no sound categories can bypass dnd
int numCategories = enabledCategories.size();
if (numCategories == 0) {
- return mContext.getString(R.string.zen_mode_behavior_no_sound);
+ return mContext.getString(R.string.zen_mode_behavior_total_silence);
}
- String s = enabledCategories.get(0).toLowerCase();
- for (int i = 1; i < numCategories; i++) {
- if (i == numCategories - 1) {
- s = mContext.getString(R.string.join_many_items_last,
- s, enabledCategories.get(i).toLowerCase());
- } else {
- s = mContext.getString(R.string.join_many_items_middle,
- s, enabledCategories.get(i).toLowerCase());
- }
+ // only alarms and media/system can bypass dnd
+ if (numCategories == 2 &&
+ isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_ALARMS) &&
+ isCategoryEnabled(policy, Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER)) {
+ return mContext.getString(R.string.zen_mode_behavior_alarms_only);
}
- return mContext.getString(R.string.zen_mode_behavior_no_sound_except, s);
+ // custom
+ return mContext.getString(R.string.zen_mode_behavior_summary_custom);
}
String getAutomaticRulesSummary() {
diff --git a/src/com/android/settings/search/DatabaseIndexingUtils.java b/src/com/android/settings/search/DatabaseIndexingUtils.java
index 207d09f..94ec650 100644
--- a/src/com/android/settings/search/DatabaseIndexingUtils.java
+++ b/src/com/android/settings/search/DatabaseIndexingUtils.java
@@ -43,7 +43,7 @@
private static final String TAG = "IndexingUtil";
- private static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
+ public static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
"SEARCH_INDEX_DATA_PROVIDER";
/**
diff --git a/src/com/android/settings/search/XmlParserUtils.java b/src/com/android/settings/search/XmlParserUtils.java
index b4ffc53..27c5cd3 100644
--- a/src/com/android/settings/search/XmlParserUtils.java
+++ b/src/com/android/settings/search/XmlParserUtils.java
@@ -71,6 +71,10 @@
return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
}
+ public static String getController(Context context, AttributeSet attrs) {
+ return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_controller);
+ }
+
public static int getDataIcon(Context context, AttributeSet attrs) {
final TypedArray ta = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Preference);
diff --git a/src/com/android/settings/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
similarity index 67%
rename from src/com/android/settings/SettingsSliceProvider.java
rename to src/com/android/settings/slices/SettingsSliceProvider.java
index 845dacd..22035d2 100644
--- a/src/com/android/settings/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -14,28 +14,31 @@
* limitations under the License
*/
-package com.android.settings;
+package com.android.settings.slices;
import android.app.PendingIntent;
-import android.app.slice.Slice;
-import android.app.slice.SliceProvider;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.net.Uri;
-import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import com.android.settings.R;
+
+import androidx.app.slice.Slice;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.builders.ListBuilder;
+
public class SettingsSliceProvider extends SliceProvider {
public static final String SLICE_AUTHORITY = "com.android.settings.slices";
public static final String PATH_WIFI = "wifi";
public static final String ACTION_WIFI_CHANGED =
"com.android.settings.slice.action.WIFI_CHANGED";
- // TODO -- Associate slice URI with search result instead of separate hardcoded thing
- public static final String[] WIFI_SEARCH_TERMS = {"wi-fi", "wifi", "internet"};
+ // TODO -- Associate slice URI with search result instead of separate hardcoded thing
public static Uri getUri(String path) {
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
@@ -44,7 +47,7 @@
}
@Override
- public boolean onCreate() {
+ public boolean onCreateSliceProvider() {
return true;
}
@@ -53,15 +56,15 @@
String path = sliceUri.getPath();
switch (path) {
case "/" + PATH_WIFI:
- return createWifi(sliceUri);
-
+ return createWifiSlice(sliceUri);
}
throw new IllegalArgumentException("Unrecognized slice uri: " + sliceUri);
}
- private Slice createWifi(Uri uri) {
+
+ // TODO (b/70622039) remove this when the proper wifi slice is enabled.
+ private Slice createWifiSlice(Uri sliceUri) {
// Get wifi state
- String[] toggleHints;
WifiManager wifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
int wifiState = wifiManager.getWifiState();
boolean wifiEnabled = false;
@@ -74,7 +77,6 @@
case WifiManager.WIFI_STATE_ENABLED:
case WifiManager.WIFI_STATE_ENABLING:
state = wifiManager.getConnectionInfo().getSSID();
- WifiInfo.removeDoubleQuotes(state);
wifiEnabled = true;
break;
case WifiManager.WIFI_STATE_UNKNOWN:
@@ -82,28 +84,17 @@
state = ""; // just don't show anything?
break;
}
- if (wifiEnabled) {
- toggleHints = new String[] {Slice.HINT_TOGGLE, Slice.HINT_SELECTED};
- } else {
- toggleHints = new String[] {Slice.HINT_TOGGLE};
- }
- // Construct the slice
- Slice.Builder b = new Slice.Builder(uri);
- b.addSubSlice(new Slice.Builder(b)
- .addAction(getIntent("android.settings.WIFI_SETTINGS"),
- new Slice.Builder(b)
- .addText(getContext().getString(R.string.wifi_settings), null)
- .addText(state, null)
- .addIcon(Icon.createWithResource(getContext(),
- R.drawable.ic_settings_wireless), null, Slice.HINT_HIDDEN)
- .addHints(Slice.HINT_TITLE)
- .build())
- .addAction(getBroadcastIntent(ACTION_WIFI_CHANGED),
- new Slice.Builder(b)
- .addHints(toggleHints)
- .build())
- .build());
- return b.build();
+
+ boolean finalWifiEnabled = wifiEnabled;
+ return new ListBuilder(sliceUri)
+ .setColor(R.color.material_blue_500)
+ .add(b -> b
+ .setTitle(getContext().getString(R.string.wifi_settings))
+ .setTitleItem(Icon.createWithResource(getContext(), R.drawable.wifi_signal))
+ .setSubtitle(state)
+ .addToggle(getBroadcastIntent(ACTION_WIFI_CHANGED), finalWifiEnabled)
+ .setContentIntent(getIntent(Intent.ACTION_MAIN)))
+ .build();
}
private PendingIntent getIntent(String action) {
diff --git a/src/com/android/settings/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java
similarity index 85%
rename from src/com/android/settings/SliceBroadcastReceiver.java
rename to src/com/android/settings/slices/SliceBroadcastReceiver.java
index f43e3a3..b6f2ab9 100644
--- a/src/com/android/settings/SliceBroadcastReceiver.java
+++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java
@@ -14,9 +14,9 @@
* limitations under the License
*/
-package com.android.settings;
+package com.android.settings.slices;
-import static com.android.settings.SettingsSliceProvider.ACTION_WIFI_CHANGED;
+import static com.android.settings.slices.SettingsSliceProvider.ACTION_WIFI_CHANGED;
import android.app.slice.Slice;
import android.content.BroadcastReceiver;
@@ -42,8 +42,8 @@
// Wait a bit for wifi to update (TODO: is there a better way to do this?)
Handler h = new Handler();
h.postDelayed(() -> {
- Uri uri = SettingsSliceProvider.getUri(SettingsSliceProvider.PATH_WIFI);
- context.getContentResolver().notifyChange(uri, null);
+ Uri uri = SettingsSliceProvider.getUri(SettingsSliceProvider.PATH_WIFI);
+ context.getContentResolver().notifyChange(uri, null);
}, 1000);
break;
}
diff --git a/src/com/android/settings/slices/SliceData.java b/src/com/android/settings/slices/SliceData.java
new file mode 100644
index 0000000..528f23c
--- /dev/null
+++ b/src/com/android/settings/slices/SliceData.java
@@ -0,0 +1,188 @@
+/*
+ * 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.slices;
+
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * TODO (b/67996923) Add SlicesIndexingManager
+ * Data class representing a slice stored by {@link SlicesIndexingManager}.
+ * Note that {@link #key} is treated as a primary key for this class and determines equality.
+ */
+public class SliceData {
+
+ private final String key;
+
+ private final String title;
+
+ private final String summary;
+
+ private final String screenTitle;
+
+ private final int iconResource;
+
+ private final String fragmentClassName;
+
+ private final Uri uri;
+
+ private final String preferenceController;
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public String getScreenTitle() {
+ return screenTitle;
+ }
+
+ public int getIconResource() {
+ return iconResource;
+ }
+
+ public String getFragmentClassName() {
+ return fragmentClassName;
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ public String getPreferenceController() {
+ return preferenceController;
+ }
+
+ private SliceData(Builder builder) {
+ key = builder.mKey;
+ title = builder.mTitle;
+ summary = builder.mSummary;
+ screenTitle = builder.mScreenTitle;
+ iconResource = builder.mIconResource;
+ fragmentClassName = builder.mFragmentClassName;
+ uri = builder.mUri;
+ preferenceController = builder.mPrefControllerClassName;
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SliceData)) {
+ return false;
+ }
+ SliceData newObject = (SliceData) obj;
+ return TextUtils.equals(key, newObject.key);
+ }
+
+ static class Builder {
+ private String mKey;
+
+ private String mTitle;
+
+ private String mSummary;
+
+ private String mScreenTitle;
+
+ private int mIconResource;
+
+ private String mFragmentClassName;
+
+ private Uri mUri;
+
+ private String mPrefControllerClassName;
+
+ public Builder setKey(String key) {
+ mKey = key;
+ return this;
+ }
+
+ public Builder setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ public Builder setSummary(String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ public Builder setScreenTitle(String screenTitle) {
+ mScreenTitle = screenTitle;
+ return this;
+ }
+
+ public Builder setIcon(int iconResource) {
+ mIconResource = iconResource;
+ return this;
+ }
+
+ public Builder setPreferenceControllerClassName(String controllerClassName) {
+ mPrefControllerClassName = controllerClassName;
+ return this;
+ }
+
+ public Builder setFragmentName(String fragmentClassName) {
+ mFragmentClassName = fragmentClassName;
+ return this;
+ }
+
+ public Builder setUri(Uri uri) {
+ mUri = uri;
+ return this;
+ }
+
+ public SliceData build() {
+ if (TextUtils.isEmpty(mKey)) {
+ throw new IllegalStateException("Key cannot be empty");
+ }
+
+ if (TextUtils.isEmpty(mTitle)) {
+ throw new IllegalStateException("Title cannot be empty");
+ }
+
+ if (TextUtils.isEmpty(mFragmentClassName)) {
+ throw new IllegalStateException("Fragment Name cannot be empty");
+ }
+
+ if (TextUtils.isEmpty(mPrefControllerClassName)) {
+ throw new IllegalStateException("Preference Controller cannot be empty");
+ }
+
+ if (mUri == null) {
+ throw new IllegalStateException("Uri cannot be null");
+ }
+
+ return new SliceData(this);
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/slices/SlicesDatabaseHelper.java b/src/com/android/settings/slices/SlicesDatabaseHelper.java
new file mode 100644
index 0000000..a74fc81
--- /dev/null
+++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java
@@ -0,0 +1,122 @@
+package com.android.settings.slices;
+
+import android.content.Context;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Defines the schema for the Slices database.
+ */
+public class SlicesDatabaseHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = "SlicesDatabaseHelper";
+
+ private static final String DATABASE_NAME = "slices_index.db";
+ private static final String SHARED_PREFS_TAG = "slices_shared_prefs";
+
+ private static final int DATABASE_VERSION = 1;
+
+ public interface Tables {
+ String TABLE_SLICES_INDEX = "slices_index";
+ }
+
+ public interface IndexColumns {
+ /**
+ * Primary key of the DB. Preference key from preference controllers.
+ */
+ String KEY = "key";
+
+ /**
+ * Title of the Setting.
+ */
+ String TITLE = "title";
+
+ /**
+ * Summary / Subtitle for the setting.
+ */
+ String SUBTITLE = "subtitle";
+
+ /**
+ * Title of the Setting screen on which the Setting lives.
+ */
+ String SCREENTITLE = "screentitle";
+
+ /**
+ * Resource ID for the icon of the setting. Should be 0 for no icon.
+ */
+ String ICON_RESOURCE = "icon";
+
+ /**
+ * Classname of the fragment name of the page that hosts the setting.
+ */
+ String FRAGMENT = "fragment";
+
+ /**
+ * Class name of the controller backing the setting. Must be a
+ * {@link com.android.settings.core.BasePreferenceController}.
+ */
+ String CONTROLLER = "controller";
+ }
+
+ private static final String CREATE_SLICES_TABLE =
+ "CREATE VIRTUAL TABLE " + Tables.TABLE_SLICES_INDEX + " USING fts4" +
+ "(" +
+ IndexColumns.KEY +
+ ", " +
+ IndexColumns.TITLE +
+ ", " +
+ IndexColumns.SUBTITLE +
+ ", " +
+ IndexColumns.SCREENTITLE +
+ ", " +
+ IndexColumns.ICON_RESOURCE +
+ ", " +
+ IndexColumns.FRAGMENT +
+ ", " +
+ IndexColumns.CONTROLLER +
+ ");";
+
+ private final Context mContext;
+
+ public SlicesDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null /* CursorFactor */, DATABASE_VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createDatabases(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < DATABASE_VERSION) {
+ Log.d(TAG, "Reconstructing DB from " + oldVersion + "to " + newVersion);
+ reconstruct(db);
+ }
+ }
+
+ @VisibleForTesting
+ void reconstruct(SQLiteDatabase db) {
+ mContext.getSharedPreferences(SHARED_PREFS_TAG, Context.MODE_PRIVATE)
+ .edit()
+ .clear()
+ .commit();
+ dropTables(db);
+ createDatabases(db);
+ }
+
+ private void createDatabases(SQLiteDatabase db) {
+ db.execSQL(CREATE_SLICES_TABLE);
+ Log.d(TAG, "Created databases");
+ }
+
+
+ private void dropTables(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SLICES_INDEX);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java
index c01bfcc..323a2d4 100644
--- a/src/com/android/settings/system/SystemDashboardFragment.java
+++ b/src/com/android/settings/system/SystemDashboardFragment.java
@@ -16,8 +16,12 @@
package com.android.settings.system;
import android.content.Context;
+import android.os.Bundle;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
+import android.support.v7.preference.PreferenceScreen;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
@@ -41,6 +45,17 @@
private static final String KEY_RESET = "reset_dashboard";
@Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final PreferenceScreen screen = getPreferenceScreen();
+ // We do not want to display an advanced button if only one setting is hidden
+ if (getVisiblePreferenceCount(screen) == screen.getInitialExpandedChildrenCount() + 1) {
+ screen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.SETTINGS_SYSTEM_CATEGORY;
}
@@ -67,13 +82,26 @@
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
- controllers.add(new SystemUpdatePreferenceController(context, UserManager.get(context)));
+ controllers.add(new SystemUpdatePreferenceController(context));
controllers.add(new AdditionalSystemUpdatePreferenceController(context));
controllers.add(new BackupSettingsActivityPreferenceController(context));
controllers.add(new GesturesSettingPreferenceController(context));
return controllers;
}
+ private int getVisiblePreferenceCount(PreferenceGroup group) {
+ int visibleCount = 0;
+ for (int i = 0; i < group.getPreferenceCount(); i++) {
+ final Preference preference = group.getPreference(i);
+ if (preference instanceof PreferenceGroup) {
+ visibleCount += getVisiblePreferenceCount((PreferenceGroup) preference);
+ } else if (preference.isVisible()) {
+ visibleCount++;
+ }
+ }
+ return visibleCount;
+ }
+
/**
* For Search.
*/
@@ -88,17 +116,18 @@
}
@Override
- public List<AbstractPreferenceController> getPreferenceControllers(Context context) {
+ public List<AbstractPreferenceController> getPreferenceControllers(
+ Context context) {
return buildPreferenceControllers(context);
}
@Override
public List<String> getNonIndexableKeys(Context context) {
List<String> keys = super.getNonIndexableKeys(context);
- keys.add((new BackupSettingsActivityPreferenceController(context)
- .getPreferenceKey()));
+ keys.add((new BackupSettingsActivityPreferenceController(
+ context).getPreferenceKey()));
keys.add(KEY_RESET);
return keys;
}
};
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/widget/EntityHeaderController.java b/src/com/android/settings/widget/EntityHeaderController.java
index 5fa7586..0d07e67 100644
--- a/src/com/android/settings/widget/EntityHeaderController.java
+++ b/src/com/android/settings/widget/EntityHeaderController.java
@@ -45,7 +45,7 @@
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.InstalledAppDetails;
import com.android.settings.applications.LayoutPreference;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
index 97e5e04..7271884 100644
--- a/tests/robotests/Android.mk
+++ b/tests/robotests/Android.mk
@@ -14,7 +14,7 @@
LOCAL_JAVA_LIBRARIES := \
junit \
- platform-robolectric-3.4.2-prebuilt \
+ platform-robolectric-3.5.1-prebuilt \
telephony-common
LOCAL_INSTRUMENTATION_FOR := Settings
@@ -42,4 +42,4 @@
LOCAL_ROBOTEST_TIMEOUT := 36000
-include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk
+include prebuilts/misc/common/robolectric/3.5.1/run_robotests.mk
diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider
index 6d3ec9a..ebcea43 100644
--- a/tests/robotests/assets/grandfather_not_implementing_index_provider
+++ b/tests/robotests/assets/grandfather_not_implementing_index_provider
@@ -1,4 +1,4 @@
-com.android.settings.applications.AppInfoDashboardFragment
+com.android.settings.applications.appinfo.AppInfoDashboardFragment
com.android.settings.bluetooth.DevicePickerFragment
com.android.settings.bluetooth.BluetoothDeviceDetailsFragment
com.android.settings.bluetooth.BluetoothPairingDetail
diff --git a/tests/robotests/res/xml-mcc999/about_legal.xml b/tests/robotests/res/xml-mcc999/about_legal.xml
index 53a2b89..3e008cb 100644
--- a/tests/robotests/res/xml-mcc999/about_legal.xml
+++ b/tests/robotests/res/xml-mcc999/about_legal.xml
@@ -30,5 +30,6 @@
<Preference
android:key="pref_key_1"
- android:title="bears_bears_bears"/>
+ android:title="bears_bears_bears"
+ settings:controller="mind_flayer"/>
</PreferenceScreen>
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/HelpTrampolineTest.java b/tests/robotests/src/com/android/settings/HelpTrampolineTest.java
index e10b878..a6bcf03 100644
--- a/tests/robotests/src/com/android/settings/HelpTrampolineTest.java
+++ b/tests/robotests/src/com/android/settings/HelpTrampolineTest.java
@@ -50,7 +50,7 @@
final Intent intent = new Intent().setClassName(
RuntimeEnvironment.application.getPackageName(), HelpTrampoline.class.getName());
- Robolectric.buildActivity(HelpTrampoline.class).withIntent(intent).create().get();
+ Robolectric.buildActivity(HelpTrampoline.class, intent).create().get();
assertThat(ShadowHelpUtils.isGetHelpIntentCalled()).isFalse();
}
@@ -60,8 +60,8 @@
final Intent intent = new Intent().setClassName(
RuntimeEnvironment.application.getPackageName(), HelpTrampoline.class.getName())
.putExtra(Intent.EXTRA_TEXT, "help_url_upgrading");
- final ShadowActivity shadow = shadowOf(Robolectric.buildActivity(HelpTrampoline.class)
- .withIntent(intent).create().get());
+ final ShadowActivity shadow =
+ shadowOf(Robolectric.buildActivity(HelpTrampoline.class, intent).create().get());
final Intent launchedIntent = shadow.getNextStartedActivity();
assertThat(ShadowHelpUtils.isGetHelpIntentCalled()).isTrue();
diff --git a/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java
deleted file mode 100644
index d710d7c..0000000
--- a/tests/robotests/src/com/android/settings/applications/AppInfoDashboardFragmentTest.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * 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 static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AlertDialog;
-import android.app.AppOpsManager;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.os.UserManager;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceManager;
-import android.support.v7.preference.PreferenceScreen;
-import android.view.View;
-
-import com.android.settings.R;
-import com.android.settings.SettingsActivity;
-import com.android.settings.TestConfig;
-import com.android.settings.applications.instantapps.InstantAppButtonsController;
-import com.android.settings.applications.instantapps.InstantAppButtonsController.ShowDialogDelegate;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settings.widget.ActionButtonPreferenceTest;
-import com.android.settings.wrapper.DevicePolicyManagerWrapper;
-import com.android.settingslib.Utils;
-import com.android.settingslib.applications.AppUtils;
-import com.android.settingslib.applications.ApplicationsState.AppEntry;
-import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.util.ReflectionHelpers;
-
-import java.util.HashSet;
-
-@RunWith(SettingsRobolectricTestRunner.class)
-@Config(
- manifest = TestConfig.MANIFEST_PATH,
- sdk = TestConfig.SDK_VERSION
-)
-public final class AppInfoDashboardFragmentTest {
-
- private static final String PACKAGE_NAME = "test_package_name";
-
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private Context mContext;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private UserManager mUserManager;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private SettingsActivity mActivity;
- @Mock
- private DevicePolicyManagerWrapper mDevicePolicyManager;
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private AppOpsManager mAppOpsManager;
-
- private FakeFeatureFactory mFeatureFactory;
- private AppInfoDashboardFragment mAppDetail;
- private Context mShadowContext;
-
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mFeatureFactory = FakeFeatureFactory.setupForTest();
- mShadowContext = RuntimeEnvironment.application;
- mAppDetail = spy(new AppInfoDashboardFragment());
- doReturn(mActivity).when(mAppDetail).getActivity();
- doReturn(mShadowContext).when(mAppDetail).getContext();
- doReturn(mPackageManager).when(mActivity).getPackageManager();
- doReturn(mAppOpsManager).when(mActivity).getSystemService(Context.APP_OPS_SERVICE);
- mAppDetail.mActionButtons = ActionButtonPreferenceTest.createMock();
-
- // Default to not considering any apps to be instant (individual tests can override this).
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> false));
- }
-
- @Test
- public void shouldShowUninstallForAll_installForOneOtherUserOnly_shouldReturnTrue() {
- when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
- when(mUserManager.getUsers().size()).thenReturn(2);
- ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
- ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
- final ApplicationInfo info = new ApplicationInfo();
- info.enabled = true;
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- final PackageInfo packageInfo = mock(PackageInfo.class);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isTrue();
- }
-
- @Test
- public void shouldShowUninstallForAll_installForSelfOnly_shouldReturnFalse() {
- when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
- when(mUserManager.getUsers().size()).thenReturn(2);
- ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
- ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
- final ApplicationInfo info = new ApplicationInfo();
- info.flags = ApplicationInfo.FLAG_INSTALLED;
- info.enabled = true;
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- final PackageInfo packageInfo = mock(PackageInfo.class);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isFalse();
- }
-
- @Test
- public void launchFragment_hasNoPackageInfo_shouldFinish() {
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", null);
-
- assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isFalse();
- verify(mActivity).finishAndRemoveTask();
- }
-
- @Test
- public void launchFragment_hasPackageInfo_shouldReturnTrue() {
- final PackageInfo packageInfo = mock(PackageInfo.class);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- assertThat(mAppDetail.ensurePackageInfoAvailable(mActivity)).isTrue();
- verify(mActivity, never()).finishAndRemoveTask();
- }
-
- @Test
- public void packageSizeChange_isOtherPackage_shouldNotRefreshUi() {
- ReflectionHelpers.setField(mAppDetail, "mPackageName", PACKAGE_NAME);
- mAppDetail.onPackageSizeChanged("Not_" + PACKAGE_NAME);
-
- verify(mAppDetail, never()).refreshUi();
- }
-
- @Test
- public void packageSizeChange_isOwnPackage_shouldRefreshUi() {
- doReturn(Boolean.TRUE).when(mAppDetail).refreshUi();
- ReflectionHelpers.setField(mAppDetail, "mPackageName", PACKAGE_NAME);
-
- mAppDetail.onPackageSizeChanged(PACKAGE_NAME);
-
- verify(mAppDetail).refreshUi();
- }
-
- // Tests that we don't show the "uninstall for all users" button for instant apps.
- @Test
- public void instantApps_noUninstallForAllButton() {
- // Make this app appear to be instant.
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
- when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
- when(mUserManager.getUsers().size()).thenReturn(2);
-
- final ApplicationInfo info = new ApplicationInfo();
- info.enabled = true;
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- final PackageInfo packageInfo = mock(PackageInfo.class);
-
- ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
- ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- assertThat(mAppDetail.shouldShowUninstallForAll(appEntry)).isFalse();
- }
-
- // Tests that we don't show the uninstall button for instant apps"
- @Test
- public void instantApps_noUninstallButton() {
- // Make this app appear to be instant.
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
- final ApplicationInfo info = new ApplicationInfo();
- info.flags = ApplicationInfo.FLAG_INSTALLED;
- info.enabled = true;
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- final PackageInfo packageInfo = mock(PackageInfo.class);
- packageInfo.applicationInfo = info;
-
- ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- mAppDetail.initUninstallButtonForUserApp();
- verify(mAppDetail.mActionButtons).setButton1Visible(false);
- }
-
- // Tests that we don't show the force stop button for instant apps (they aren't allowed to run
- // when they aren't in the foreground).
- @Test
- public void instantApps_noForceStop() {
- // Make this app appear to be instant.
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
- final PackageInfo packageInfo = mock(PackageInfo.class);
- final AppEntry appEntry = mock(AppEntry.class);
- final ApplicationInfo info = new ApplicationInfo();
- appEntry.info = info;
-
- ReflectionHelpers.setField(mAppDetail, "mDpm", mDevicePolicyManager);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
-
- mAppDetail.checkForceStop();
- verify(mAppDetail.mActionButtons).setButton2Visible(false);
- }
-
- @Test
- public void instantApps_buttonControllerHandlesDialog() {
- InstantAppButtonsController mockController = mock(InstantAppButtonsController.class);
- ReflectionHelpers.setField(
- mAppDetail, "mInstantAppButtonsController", mockController);
- // Make sure first that button controller is not called for supported dialog id
- AlertDialog mockDialog = mock(AlertDialog.class);
- when(mockController.createDialog(InstantAppButtonsController.DLG_CLEAR_APP))
- .thenReturn(mockDialog);
- assertThat(mAppDetail.createDialog(InstantAppButtonsController.DLG_CLEAR_APP, 0))
- .isEqualTo(mockDialog);
- verify(mockController).createDialog(InstantAppButtonsController.DLG_CLEAR_APP);
- }
-
- // A helper class for testing the InstantAppButtonsController - it lets us look up the
- // preference associated with a key for instant app buttons and get back a mock
- // LayoutPreference (to avoid a null pointer exception).
- public static class InstalledAppDetailsWithMockInstantButtons extends InstalledAppDetails {
- @Mock
- private LayoutPreference mInstantButtons;
-
- public InstalledAppDetailsWithMockInstantButtons() {
- super();
- MockitoAnnotations.initMocks(this);
- }
-
- @Override
- public Preference findPreference(CharSequence key) {
- if (key == "instant_app_buttons") {
- return mInstantButtons;
- }
- return super.findPreference(key);
- }
- }
-
- @Test
- public void instantApps_instantSpecificButtons() {
- // Make this app appear to be instant.
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
- final PackageInfo packageInfo = mock(PackageInfo.class);
-
- final InstalledAppDetailsWithMockInstantButtons
- fragment = new InstalledAppDetailsWithMockInstantButtons();
- ReflectionHelpers.setField(fragment, "mPackageInfo", packageInfo);
- ReflectionHelpers.setField(fragment, "mApplicationFeatureProvider",
- mFeatureFactory.applicationFeatureProvider);
-
- final InstantAppButtonsController buttonsController =
- mock(InstantAppButtonsController.class);
- when(buttonsController.setPackageName(nullable(String.class)))
- .thenReturn(buttonsController);
- when(mFeatureFactory.applicationFeatureProvider.newInstantAppButtonsController(
- nullable(Fragment.class), nullable(View.class), nullable(ShowDialogDelegate.class)))
- .thenReturn(buttonsController);
-
- fragment.maybeAddInstantAppButtons();
- verify(buttonsController).setPackageName(nullable(String.class));
- verify(buttonsController).show();
- }
-
- @Test
- public void instantApps_removeCorrectPref() {
- PreferenceScreen mockPreferenceScreen = mock(PreferenceScreen.class);
- PreferenceManager mockPreferenceManager = mock(PreferenceManager.class);
- AppDomainsPreference mockAppDomainsPref = mock(AppDomainsPreference.class);
- PackageInfo mockPackageInfo = mock(PackageInfo.class);
- PackageManager mockPackageManager = mock(PackageManager.class);
- ReflectionHelpers.setField(
- mAppDetail, "mInstantAppDomainsPreference", mockAppDomainsPref);
- ReflectionHelpers.setField(
- mAppDetail, "mPreferenceManager", mockPreferenceManager);
- ReflectionHelpers.setField(
- mAppDetail, "mPackageInfo", mockPackageInfo);
- ReflectionHelpers.setField(
- mAppDetail, "mPm", mockPackageManager);
- when(mockPreferenceManager.getPreferenceScreen()).thenReturn(mockPreferenceScreen);
-
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> false));
- mAppDetail.prepareInstantAppPrefs();
-
- // For the non instant case we remove the app domain pref, and leave the launch pref
- verify(mockPreferenceScreen).removePreference(mockAppDomainsPref);
-
- // For the instant app case we remove the launch preff, and leave the app domain pref
- ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
- (InstantAppDataProvider) (i -> true));
-
- mAppDetail.prepareInstantAppPrefs();
- // Will be 1 still due to above call
- verify(mockPreferenceScreen, times(1))
- .removePreference(mockAppDomainsPref);
- }
-
- @Test
- public void onActivityResult_uninstalledUpdates_shouldInvalidateOptionsMenu() {
- doReturn(true).when(mAppDetail).refreshUi();
-
- mAppDetail.onActivityResult(InstalledAppDetails.REQUEST_UNINSTALL, 0, mock(Intent.class));
-
- verify(mActivity).invalidateOptionsMenu();
- }
-
- @Test
- public void handleDisableable_appIsHomeApp_buttonShouldNotWork() {
- final ApplicationInfo info = new ApplicationInfo();
- info.packageName = "pkg";
- info.enabled = true;
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- final HashSet<String> homePackages = new HashSet<>();
- homePackages.add(info.packageName);
-
- ReflectionHelpers.setField(mAppDetail, "mHomePackages", homePackages);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
-
- assertThat(mAppDetail.handleDisableable()).isFalse();
- verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text);
- }
-
- @Test
- @Config(shadows = ShadowUtils.class)
- public void handleDisableable_appIsEnabled_buttonShouldWork() {
- final ApplicationInfo info = new ApplicationInfo();
- info.packageName = "pkg";
- info.enabled = true;
- info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn(
- new HashSet<>());
-
- ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider",
- mFeatureFactory.applicationFeatureProvider);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
-
- assertThat(mAppDetail.handleDisableable()).isTrue();
- verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text);
- }
-
- @Test
- @Config(shadows = ShadowUtils.class)
- public void handleDisableable_appIsDisabled_buttonShouldShowEnable() {
- final ApplicationInfo info = new ApplicationInfo();
- info.packageName = "pkg";
- info.enabled = false;
- info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
- when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn(
- new HashSet<>());
-
- ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider",
- mFeatureFactory.applicationFeatureProvider);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
-
- assertThat(mAppDetail.handleDisableable()).isTrue();
- verify(mAppDetail.mActionButtons).setButton1Text(R.string.enable_text);
- verify(mAppDetail.mActionButtons).setButton1Positive(true);
- }
-
- @Test
- @Config(shadows = ShadowUtils.class)
- public void handleDisableable_appIsEnabledAndInKeepEnabledWhitelist_buttonShouldNotWork() {
- final ApplicationInfo info = new ApplicationInfo();
- info.packageName = "pkg";
- info.enabled = true;
- info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-
- final AppEntry appEntry = mock(AppEntry.class);
- appEntry.info = info;
-
- final HashSet<String> packages = new HashSet<>();
- packages.add(info.packageName);
- when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages()).thenReturn(
- packages);
-
- ReflectionHelpers.setField(mAppDetail, "mApplicationFeatureProvider",
- mFeatureFactory.applicationFeatureProvider);
- ReflectionHelpers.setField(mAppDetail, "mAppEntry", appEntry);
-
- assertThat(mAppDetail.handleDisableable()).isFalse();
- verify(mAppDetail.mActionButtons).setButton1Text(R.string.disable_text);
- }
-
- @Test
- public void initUninstallButtonForUserApp_shouldSetNegativeButton() {
- final ApplicationInfo info = new ApplicationInfo();
- info.flags = ApplicationInfo.FLAG_INSTALLED;
- info.enabled = true;
- final PackageInfo packageInfo = mock(PackageInfo.class);
- packageInfo.applicationInfo = info;
- ReflectionHelpers.setField(mAppDetail, "mUserManager", mUserManager);
- ReflectionHelpers.setField(mAppDetail, "mPackageInfo", packageInfo);
-
- mAppDetail.initUninstallButtonForUserApp();
-
- verify(mAppDetail.mActionButtons).setButton1Positive(false);
- }
-
- @Implements(Utils.class)
- public static class ShadowUtils {
- @Implementation
- public static boolean isSystemPackage(Resources resources, PackageManager pm,
- PackageInfo pkg) {
- return false;
- }
- }
-}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
new file mode 100644
index 0000000..17b7a22
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppActionButtonPreferenceControllerTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.widget.ActionButtonPreference;
+import com.android.settings.widget.ActionButtonPreferenceTest;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.Utils;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AppActionButtonPreferenceControllerTest {
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private DevicePolicyManagerWrapper mDevicePolicyManager;
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+
+ private Context mContext;
+ private AppActionButtonPreferenceController mController;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mContext = spy(RuntimeEnvironment.application);
+ mController = spy(new AppActionButtonPreferenceController(mContext, mFragment, "Package1"));
+ mController.mActionButtons = ActionButtonPreferenceTest.createMock();
+ ReflectionHelpers.setField(mController, "mUserManager", mUserManager);
+ ReflectionHelpers.setField(mController, "mDpm", mDevicePolicyManager);
+ ReflectionHelpers.setField(mController, "mApplicationFeatureProvider",
+ mFeatureFactory.applicationFeatureProvider);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ }
+
+ @Test
+ public void displayPreference_shouldInitializeForceStopButton() {
+ final PreferenceScreen screen = mock(PreferenceScreen.class);
+ final ActionButtonPreference preference = spy(new ActionButtonPreference(mContext));
+ when(screen.findPreference(mController.getPreferenceKey())).thenReturn(preference);
+
+ mController.displayPreference(screen);
+
+ verify(preference).setButton2Positive(false);
+ verify(preference).setButton2Text(R.string.force_stop);
+ verify(preference).setButton2Enabled(false);
+ }
+
+ @Test
+ public void refreshUi_shouldRefreshButton() {
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+ doNothing().when(mController).checkForceStop(appEntry, packageInfo);
+ doNothing().when(mController).initUninstallButtons(appEntry, packageInfo);
+ when(mFragment.getAppEntry()).thenReturn(appEntry);
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+
+ mController.refreshUi();
+
+ verify(mController).checkForceStop(appEntry, packageInfo);
+ verify(mController).initUninstallButtons(appEntry, packageInfo);
+ }
+
+ @Test
+ public void initUninstallButtonForUserApp_shouldSetNegativeButton() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.flags = ApplicationInfo.FLAG_INSTALLED;
+ info.enabled = true;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = info;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+
+ assertThat(mController.initUninstallButtonForUserApp()).isTrue();
+ verify(mController.mActionButtons).setButton1Positive(false);
+ }
+
+ // Tests that we don't show the uninstall button for instant apps"
+ @Test
+ public void initUninstallButtonForUserApp_instantApps_noUninstallButton() {
+ // Make this app appear to be instant.
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> true));
+ final ApplicationInfo info = new ApplicationInfo();
+ info.flags = ApplicationInfo.FLAG_INSTALLED;
+ info.enabled = true;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = info;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = info;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+
+ assertThat(mController.initUninstallButtonForUserApp()).isFalse();
+ verify(mController.mActionButtons).setButton1Visible(false);
+ }
+
+ @Test
+ public void initUninstallButtonForUserApp_notInstalledForCurrentUser_shouldDisableButton() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.enabled = true;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = info;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+ final int userID1 = 1;
+ final int userID2 = 2;
+ final List<UserInfo> userInfos = new ArrayList<>();
+ userInfos.add(new UserInfo(userID1, "User1", UserInfo.FLAG_PRIMARY));
+ userInfos.add(new UserInfo(userID2, "User2", UserInfo.FLAG_GUEST));
+ when(mUserManager.getUsers(true)).thenReturn(userInfos);
+
+ assertThat(mController.initUninstallButtonForUserApp()).isFalse();
+ }
+
+ // Tests that we don't show the force stop button for instant apps (they aren't allowed to run
+ // when they aren't in the foreground).
+ @Test
+ public void checkForceStop_instantApps_shouldNotShowForceStop() {
+ // Make this app appear to be instant.
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> true));
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+
+ mController.checkForceStop(appEntry, packageInfo);
+
+ verify(mController.mActionButtons).setButton2Visible(false);
+ }
+
+ @Test
+ public void checkForceStop_hasActiveAdmin_shouldDisableForceStop() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final String packageName = "Package1";
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ when(mDevicePolicyManager.packageHasActiveAdmins(packageName)).thenReturn(true);
+
+ mController.checkForceStop(appEntry, packageInfo);
+
+ verify(mController.mActionButtons).setButton2Enabled(false);
+ }
+
+ @Test
+ public void checkForceStop_appRunning_shouldEnableForceStop() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+
+ mController.checkForceStop(appEntry, packageInfo);
+
+ verify(mController.mActionButtons).setButton2Enabled(true);
+ }
+
+ @Test
+ public void checkForceStop_appStopped_shouldQueryPackageRestart() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ final ApplicationInfo info = new ApplicationInfo();
+ appEntry.info = info;
+ info.flags = ApplicationInfo.FLAG_STOPPED;
+ info.packageName = "com.android.setting";
+
+ mController.checkForceStop(appEntry, packageInfo);
+
+ verify(mContext).sendOrderedBroadcastAsUser(argThat(intent-> intent != null
+ && intent.getAction().equals(Intent.ACTION_QUERY_PACKAGE_RESTART)),
+ any(UserHandle.class), nullable(String.class), any(BroadcastReceiver.class),
+ nullable(Handler.class), anyInt(), nullable(String.class), nullable(Bundle.class));
+ }
+
+ @Test
+ public void handleDisableable_appIsHomeApp_buttonShouldNotWork() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "pkg";
+ info.enabled = true;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = info;
+ final HashSet<String> homePackages = new HashSet<>();
+ homePackages.add(info.packageName);
+ ReflectionHelpers.setField(mController, "mHomePackages", homePackages);
+
+ assertThat(mController.handleDisableable(appEntry, mock(PackageInfo.class))).isFalse();
+ verify(mController.mActionButtons).setButton1Text(R.string.disable_text);
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void handleDisableable_appIsEnabled_buttonShouldWork() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "pkg";
+ info.enabled = true;
+ info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = info;
+ when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages())
+ .thenReturn(new HashSet<>());
+
+ assertThat(mController.handleDisableable(appEntry, mock(PackageInfo.class))).isTrue();
+ verify(mController.mActionButtons).setButton1Text(R.string.disable_text);
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void handleDisableable_appIsDisabled_buttonShouldShowEnable() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "pkg";
+ info.enabled = false;
+ info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = info;
+ when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages())
+ .thenReturn(new HashSet<>());
+
+ assertThat(mController.handleDisableable(appEntry, mock(PackageInfo.class))).isTrue();
+ verify(mController.mActionButtons).setButton1Text(R.string.enable_text);
+ verify(mController.mActionButtons).setButton1Positive(true);
+ }
+
+ @Test
+ @Config(shadows = ShadowUtils.class)
+ public void handleDisableable_appIsEnabledAndInKeepEnabledWhitelist_buttonShouldNotWork() {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "pkg";
+ info.enabled = true;
+ info.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
+ appEntry.info = info;
+ final HashSet<String> packages = new HashSet<>();
+ packages.add(info.packageName);
+ when(mFeatureFactory.applicationFeatureProvider.getKeepEnabledPackages())
+ .thenReturn(packages);
+
+ assertThat(mController.handleDisableable(appEntry, mock(PackageInfo.class))).isFalse();
+ verify(mController.mActionButtons).setButton1Text(R.string.disable_text);
+ }
+
+ @Implements(Utils.class)
+ public static class ShadowUtils {
+ @Implementation
+ public static boolean isSystemPackage(Resources resources, PackageManager pm,
+ PackageInfo pkg) {
+ return false;
+ }
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
index 3516445..91833f5 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppBatteryPreferenceControllerTest.java
@@ -40,7 +40,6 @@
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java
index b02e01e..76160ee 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppDataUsagePreferenceControllerTest.java
@@ -37,7 +37,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.datausage.AppDataUsage;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
new file mode 100644
index 0000000..87b82ad
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoDashboardFragmentTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.wrapper.DevicePolicyManagerWrapper;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(
+ manifest = TestConfig.MANIFEST_PATH,
+ sdk = TestConfig.SDK_VERSION
+)
+public final class AppInfoDashboardFragmentTest {
+
+ private static final String PACKAGE_NAME = "test_package_name";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private UserManager mUserManager;
+ @Mock
+ private SettingsActivity mActivity;
+ @Mock
+ private DevicePolicyManagerWrapper mDevicePolicyManager;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private AppInfoDashboardFragment mFragment;
+ private Context mShadowContext;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mShadowContext = RuntimeEnvironment.application;
+ mFragment = spy(new AppInfoDashboardFragment());
+ doReturn(mActivity).when(mFragment).getActivity();
+ doReturn(mShadowContext).when(mFragment).getContext();
+ doReturn(mPackageManager).when(mActivity).getPackageManager();
+
+ // Default to not considering any apps to be instant (individual tests can override this).
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+ }
+
+ @Test
+ public void shouldShowUninstallForAll_installForOneOtherUserOnly_shouldReturnTrue() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ when(mUserManager.getUsers().size()).thenReturn(2);
+ ReflectionHelpers.setField(mFragment, "mDpm", mDevicePolicyManager);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ final ApplicationInfo info = new ApplicationInfo();
+ info.enabled = true;
+ final AppEntry appEntry = mock(AppEntry.class);
+ appEntry.info = info;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", packageInfo);
+
+ assertThat(mFragment.shouldShowUninstallForAll(appEntry)).isTrue();
+ }
+
+ @Test
+ public void shouldShowUninstallForAll_installForSelfOnly_shouldReturnFalse() {
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ when(mUserManager.getUsers().size()).thenReturn(2);
+ ReflectionHelpers.setField(mFragment, "mDpm", mDevicePolicyManager);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ final ApplicationInfo info = new ApplicationInfo();
+ info.flags = ApplicationInfo.FLAG_INSTALLED;
+ info.enabled = true;
+ final AppEntry appEntry = mock(AppEntry.class);
+ appEntry.info = info;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", packageInfo);
+
+ assertThat(mFragment.shouldShowUninstallForAll(appEntry)).isFalse();
+ }
+
+ @Test
+ public void launchFragment_hasNoPackageInfo_shouldFinish() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", null);
+
+ assertThat(mFragment.ensurePackageInfoAvailable(mActivity)).isFalse();
+ verify(mActivity).finishAndRemoveTask();
+ }
+
+ @Test
+ public void launchFragment_hasPackageInfo_shouldReturnTrue() {
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", packageInfo);
+
+ assertThat(mFragment.ensurePackageInfoAvailable(mActivity)).isTrue();
+ verify(mActivity, never()).finishAndRemoveTask();
+ }
+
+ @Test
+ public void packageSizeChange_isOtherPackage_shouldNotRefreshUi() {
+ ReflectionHelpers.setField(mFragment, "mPackageName", PACKAGE_NAME);
+ mFragment.onPackageSizeChanged("Not_" + PACKAGE_NAME);
+
+ verify(mFragment, never()).refreshUi();
+ }
+
+ @Test
+ public void packageSizeChange_isOwnPackage_shouldRefreshUi() {
+ doReturn(Boolean.TRUE).when(mFragment).refreshUi();
+ ReflectionHelpers.setField(mFragment, "mPackageName", PACKAGE_NAME);
+
+ mFragment.onPackageSizeChanged(PACKAGE_NAME);
+
+ verify(mFragment).refreshUi();
+ }
+
+ // Tests that we don't show the "uninstall for all users" button for instant apps.
+ @Test
+ public void instantApps_noUninstallForAllButton() {
+ // Make this app appear to be instant.
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> true));
+ when(mDevicePolicyManager.packageHasActiveAdmins(nullable(String.class))).thenReturn(false);
+ when(mUserManager.getUsers().size()).thenReturn(2);
+
+ final ApplicationInfo info = new ApplicationInfo();
+ info.enabled = true;
+ final AppEntry appEntry = mock(AppEntry.class);
+ appEntry.info = info;
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+
+ ReflectionHelpers.setField(mFragment, "mDpm", mDevicePolicyManager);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", packageInfo);
+
+ assertThat(mFragment.shouldShowUninstallForAll(appEntry)).isFalse();
+ }
+
+ @Test
+ public void onActivityResult_uninstalledUpdates_shouldInvalidateOptionsMenu() {
+ doReturn(true).when(mFragment).refreshUi();
+
+ mFragment.onActivityResult(mFragment.REQUEST_UNINSTALL, 0, mock(Intent.class));
+
+ verify(mActivity).invalidateOptionsMenu();
+ }
+
+ @Test
+ public void getNumberOfUserWithPackageInstalled_twoUsersInstalled_shouldReturnTwo()
+ throws PackageManager.NameNotFoundException{
+ final String packageName = "Package1";
+ final int userID1 = 1;
+ final int userID2 = 2;
+ final List<UserInfo> userInfos = new ArrayList<>();
+ userInfos.add(new UserInfo(userID1, "User1", UserInfo.FLAG_PRIMARY));
+ userInfos.add(new UserInfo(userID2, "yue", UserInfo.FLAG_GUEST));
+ when(mUserManager.getUsers(true)).thenReturn(userInfos);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ final ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.flags = ApplicationInfo.FLAG_INSTALLED;
+ when(mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userID1))
+ .thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userID2))
+ .thenReturn(appInfo);
+ ReflectionHelpers.setField(mFragment, "mPm", mPackageManager);
+
+ assertThat(mFragment.getNumberOfUserWithPackageInstalled(packageName)).isEqualTo(2);
+ }
+
+ @Test
+ public void getNumberOfUserWithPackageInstalled_oneUserInstalled_shouldReturnOne()
+ throws PackageManager.NameNotFoundException{
+ final String packageName = "Package1";
+ final int userID1 = 1;
+ final int userID2 = 2;
+ final List<UserInfo> userInfos = new ArrayList<>();
+ userInfos.add(new UserInfo(userID1, "User1", UserInfo.FLAG_PRIMARY));
+ userInfos.add(new UserInfo(userID2, "yue", UserInfo.FLAG_GUEST));
+ when(mUserManager.getUsers(true)).thenReturn(userInfos);
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ final ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.flags = ApplicationInfo.FLAG_INSTALLED;
+ when(mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userID1))
+ .thenReturn(appInfo);
+ when(mPackageManager.getApplicationInfoAsUser(
+ packageName, PackageManager.GET_META_DATA, userID2))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ ReflectionHelpers.setField(mFragment, "mPm", mPackageManager);
+
+ assertThat(mFragment.getNumberOfUserWithPackageInstalled(packageName)).isEqualTo(1);
+
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java
index 25dcab3..51b6ddf 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInfoPreferenceControllerBaseTest.java
@@ -32,7 +32,6 @@
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java
new file mode 100644
index 0000000..d8d11bc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppInstallerInfoPreferenceControllerTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AppInstallerInfoPreferenceControllerTest {
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mAppInfo;
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+ @Mock
+ private Preference mPreference;
+
+ private Context mContext;
+ private AppInstallerInfoPreferenceController mController;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ final String installerPackage = "Installer1";
+ when(mPackageManager.getInstallerPackageName(anyString())).thenReturn(installerPackage);
+ when(mPackageManager.getApplicationInfo(eq(installerPackage), anyInt()))
+ .thenReturn(mAppInfo);
+ mController = new AppInstallerInfoPreferenceController(mContext, mFragment, "Package1");
+ }
+
+ @Test
+ public void getAvailabilityStatus_managedProfile_shouldReturnDisabled() {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noAppLabel_shouldReturnDisabled() {
+ when(mUserManager.isManagedProfile()).thenReturn(false);
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER);
+ }
+
+ @Test
+ public void getAvailabilityStatus_hasAppLabel_shouldReturnAvailable() {
+ when(mUserManager.isManagedProfile()).thenReturn(false);
+ when(mAppInfo.loadLabel(mPackageManager)).thenReturn("Label1");
+ mController = new AppInstallerInfoPreferenceController(mContext, mFragment, "Package1");
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE);
+ }
+
+ @Test
+ public void updateState_shouldSetSummary() {
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = mAppInfo;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(any());
+ }
+
+ @Test
+ public void updateState_noAppStoreLink_shouldDisablePreference() {
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = mAppInfo;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+ when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(null);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setEnabled(false);
+ }
+
+ @Test
+ public void updateState_hasAppStoreLink_shouldSetPreferenceIntent() {
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = mAppInfo;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.packageName = "Pkg1";
+ resolveInfo.activityInfo.name = "Name1";
+ when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference, never()).setEnabled(false);
+ verify(mPreference).setIntent(any(Intent.class));
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
index d74e301..47844c5 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppMemoryPreferenceControllerTest.java
@@ -34,7 +34,6 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.ProcStatsData;
import com.android.settings.applications.ProcessStatsDetail;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
index 482f33c..0b747a8 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
@@ -30,7 +30,6 @@
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.notification.AppNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
index b708232..c5003cc 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppOpenByDefaultPreferenceControllerTest.java
@@ -32,7 +32,6 @@
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.AppLaunchSettings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.AppUtils;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java
index f9f8d98..f0b415c 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppPermissionPreferenceControllerTest.java
@@ -34,7 +34,6 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java
index 729914a..c069517 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppStoragePreferenceControllerTest.java
@@ -32,7 +32,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.AppStorageSettings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java
index 7418489..d6ecf3e 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppVersionPreferenceControllerTest.java
@@ -25,7 +25,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBaseTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBaseTest.java
index 358e50d..e44fdfb 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBaseTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/DefaultAppShortcutPreferenceControllerBaseTest.java
@@ -31,7 +31,6 @@
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.applications.DefaultAppSettings;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java
index a7468b5..18a29e3 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/DrawOverlayDetailPreferenceControllerTest.java
@@ -32,7 +32,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java
index d500be9..7e542f7 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourceDetailPreferenceControllerTest.java
@@ -28,7 +28,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
new file mode 100644
index 0000000..ce38a56
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserManager;
+
+import com.android.settings.TestConfig;
+import com.android.settings.applications.AppStateInstallAppsBridge;
+import com.android.settings.applications.AppStateInstallAppsBridge.InstallAppsState;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.RestrictedSwitchPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ExternalSourcesDetailsTest {
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private RestrictedSwitchPreference mSwitchPref;
+ @Mock
+ private PackageInfo mPackageInfo;
+
+ private ExternalSourcesDetails mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mFragment = new ExternalSourcesDetails();
+ ReflectionHelpers.setField(mFragment, "mUserManager", mUserManager);
+ ReflectionHelpers.setField(mFragment, "mSwitchPref", mSwitchPref);
+ }
+
+ @Test
+ public void refreshUi_noPackageInfo_shouldReturnFalseAndNoCrash() {
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isFalse();
+ // should not crash
+ }
+
+ @Test
+ public void refreshUi_noApplicationInfo_shouldReturnFalseAndNoCrash() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isFalse();
+ // should not crash
+ }
+
+ @Test
+ public void refreshUi_hasApplicationInfo_shouldReturnTrue() {
+ ReflectionHelpers.setField(mFragment, "mPackageInfo", mPackageInfo);
+ mPackageInfo.applicationInfo = new ApplicationInfo();
+ final AppStateInstallAppsBridge appBridge = mock(AppStateInstallAppsBridge.class);
+ ReflectionHelpers.setField(mFragment, "mAppBridge", appBridge);
+ when(appBridge.createInstallAppsStateFor(nullable(String.class), anyInt()))
+ .thenReturn(mock(InstallAppsState.class));
+
+ mFragment.refreshUi();
+
+ assertThat(mFragment.refreshUi()).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java
new file mode 100644
index 0000000..eb8a082
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppButtonsPreferenceControllerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.View;
+
+import com.android.settings.TestConfig;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.applications.instantapps.InstantAppButtonsController;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class InstantAppButtonsPreferenceControllerTest {
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mAppInfo;
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+
+ private Context mContext;
+ private InstantAppButtonsPreferenceController mController;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mContext = spy(RuntimeEnvironment.application);
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = mAppInfo;
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+ mController =
+ spy(new InstantAppButtonsPreferenceController(mContext, mFragment, "Package1"));
+ }
+
+ @Test
+ public void getAvailabilityStatus_notInstantApp_shouldReturnDisabled() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER);
+ }
+
+ @Test
+ public void getAvailabilityStatus_isInstantApp_shouldReturnAvailable() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> true));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE);
+ }
+
+ @Test
+ public void displayPreference_shouldSetPreferenceTitle() {
+ final PreferenceScreen screen = mock(PreferenceScreen.class);
+ final LayoutPreference preference = mock(LayoutPreference.class);
+ when(screen.findPreference(mController.getPreferenceKey())).thenReturn(preference);
+ when(mController.getApplicationFeatureProvider())
+ .thenReturn(mFeatureFactory.applicationFeatureProvider);
+ final InstantAppButtonsController buttonsController =
+ mock(InstantAppButtonsController.class);
+ when(buttonsController.setPackageName(nullable(String.class)))
+ .thenReturn(buttonsController);
+ when(mFeatureFactory.applicationFeatureProvider.newInstantAppButtonsController(
+ nullable(Fragment.class), nullable(View.class),
+ nullable(InstantAppButtonsController.ShowDialogDelegate.class)))
+ .thenReturn(buttonsController);
+
+ mController.displayPreference(screen);
+
+ verify(buttonsController).setPackageName(nullable(String.class));
+ verify(buttonsController).show();
+ }
+
+ @Test
+ public void createDialog_shouldReturnDialogFromButtonController() {
+ final InstantAppButtonsController buttonsController =
+ mock(InstantAppButtonsController.class);
+ ReflectionHelpers.setField(
+ mController, "mInstantAppButtonsController", buttonsController);
+ final AlertDialog mockDialog = mock(AlertDialog.class);
+ when(buttonsController.createDialog(InstantAppButtonsController.DLG_CLEAR_APP))
+ .thenReturn(mockDialog);
+
+ assertThat(mController.createDialog(InstantAppButtonsController.DLG_CLEAR_APP))
+ .isEqualTo(mockDialog);
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceControllerTest.java
new file mode 100644
index 0000000..bb0b42a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/InstantAppDomainsPreferenceControllerTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.appinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import com.android.settings.TestConfig;
+import com.android.settings.applications.AppDomainsPreference;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class InstantAppDomainsPreferenceControllerTest {
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mAppInfo;
+ @Mock
+ private AppInfoDashboardFragment mFragment;
+ @Mock
+ private AppDomainsPreference mPreference;
+
+ private Context mContext;
+ private InstantAppDomainsPreferenceController mController;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ final PackageInfo packageInfo = mock(PackageInfo.class);
+ packageInfo.applicationInfo = mAppInfo;
+ packageInfo.packageName = "Package1";
+ when(mFragment.getPackageInfo()).thenReturn(packageInfo);
+ mController = new InstantAppDomainsPreferenceController(mContext, mFragment);
+ }
+
+ @Test
+ public void getAvailabilityStatus_notInstantApp_shouldReturnDisabled() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> false));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.DISABLED_FOR_USER);
+ }
+
+ @Test
+ public void getAvailabilityStatus_isInstantApp_shouldReturnAvailable() {
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ (InstantAppDataProvider) (i -> true));
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(mController.AVAILABLE);
+ }
+
+ @Test
+ public void updateState_shouldSetPreferenceTitle() {
+ final String[] domain = { "Domain1" };
+ final ArraySet<String> domains = new ArraySet<>();
+ domains.add(domain[0]);
+ final List<IntentFilterVerificationInfo> infoList = new ArrayList<>();
+ final IntentFilterVerificationInfo info =
+ new IntentFilterVerificationInfo("Package1", domains);
+ infoList.add(info);
+
+ when(mPackageManager.getIntentFilterVerifications("Package1")).thenReturn(infoList);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setTitles(domain);
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java
index 7d81168..cf37b36 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/PictureInPictureDetailPreferenceControllerTest.java
@@ -29,7 +29,6 @@
import com.android.settings.R;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java
index fabcbb2..08133f0 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/WriteSystemSettingsPreferenceControllerTest.java
@@ -32,7 +32,6 @@
import android.support.v7.preference.Preference;
import com.android.settings.TestConfig;
-import com.android.settings.applications.AppInfoDashboardFragment;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceControllerTest.java
index cde95cd..62a0d42 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDeviceRenamePreferenceControllerTest.java
@@ -47,6 +47,7 @@
public class BluetoothDeviceRenamePreferenceControllerTest {
private static final String DEVICE_NAME = "Nightshade";
+ private static final String PREF_KEY = "bt_rename_devices";
@Mock
private LocalBluetoothAdapter mLocalAdapter;
@@ -66,10 +67,10 @@
mContext = spy(RuntimeEnvironment.application);
mPreference = new Preference(mContext);
- mPreference.setKey(BluetoothDeviceRenamePreferenceController.PREF_KEY);
+ mPreference.setKey(PREF_KEY);
mController = new BluetoothDeviceRenamePreferenceController(
- mContext, mFragment, mLocalAdapter);
+ mContext, PREF_KEY, mFragment, mLocalAdapter);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
new file mode 100644
index 0000000..aa9d266
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothSwitchPreferenceControllerTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothSwitchPreferenceControllerTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private LocalBluetoothManager mBluetoothManager;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private SwitchPreference mPreference;
+ @Mock
+ private RestrictionUtils mRestrictionUtils;
+ @Mock
+ private LocalBluetoothAdapter mLocalBluetoothAdapter;
+
+ private Context mContext;
+ private BluetoothSwitchPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application.getApplicationContext());
+ FakeFeatureFactory.setupForTest();
+
+ mController = new BluetoothSwitchPreferenceController(
+ mContext, mBluetoothManager, mRestrictionUtils);
+ when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
+ when(mPreference.getKey()).thenReturn(mController.getPreferenceKey());
+ }
+
+ @Test
+ public void testGetAvailabilityStatus_adapterNull_returnDisabled() {
+ mController.mBluetoothAdapter = null;
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.DISABLED_UNSUPPORTED);
+ }
+
+ @Test
+ public void testGetAvailabilityStatus_adapterExisted_returnAvailable() {
+ mController.mBluetoothAdapter = mLocalBluetoothAdapter;
+
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void testOnStart_shouldRegisterPreferenceChangeListener() {
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ verify(mPreference).setOnPreferenceChangeListener(
+ any(BluetoothSwitchPreferenceController.SwitchController.class));
+ }
+
+ @Test
+ public void testOnStop_shouldRegisterPreferenceChangeListener() {
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ mController.onStop();
+
+ verify(mPreference).setOnPreferenceChangeListener(null);
+ }
+
+ @Test
+ public void testIsChecked_adapterNull_returnFalse() {
+ mController.mBluetoothAdapter = null;
+
+ assertThat(mController.isChecked()).isFalse();
+ }
+
+ @Test
+ public void testIsChecked_adapterExisted_returnFromAdapter() {
+ mController.mBluetoothAdapter = mLocalBluetoothAdapter;
+ doReturn(true).when(mLocalBluetoothAdapter).isEnabled();
+
+ assertThat(mController.isChecked()).isTrue();
+ }
+
+ @Test
+ public void testSetChecked_adapterExisted() {
+ mController.mBluetoothAdapter = mLocalBluetoothAdapter;
+
+ mController.setChecked(true);
+
+ verify(mLocalBluetoothAdapter).setBluetoothEnabled(true);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
index f9efc0b..aa5eb67 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
@@ -37,6 +37,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
@@ -45,13 +46,17 @@
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ConnectedDeviceGroupControllerTest {
+ private static final String PREFERENCE_KEY_1 = "pref_key_1";
+
@Mock
private DashboardFragment mDashboardFragment;
@Mock
private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater;
@Mock
- private PreferenceScreen mPreferenceScreen;
+ private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
@Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceManager mPreferenceManager;
private PreferenceGroup mPreferenceGroup;
@@ -66,30 +71,33 @@
mContext = RuntimeEnvironment.application;
mPreference = new Preference(mContext);
+ mPreference.setKey(PREFERENCE_KEY_1);
mLifecycle = new Lifecycle(() -> mLifecycle);
mPreferenceGroup = spy(new PreferenceScreen(mContext, null));
doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager();
doReturn(mContext).when(mDashboardFragment).getContext();
mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mDashboardFragment,
- mLifecycle, mConnectedBluetoothDeviceUpdater);
+ mLifecycle, mConnectedBluetoothDeviceUpdater, mConnectedUsbDeviceUpdater);
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
}
@Test
- public void testOnDeviceAdded_firstAdd_becomeVisible() {
+ public void testOnDeviceAdded_firstAdd_becomeVisibleAndPreferenceAdded() {
mConnectedDeviceGroupController.onDeviceAdded(mPreference);
assertThat(mPreferenceGroup.isVisible()).isTrue();
+ assertThat(mPreferenceGroup.findPreference(PREFERENCE_KEY_1)).isEqualTo(mPreference);
}
@Test
- public void testOnDeviceRemoved_lastRemove_becomeInvisible() {
+ public void testOnDeviceRemoved_lastRemove_becomeInvisibleAndPreferenceRemoved() {
mPreferenceGroup.addPreference(mPreference);
mConnectedDeviceGroupController.onDeviceRemoved(mPreference);
assertThat(mPreferenceGroup.isVisible()).isFalse();
+ assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(0);
}
@Test
@@ -117,9 +125,11 @@
// register the callback in onStart()
mLifecycle.handleLifecycleEvent(android.arch.lifecycle.Lifecycle.Event.ON_START);
verify(mConnectedBluetoothDeviceUpdater).registerCallback();
+ verify(mConnectedUsbDeviceUpdater).registerCallback();
// unregister the callback in onStop()
mLifecycle.handleLifecycleEvent(android.arch.lifecycle.Lifecycle.Event.ON_STOP);
verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
+ verify(mConnectedUsbDeviceUpdater).unregisterCallback();
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java
new file mode 100644
index 0000000..16cd3a7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedUsbDeviceUpdaterTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.connecteddevice;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.deviceinfo.UsbBackend;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ConnectedUsbDeviceUpdaterTest {
+ private Context mContext;
+ private ConnectedUsbDeviceUpdater mDeviceUpdater;
+
+ @Mock
+ private UsbConnectionBroadcastReceiver mUsbReceiver;
+ @Mock
+ private DevicePreferenceCallback mDevicePreferenceCallback;
+ @Mock
+ private UsbBackend mUsbBackend;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mDeviceUpdater = new ConnectedUsbDeviceUpdater(mContext, mDevicePreferenceCallback,
+ mUsbBackend);
+ mDeviceUpdater.mUsbReceiver = mUsbReceiver;
+ }
+
+ @Test
+ public void testInitUsbPreference_preferenceInit() {
+ mDeviceUpdater.initUsbPreference(mContext);
+
+ assertThat(mDeviceUpdater.mUsbPreference.getTitle()).isEqualTo("USB");
+ assertThat(mDeviceUpdater.mUsbPreference.getIcon()).isEqualTo(mContext.getDrawable(
+ R.drawable.ic_usb));
+ assertThat(mDeviceUpdater.mUsbPreference.isSelectable()).isFalse();
+ }
+
+ @Test
+ public void testInitUsbPreference_usbConnected_preferenceAdded() {
+ doReturn(true).when(mUsbReceiver).isConnected();
+
+ mDeviceUpdater.initUsbPreference(mContext);
+
+ verify(mDevicePreferenceCallback).onDeviceAdded(mDeviceUpdater.mUsbPreference);
+ }
+
+ @Test
+ public void testInitUsbPreference_usbDisconnected_preferenceRemoved() {
+ doReturn(false).when(mUsbReceiver).isConnected();
+
+ mDeviceUpdater.initUsbPreference(mContext);
+
+ verify(mDevicePreferenceCallback).onDeviceRemoved(mDeviceUpdater.mUsbPreference);
+ }
+
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java
new file mode 100644
index 0000000..06bd5b7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/UsbConnectionBroadcastReceiverTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.connecteddevice;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbManager;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UsbConnectionBroadcastReceiverTest {
+ private Context mContext;
+ private UsbConnectionBroadcastReceiver mReceiver;
+ private ShadowApplication mShadowApplication;
+
+ @Mock
+ private UsbConnectionBroadcastReceiver.UsbConnectionListener mListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mShadowApplication = ShadowApplication.getInstance();
+ mContext = RuntimeEnvironment.application;
+ mReceiver = new UsbConnectionBroadcastReceiver(mContext, mListener);
+ }
+
+ @Test
+ public void testOnReceive_usbConnected_invokeCallback() {
+ final Intent intent = new Intent();
+ intent.putExtra(UsbManager.USB_CONNECTED, true);
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mListener).onUsbConnectionChanged(true);
+ }
+
+ @Test
+ public void testOnReceive_usbDisconnected_invokeCallback() {
+ final Intent intent = new Intent();
+ intent.putExtra(UsbManager.USB_CONNECTED, false);
+
+ mReceiver.onReceive(mContext, intent);
+
+ verify(mListener).onUsbConnectionChanged(false);
+ }
+
+ @Test
+ public void testRegister_invokeMethodTwice_registerOnce() {
+ mReceiver.register();
+ mReceiver.register();
+
+ final List<BroadcastReceiver> receivers = mShadowApplication.getReceiversForIntent(
+ new Intent(UsbManager.ACTION_USB_STATE));
+ assertHasOneUsbConnectionBroadcastReceiver(receivers);
+ }
+
+ @Test
+ public void testUnregister_invokeMethodTwice_unregisterOnce() {
+ mReceiver.register();
+ mReceiver.unregister();
+ mReceiver.unregister();
+
+ final List<BroadcastReceiver> receivers = mShadowApplication.getReceiversForIntent(
+ new Intent(UsbManager.ACTION_USB_STATE));
+ assertHasNoUsbConnectionBroadcastReceiver(receivers);
+ }
+
+ private void assertHasOneUsbConnectionBroadcastReceiver(List<BroadcastReceiver> receivers) {
+ boolean hasReceiver = false;
+ for (final BroadcastReceiver receiver : receivers) {
+ if (receiver instanceof UsbConnectionBroadcastReceiver) {
+ // If hasReceiver is true, then we're at the second copy of it so fail.
+ assertWithMessage(
+ "Only one instance of UsbConnectionBroadcastReceiver should be "
+ + "registered").that(
+ hasReceiver).isFalse();
+ hasReceiver = true;
+ }
+ }
+ assertThat(hasReceiver).isTrue();
+ }
+
+ private void assertHasNoUsbConnectionBroadcastReceiver(List<BroadcastReceiver> receivers) {
+ for (final BroadcastReceiver receiver : receivers) {
+ assertThat(receiver instanceof UsbConnectionBroadcastReceiver).isFalse();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java b/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java
new file mode 100644
index 0000000..ed4e815
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/core/XmlControllerAttributeTest.java
@@ -0,0 +1,275 @@
+package com.android.settings.core;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.provider.SearchIndexableResource;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.settings.R;
+import com.android.settings.TestConfig;
+import com.android.settings.search.DatabaseIndexingUtils;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.SearchIndexableResources;
+import com.android.settings.search.XmlParserUtils;
+import com.android.settings.security.SecuritySettings;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class XmlControllerAttributeTest {
+
+ // List of classes that are too hard to mock in order to retrieve xml information.
+ private final List<Class> illegalClasses = new ArrayList<>(
+ Arrays.asList(
+ SecuritySettings.class
+ ));
+
+ // List of XML that could be retrieved from the illegalClasses list.
+ private final List<Integer> whitelistXml = new ArrayList<>(
+ Arrays.asList(
+ R.xml.security_settings_misc,
+ R.xml.security_settings_lockscreen_profile,
+ R.xml.security_settings_lockscreen,
+ R.xml.security_settings_chooser,
+ R.xml.security_settings_pattern_profile,
+ R.xml.security_settings_pin_profile,
+ R.xml.security_settings_password_profile,
+ R.xml.security_settings_pattern,
+ R.xml.security_settings_pin,
+ R.xml.security_settings_password,
+ R.xml.security_settings,
+ R.xml.security_settings_status
+ ));
+
+ private static final String NO_VALID_CONSTRUCTOR_ERROR =
+ "Controllers added in XML need a constructor following either:"
+ + "\n\tClassName(Context)\n\tClassName(Context, String)"
+ + "\nThese controllers are missing a valid constructor:\n";
+
+ private static final String NOT_BASE_PREF_CONTROLLER_ERROR =
+ "Controllers added in XML need to extend com.android.settings.core"
+ + ".BasePreferenceController\nThese controllers do not:\n";
+
+ private static final String BAD_CLASSNAME_ERROR =
+ "The following controllers set in the XML did not have valid class names:\n";
+
+ private static final String BAD_CONSTRUCTOR_ERROR =
+ "The constructor provided by the following classes were insufficient to instantiate "
+ + "the object. It could be due to being an interface, abstract, or an "
+ + "IllegalAccessException. Please fix the following classes:\n";
+
+ Context mContext;
+
+ private Set<Class> mProviderClassesCopy;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mProviderClassesCopy = new HashSet<>(SearchIndexableResources.providerValues());
+ }
+
+ @After
+ public void cleanUp() {
+ SearchIndexableResources.providerValues().clear();
+ SearchIndexableResources.providerValues().addAll(mProviderClassesCopy);
+ }
+
+ @Test
+ public void testAllIndexableXML_onlyValidBasePreferenceControllersAdded() {
+ Set<Integer> xmlSet = getIndexableXml();
+ xmlSet.addAll(whitelistXml);
+
+ List<String> xmlControllers = new ArrayList<>();
+ Set<String> invalidConstructors = new HashSet<>();
+ Set<String> invalidClassHierarchy = new HashSet<>();
+ Set<String> badClassNameControllers = new HashSet<>();
+ Set<String> badConstructorControllers = new HashSet<>();
+
+ for (int resId : xmlSet) {
+ xmlControllers.addAll(getXmlControllers(resId));
+ }
+
+ for (String controllerClassName : xmlControllers) {
+ Class<?> clazz = getClassFromClassName(controllerClassName);
+
+ if (clazz == null) {
+ badClassNameControllers.add(controllerClassName);
+ continue;
+ }
+
+ Constructor<?> constructor = getConstructorFromClass(clazz);
+
+ if (constructor == null) {
+ invalidConstructors.add(controllerClassName);
+ continue;
+ }
+
+ Object controller = getObjectFromConstructor(constructor);
+ if (controller == null) {
+ badConstructorControllers.add(controllerClassName);
+ continue;
+ }
+
+ if (!(controller instanceof BasePreferenceController)) {
+ invalidClassHierarchy.add(controllerClassName);
+ }
+ }
+
+ final String invalidConstructorError = buildErrorMessage(NO_VALID_CONSTRUCTOR_ERROR,
+ invalidConstructors);
+ final String invalidClassHierarchyError = buildErrorMessage(NOT_BASE_PREF_CONTROLLER_ERROR,
+ invalidClassHierarchy);
+ final String badClassNameError = buildErrorMessage(BAD_CLASSNAME_ERROR,
+ badClassNameControllers);
+ final String badConstructorError = buildErrorMessage(BAD_CONSTRUCTOR_ERROR,
+ badConstructorControllers);
+
+ assertWithMessage(invalidConstructorError).that(invalidConstructors).isEmpty();
+ assertWithMessage(invalidClassHierarchyError).that(invalidClassHierarchy).isEmpty();
+ assertWithMessage(badClassNameError).that(badClassNameControllers).isEmpty();
+ assertWithMessage(badConstructorError).that(badConstructorControllers).isEmpty();
+ }
+
+ private Set<Integer> getIndexableXml() {
+ Set<Integer> xmlResSet = new HashSet();
+
+ Collection<Class> indexableClasses = SearchIndexableResources.providerValues();
+ indexableClasses.removeAll(illegalClasses);
+
+ for (Class clazz : indexableClasses) {
+
+ Indexable.SearchIndexProvider provider = DatabaseIndexingUtils.getSearchIndexProvider(
+ clazz);
+
+ if (provider == null) {
+ continue;
+ }
+
+ List<SearchIndexableResource> resources = provider.getXmlResourcesToIndex(mContext,
+ true);
+
+ if (resources == null) {
+ continue;
+ }
+
+ for (SearchIndexableResource resource : resources) {
+ // Add '0's anyway. It won't break the test.
+ xmlResSet.add(resource.xmlResId);
+ }
+ }
+ return xmlResSet;
+ }
+
+ private List<String> getXmlControllers(int xmlResId) {
+ List<String> xmlControllers = new ArrayList<>();
+
+ XmlResourceParser parser;
+ try {
+ parser = mContext.getResources().getXml(xmlResId);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+
+ final int outerDepth = parser.getDepth();
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ String controllerClassName;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ controllerClassName = XmlParserUtils.getController(mContext, attrs);
+ // If controller is not indexed, then it is not compatible with
+ if (!TextUtils.isEmpty(controllerClassName)) {
+ xmlControllers.add(controllerClassName);
+ }
+ }
+ } catch (Exception e) {
+ // Assume an issue with robolectric resources
+ }
+ return xmlControllers;
+ }
+
+ private String buildErrorMessage(String errorSummary, Set<String> errorClasses) {
+ final StringBuilder error = new StringBuilder(errorSummary);
+ for (String c : errorClasses) {
+ error.append(c).append("\n");
+ }
+ return error.toString();
+ }
+
+ private Class<?> getClassFromClassName(String className) {
+ Class<?> clazz = null;
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ }
+ return clazz;
+ }
+
+ private Constructor<?> getConstructorFromClass(Class<?> clazz) {
+ Constructor<?> constructor = null;
+ try {
+ constructor = clazz.getConstructor(Context.class);
+ } catch (NoSuchMethodException e) {
+ }
+
+ if (constructor != null) {
+ return constructor;
+ }
+
+ try {
+ constructor = clazz.getConstructor(Context.class, String.class);
+ } catch (NoSuchMethodException e) {
+ }
+
+ return constructor;
+ }
+
+ private Object getObjectFromConstructor(Constructor<?> constructor) {
+ Object controller = null;
+
+ try {
+ controller = constructor.newInstance(mContext);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
+ IllegalArgumentException e) {
+ }
+
+ if (controller != null) {
+ return controller;
+ }
+
+ try {
+ controller = constructor.newInstance(mContext, "key");
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
+ IllegalArgumentException e) {
+ }
+
+ return controller;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceControllerTest.java
new file mode 100644
index 0000000..95fd111
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/EnableGnssRawMeasFullTrackingPreferenceControllerTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.development;
+
+
+import static com.android.settings.development.EnableGnssRawMeasFullTrackingPreferenceController
+ .SETTING_VALUE_OFF;
+import static com.android.settings.development.EnableGnssRawMeasFullTrackingPreferenceController
+ .SETTING_VALUE_ON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class EnableGnssRawMeasFullTrackingPreferenceControllerTest {
+
+ @Mock
+ private SwitchPreference mPreference;
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+
+ private Context mContext;
+ private EnableGnssRawMeasFullTrackingPreferenceController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mController = new EnableGnssRawMeasFullTrackingPreferenceController(mContext);
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn(
+ mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ }
+
+ @Test
+ public void onPreferenceChange_settingEnabled_enableGnssRawMeasFullTrackingShouldBeOn() {
+ mController.onPreferenceChange(mPreference, true /* new value */);
+
+ final int mode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, -1 /* default */);
+
+ assertThat(mode).isEqualTo(SETTING_VALUE_ON);
+ }
+
+ @Test
+ public void onPreferenceChange_settingDisabled_enableGnssRawMeasFullTrackingShouldBeOff() {
+ mController.onPreferenceChange(mPreference, false /* new value */);
+
+ final int mode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, -1 /* default */);
+
+ assertThat(mode).isEqualTo(SETTING_VALUE_OFF);
+ }
+
+ @Test
+ public void updateState_settingDisabled_preferenceShouldNotBeChecked() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, SETTING_VALUE_OFF);
+ mController.updateState(mPreference);
+
+ verify(mPreference).setChecked(false);
+ }
+
+ @Test
+ public void updateState_settingEnabled_preferenceShouldBeChecked() {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, SETTING_VALUE_ON);
+ mController.updateState(mPreference);
+
+ verify(mPreference).setChecked(true);
+ }
+
+ @Test
+ public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() {
+ mController.onDeveloperOptionsSwitchDisabled();
+
+ final int mode = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, -1 /* default */);
+
+ assertThat(mode).isEqualTo(SETTING_VALUE_OFF);
+ verify(mPreference).setChecked(false);
+ verify(mPreference).setEnabled(false);
+ }
+
+ @Test
+ public void onDeveloperOptionsSwitchEnabled_shouldEnablePreference() {
+ mController.onDeveloperOptionsSwitchEnabled();
+
+ verify(mPreference).setEnabled(true);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java
index 05670e2..1fd5430 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/SystemUpdatePreferenceControllerTest.java
@@ -57,7 +57,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new SystemUpdatePreferenceController(mContext, mUserManager);
+
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ mController = new SystemUpdatePreferenceController(mContext);
mPreference = new Preference(RuntimeEnvironment.application);
mPreference.setKey(mController.getPreferenceKey());
when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference);
@@ -82,7 +84,7 @@
mController.updateNonIndexableKeys(keys);
- assertThat(keys.size()).isEqualTo(1);
+ assertThat(keys).hasSize(1);
}
@Test
@@ -94,8 +96,8 @@
@Test
public void updateState_shouldSetToAndroidVersion() {
- mController = new SystemUpdatePreferenceController(
- RuntimeEnvironment.application, mUserManager);
+ mController = new SystemUpdatePreferenceController(RuntimeEnvironment.application);
+
mController.updateState(mPreference);
assertThat(mPreference.getSummary())
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
index fd48162..2f896ac 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/simstatus/SimStatusDialogControllerTest.java
@@ -35,10 +35,13 @@
import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController
.SERVICE_STATE_VALUE_ID;
import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController
+ .SIGNAL_STRENGTH_LABEL_ID;
+import static com.android.settings.deviceinfo.simstatus.SimStatusDialogController
.SIGNAL_STRENGTH_VALUE_ID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -157,6 +160,9 @@
@Test
public void initialize_updateDataStateWithPowerOff_shouldUpdateSettingAndResetSignalStrength() {
when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+ when(mPersistableBundle.getBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL)).thenReturn(
+ true);
mController.initialize();
@@ -172,6 +178,9 @@
final int signalAsu = 50;
doReturn(signalDbm).when(mController).getDbm(mSignalStrength);
doReturn(signalAsu).when(mController).getAsuLevel(mSignalStrength);
+ when(mPersistableBundle.getBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL)).thenReturn(
+ true);
mController.initialize();
@@ -226,6 +235,30 @@
}
@Test
+ public void initialize_doNotShowSignalStrength_shouldRemoveSignalStrengthSetting() {
+ when(mPersistableBundle.getBoolean(
+ CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL)).thenReturn(
+ false);
+
+ mController.initialize();
+
+ verify(mDialog).removeSettingFromScreen(SIGNAL_STRENGTH_LABEL_ID);
+ verify(mDialog).removeSettingFromScreen(SIGNAL_STRENGTH_VALUE_ID);
+ }
+
+ @Test
+ public void initialize_showSignalStrengthAndIccId_shouldShowSignalStrengthAndIccIdSetting() {
+ // getConfigForSubId is nullable, so make sure the default behavior is correct
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
+
+ mController.initialize();
+
+ verify(mDialog).setText(eq(SIGNAL_STRENGTH_VALUE_ID), any());
+ verify(mDialog).removeSettingFromScreen(ICCID_INFO_LABEL_ID);
+ verify(mDialog).removeSettingFromScreen(ICCID_INFO_VALUE_ID);
+ }
+
+ @Test
public void initialize_showIccid_shouldSetIccidToSetting() {
final String iccid = "12351351231241";
when(mPersistableBundle.getBoolean(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
index a163a43..c75dbf4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
+import android.os.PowerManager;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
@@ -83,6 +84,14 @@
}
@Test
+ public void testOnReceive_powerSaveModeChanged_listenerInvoked() {
+ mBatteryBroadcastReceiver.onReceive(mContext,
+ new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
+
+ verify(mBatteryListener).onBatteryChanged();
+ }
+
+ @Test
public void testOnReceive_batteryDataNotChanged_listenerNotInvoked() {
final String batteryLevel = Utils.getBatteryPercentage(mChargingIntent);
final String batteryStatus = Utils.getBatteryStatus(mContext.getResources(),
diff --git a/tests/robotests/src/com/android/settings/location/LocationModeTest.java b/tests/robotests/src/com/android/settings/location/LocationModeTest.java
new file mode 100644
index 0000000..0e7a9d7
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/location/LocationModeTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.location;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settings.testutils.XmlTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LocationModeTest {
+
+ private Context mContext;
+ private LocationMode mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mFragment = new LocationMode();
+ }
+
+ @Test
+ public void testSearchIndexProvider_shouldIndexResource() {
+ final List<SearchIndexableResource> indexRes =
+ mFragment.SEARCH_INDEX_DATA_PROVIDER.getXmlResourcesToIndex(mContext,
+ true /* enabled */);
+
+ assertThat(indexRes).isNotNull();
+ assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId());
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ public void testSearchIndexProvider_ifPageDisabled_shouldNotIndexResource() {
+ final List<String> niks = LocationMode.SEARCH_INDEX_DATA_PROVIDER
+ .getNonIndexableKeys(mContext);
+ final int xmlId = mFragment.getPreferenceScreenResId();
+
+ final List<String> keys = XmlTestUtils.getKeysFromPreferenceXml(mContext, xmlId);
+ assertThat(niks).containsAllIn(keys);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java
index 5d7cca4..a1268d0 100644
--- a/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/location/RecentLocationRequestPreferenceControllerTest.java
@@ -41,7 +41,7 @@
import com.android.settings.SettingsActivity;
import com.android.settings.TestConfig;
import com.android.settings.applications.InstalledAppDetails;
-import com.android.settings.applications.AppInfoDashboardFragment;
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
import com.android.settings.core.FeatureFlags;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.widget.AppPreference;
diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java
index fa2c6b9..80233d8 100644
--- a/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/notification/ZenModeSettingsTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import android.app.NotificationManager;
@@ -53,7 +54,7 @@
}
@Test
- public void testGetBehaviorSettingSummary_sameOrderAsTargetPage() {
+ public void testGetBehaviorSettingSummary_customBehavior() {
NotificationManager.Policy policy = new NotificationManager.Policy(
NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS
| NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS
@@ -63,18 +64,31 @@
final String result = mBuilder.getBehaviorSettingSummary(policy,
Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- String alarms = mContext.getString(R.string.zen_mode_alarms).toLowerCase();
- String reminders = mContext.getString(R.string.zen_mode_reminders).toLowerCase();
- String events = mContext.getString(R.string.zen_mode_events).toLowerCase();
- String media = mContext.getString(R.string.zen_mode_media_system_other).toLowerCase();
+ String custom = mContext.getString(R.string.zen_mode_behavior_summary_custom);
+ assertEquals(custom, result);
+ }
- assertThat(result).contains(alarms);
- assertThat(result).contains(reminders);
- assertThat(result).contains(events);
- assertThat(result).contains(media);
- assertTrue(result.indexOf(alarms) < result.indexOf(media)
- && result.indexOf(media) < result.indexOf(reminders)
- && result.indexOf(reminders) < result.indexOf(events));
+ @Test
+ public void testGetBehaviorSettingSummary_totalSilence() {
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
+ final String result = mBuilder.getBehaviorSettingSummary(policy,
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ String totalSilence = mContext.getString(R.string.zen_mode_behavior_total_silence);
+ assertEquals(totalSilence, result);
+ }
+
+ @Test
+ public void testGetBehaviorSettingSummary_alarmsAndMedia() {
+ NotificationManager.Policy policy = new NotificationManager.Policy(
+ NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
+ | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
+ 0, 0);
+ final String result = mBuilder.getBehaviorSettingSummary(policy,
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ String alarmsAndMedia = mContext.getString(R.string.zen_mode_behavior_alarms_only);
+ assertEquals(alarmsAndMedia, result);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockSettingsHelperTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockSettingsHelperTest.java
index 20a05e3..8628678 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockSettingsHelperTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockSettingsHelperTest.java
@@ -131,9 +131,7 @@
public void testLaunchConfirmationActivity_internal_shouldPropagateTheme() {
Intent intent = new Intent()
.putExtra(WizardManagerHelper.EXTRA_THEME, WizardManagerHelper.THEME_GLIF_V2);
- Activity activity = Robolectric.buildActivity(Activity.class)
- .withIntent(intent)
- .get();
+ Activity activity = Robolectric.buildActivity(Activity.class, intent).get();
ChooseLockSettingsHelper helper = getChooseLockSettingsHelper(activity);
helper.launchConfirmationActivity(123, "test title", true, 0 /* userId */);
diff --git a/tests/robotests/src/com/android/settings/search/SearchIndexProviderCodeInspector.java b/tests/robotests/src/com/android/settings/search/SearchIndexProviderCodeInspector.java
index a8372d9..f84f9a2 100644
--- a/tests/robotests/src/com/android/settings/search/SearchIndexProviderCodeInspector.java
+++ b/tests/robotests/src/com/android/settings/search/SearchIndexProviderCodeInspector.java
@@ -43,13 +43,13 @@
"SettingsPreferenceFragment should implement Indexable, but these do not:\n";
private static final String NOT_CONTAINING_PROVIDER_OBJECT_ERROR =
"Indexable should have public field "
- + DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ + DatabaseIndexingUtils.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ " but these are not:\n";
private static final String NOT_SHARING_PREF_CONTROLLERS_BETWEEN_FRAG_AND_PROVIDER =
"DashboardFragment should share pref controllers with its SearchIndexProvider, but "
+ " these are not: \n";
private static final String NOT_IN_INDEXABLE_PROVIDER_REGISTRY =
- "Class containing " + DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ "Class containing " + DatabaseIndexingUtils.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ " must be added to " + SearchIndexableResources.class.getName()
+ " but these are not: \n";
private static final String NOT_PROVIDING_VALID_RESOURCE_ERROR =
@@ -173,7 +173,7 @@
private boolean hasSearchIndexProvider(Class clazz) {
try {
final Field f = clazz.getField(
- DatabaseIndexingManager.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
+ DatabaseIndexingUtils.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
return f != null;
} catch (NoClassDefFoundError e) {
// Cannot find class def, ignore
diff --git a/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java b/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java
index 6050b32..2bec503 100644
--- a/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java
+++ b/tests/robotests/src/com/android/settings/search/XmlParserUtilTest.java
@@ -129,6 +129,16 @@
}
@Test
+ @Config(qualifiers = "mcc999")
+ public void testControllerAttribute_returnsValidData() {
+ XmlResourceParser parser = getChildByType(R.xml.about_legal, "Preference");
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ String controller = XmlParserUtils.getController(mContext, attrs);
+ assertThat(controller).isEqualTo("mind_flayer");
+ }
+
+ @Test
public void testDataSummaryInvalid_ReturnsNull() {
XmlResourceParser parser = getParentPrimedParser(R.xml.display_settings);
final AttributeSet attrs = Xml.asAttributeSet(parser);
diff --git a/tests/robotests/src/com/android/settings/slices/SliceDataTest.java b/tests/robotests/src/com/android/settings/slices/SliceDataTest.java
new file mode 100644
index 0000000..0e4acca
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SliceDataTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.slices;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SliceDataTest {
+
+ private final String KEY = "KEY";
+ private final String TITLE = "title";
+ private final String SUMMARY = "summary";
+ private final String SCREEN_TITLE = "screen title";
+ private final String FRAGMENT_NAME = "fragment name";
+ private final int ICON = 1234; // I declare a thumb war
+ private final Uri URI = Uri.parse("content://com.android.settings.slices/test");
+ private final String PREF_CONTROLLER = "com.android.settings.slices.tester";
+
+ @Test
+ public void testBuilder_buildsMatchingObject() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData data = builder.build();
+
+ assertThat(data.getKey()).isEqualTo(KEY);
+ assertThat(data.getTitle()).isEqualTo(TITLE);
+ assertThat(data.getSummary()).isEqualTo(SUMMARY);
+ assertThat(data.getScreenTitle()).isEqualTo(SCREEN_TITLE);
+ assertThat(data.getIconResource()).isEqualTo(ICON);
+ assertThat(data.getFragmentClassName()).isEqualTo(FRAGMENT_NAME);
+ assertThat(data.getUri()).isEqualTo(URI);
+ assertThat(data.getPreferenceController()).isEqualTo(PREF_CONTROLLER);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_noKey_throwsIllegalStateException() {
+ new SliceData.Builder()
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER)
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_noTitle_throwsIllegalStateException() {
+ new SliceData.Builder()
+ .setKey(KEY)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER)
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_noFragment_throwsIllegalStateException() {
+ new SliceData.Builder()
+ .setKey(KEY)
+ .setFragmentName(FRAGMENT_NAME)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER)
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_noUri_throwsIllegalStateException() {
+ new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setPreferenceControllerClassName(PREF_CONTROLLER)
+ .build();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testBuilder_noPrefController_throwsIllegalStateException() {
+ new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setUri(URI)
+ .setFragmentName(FRAGMENT_NAME)
+ .build();
+ }
+
+ @Test
+ public void testBuilder_noSubtitle_buildsMatchingObject() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData data = builder.build();
+
+ assertThat(data.getKey()).isEqualTo(KEY);
+ assertThat(data.getTitle()).isEqualTo(TITLE);
+ assertThat(data.getSummary()).isNull();
+ assertThat(data.getScreenTitle()).isEqualTo(SCREEN_TITLE);
+ assertThat(data.getIconResource()).isEqualTo(ICON);
+ assertThat(data.getFragmentClassName()).isEqualTo(FRAGMENT_NAME);
+ assertThat(data.getUri()).isEqualTo(URI);
+ assertThat(data.getPreferenceController()).isEqualTo(PREF_CONTROLLER);
+ }
+
+ @Test
+ public void testBuilder_noScreenTitle_buildsMatchingObject() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData data = builder.build();
+
+ assertThat(data.getKey()).isEqualTo(KEY);
+ assertThat(data.getTitle()).isEqualTo(TITLE);
+ assertThat(data.getSummary()).isEqualTo(SUMMARY);
+ assertThat(data.getScreenTitle()).isNull();
+ assertThat(data.getIconResource()).isEqualTo(ICON);
+ assertThat(data.getFragmentClassName()).isEqualTo(FRAGMENT_NAME);
+ assertThat(data.getUri()).isEqualTo(URI);
+ assertThat(data.getPreferenceController()).isEqualTo(PREF_CONTROLLER);
+ }
+
+ @Test
+ public void testBuilder_noIcon_buildsMatchingObject() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData data = builder.build();
+
+ assertThat(data.getKey()).isEqualTo(KEY);
+ assertThat(data.getTitle()).isEqualTo(TITLE);
+ assertThat(data.getSummary()).isEqualTo(SUMMARY);
+ assertThat(data.getScreenTitle()).isEqualTo(SCREEN_TITLE);
+ assertThat(data.getIconResource()).isEqualTo(0);
+ assertThat(data.getFragmentClassName()).isEqualTo(FRAGMENT_NAME);
+ assertThat(data.getUri()).isEqualTo(URI);
+ assertThat(data.getPreferenceController()).isEqualTo(PREF_CONTROLLER);
+ }
+
+ @Test
+ public void testEquality_identicalObjects() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData dataOne = builder.build();
+ SliceData dataTwo = builder.build();
+
+ assertThat(dataOne.hashCode()).isEqualTo(dataTwo.hashCode());
+ assertThat(dataOne).isEqualTo(dataTwo);
+ }
+
+ @Test
+ public void testEquality_matchingKey_EqualObjects() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData dataOne = builder.build();
+
+ builder.setTitle(TITLE + " diff")
+ .setSummary(SUMMARY + " diff")
+ .setScreenTitle(SCREEN_TITLE + " diff")
+ .setIcon(ICON + 1)
+ .setFragmentName(FRAGMENT_NAME + " diff")
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER + " diff");
+
+ SliceData dataTwo = builder.build();
+
+ assertThat(dataOne.hashCode()).isEqualTo(dataTwo.hashCode());
+ assertThat(dataOne).isEqualTo(dataTwo);
+ }
+
+ @Test
+ public void testEquality_differentKey_differentObjects() {
+ SliceData.Builder builder = new SliceData.Builder()
+ .setKey(KEY)
+ .setTitle(TITLE)
+ .setSummary(SUMMARY)
+ .setScreenTitle(SCREEN_TITLE)
+ .setIcon(ICON)
+ .setFragmentName(FRAGMENT_NAME)
+ .setUri(URI)
+ .setPreferenceControllerClassName(PREF_CONTROLLER);
+
+ SliceData dataOne = builder.build();
+
+ builder.setKey("not key");
+ SliceData dataTwo = builder.build();
+
+ assertThat(dataOne.hashCode()).isNotEqualTo(dataTwo.hashCode());
+ assertThat(dataOne).isNotEqualTo(dataTwo);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/slices/SlicesDatabaseHelperTest.java b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseHelperTest.java
new file mode 100644
index 0000000..a4020dd
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/SlicesDatabaseHelperTest.java
@@ -0,0 +1,87 @@
+package com.android.settings.slices;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.settings.TestConfig;
+import com.android.settings.slices.SlicesDatabaseHelper.IndexColumns;
+import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SlicesDatabaseHelperTest {
+
+ private Context mContext;
+ private SlicesDatabaseHelper mSlicesDatabaseHelper;
+ private SQLiteDatabase mDatabase;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mSlicesDatabaseHelper = new SlicesDatabaseHelper(mContext);
+ mDatabase = mSlicesDatabaseHelper.getWritableDatabase();
+ }
+
+ @After
+ public void cleanUp() {
+ DatabaseTestUtils.clearDb(mContext);
+ }
+
+ @Test
+ public void testDatabaseSchema() {
+ Cursor cursor = mDatabase.rawQuery("SELECT * FROM slices_index", null);
+ String[] columnNames = cursor.getColumnNames();
+
+ String[] expectedNames = new String[]{
+ IndexColumns.KEY,
+ IndexColumns.TITLE,
+ IndexColumns.SUBTITLE,
+ IndexColumns.SCREENTITLE,
+ IndexColumns.ICON_RESOURCE,
+ IndexColumns.FRAGMENT,
+ IndexColumns.CONTROLLER
+ };
+
+ assertThat(columnNames).isEqualTo(expectedNames);
+ }
+
+ @Test
+ public void testUpgrade_dropsOldData() {
+ ContentValues dummyValues = getDummyRow();
+
+ mDatabase.replaceOrThrow(SlicesDatabaseHelper.Tables.TABLE_SLICES_INDEX, null, dummyValues);
+ Cursor baseline = mDatabase.rawQuery("SELECT * FROM slices_index", null);
+ assertThat(baseline.getCount()).isEqualTo(1);
+
+ mSlicesDatabaseHelper.onUpgrade(mDatabase, 0, 1);
+
+ Cursor newCursor = mDatabase.rawQuery("SELECT * FROM slices_index", null);
+ assertThat(newCursor.getCount()).isEqualTo(0);
+ }
+
+ private ContentValues getDummyRow() {
+ ContentValues values;
+
+ values = new ContentValues();
+ values.put(IndexColumns.KEY, "key");
+ values.put(IndexColumns.TITLE, "title");
+ values.put(IndexColumns.SUBTITLE, "subtitle");
+ values.put(IndexColumns.ICON_RESOURCE, 99);
+ values.put(IndexColumns.FRAGMENT, "fragmentClassName");
+ values.put(IndexColumns.CONTROLLER, "preferenceController");
+
+ return values;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/SettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/settings/testutils/SettingsRobolectricTestRunner.java
index 2e8bac0..7c374e9 100644
--- a/tests/robotests/src/com/android/settings/testutils/SettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/settings/testutils/SettingsRobolectricTestRunner.java
@@ -62,8 +62,8 @@
// By adding any resources from libraries we need the AndroidManifest, we can access
// them from within the parallel universe's resource loader.
- final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath),
- Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) {
+ return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir),
+ Fs.fileFromPath(assetsDir), "com.android.settings") {
@Override
public List<ResourcePath> getIncludedResourcePaths() {
List<ResourcePath> paths = super.getIncludedResourcePaths();
@@ -71,10 +71,6 @@
return paths;
}
};
-
- // Set the package name to the renamed one
- manifest.setPackageName("com.android.settings");
- return manifest;
}
public static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) {
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index 65ed661..b22c01b 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
diff --git a/tests/unit/src/com/android/settings/applications/AppOpsSettingsTest.java b/tests/unit/src/com/android/settings/applications/AppOpsSettingsTest.java
new file mode 100644
index 0000000..d89d4a3
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/AppOpsSettingsTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Direction;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.support.v7.widget.RecyclerView;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * An abstract parent for testing settings activities that manage an AppOps permission.
+ */
+abstract public class AppOpsSettingsTest {
+ private static final String WM_DISMISS_KEYGUARD_COMMAND = "wm dismiss-keyguard";
+ private static final long START_ACTIVITY_TIMEOUT = 5000;
+
+ private Context mContext;
+ private UiDevice mUiDevice;
+ private PackageManager mPackageManager;
+ private AppOpsManager mAppOpsManager;
+ private List<UserInfo> mProfiles;
+ private String mPackageName;
+
+ // These depend on which app op's settings UI is being tested.
+ private final String mActivityAction;
+ private final int mAppOpCode;
+
+ protected AppOpsSettingsTest(String activityAction, int appOpCode) {
+ mActivityAction = activityAction;
+ mAppOpCode = appOpCode;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPackageName = InstrumentationRegistry.getContext().getPackageName();
+ mPackageManager = mContext.getPackageManager();
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ mProfiles = mContext.getSystemService(UserManager.class).getProfiles(UserHandle.myUserId());
+ resetAppOpModeForAllProfiles();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mUiDevice.wakeUp();
+ mUiDevice.executeShellCommand(WM_DISMISS_KEYGUARD_COMMAND);
+ }
+
+ private void resetAppOpModeForAllProfiles() throws Exception {
+ for (UserInfo user : mProfiles) {
+ final int uid = mPackageManager.getPackageUidAsUser(mPackageName, user.id);
+ mAppOpsManager.setMode(mAppOpCode, uid, mPackageName, MODE_DEFAULT);
+ }
+ }
+
+ /**
+ * Creates an intent for showing the permission settings for all apps.
+ */
+ private Intent createManageAllAppsIntent() {
+ return new Intent(mActivityAction);
+ }
+
+ /**
+ * Creates an intent for showing the permission setting for a single app.
+ */
+ private Intent createManageSingleAppIntent(String packageName) {
+ final Intent intent = createManageAllAppsIntent();
+ intent.setData(Uri.parse("package:" + packageName));
+ return intent;
+ }
+
+ private String getApplicationLabel(String packageName) throws Exception {
+ final ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
+ return mPackageManager.getApplicationLabel(info).toString();
+ }
+
+ private UiObject2 findAndVerifySwitchState(boolean checked) {
+ final BySelector switchSelector = By.clazz(Switch.class).res("android:id/switch_widget");
+ final UiObject2 switchPref = mUiDevice.wait(Until.findObject(switchSelector),
+ START_ACTIVITY_TIMEOUT);
+ assertNotNull("Switch not shown", switchPref);
+ assertTrue("Switch in invalid state", switchPref.isChecked() == checked);
+ return switchPref;
+ }
+
+ @Test
+ public void testAppList() throws Exception {
+ final String testAppLabel = getApplicationLabel(mPackageName);
+
+ mContext.startActivity(createManageAllAppsIntent());
+ final BySelector preferenceListSelector =
+ By.clazz(RecyclerView.class).res("com.android.settings:id/apps_list");
+ final UiObject2 preferenceList = mUiDevice.wait(Until.findObject(preferenceListSelector),
+ START_ACTIVITY_TIMEOUT);
+ assertNotNull("App list not shown", preferenceList);
+
+ final BySelector appLabelTextViewSelector = By.clazz(TextView.class)
+ .res("android:id/title")
+ .text(testAppLabel);
+ List<UiObject2> listOfMatchingTextViews;
+ do {
+ listOfMatchingTextViews = preferenceList.findObjects(appLabelTextViewSelector);
+ // assuming the number of profiles will be sufficiently small so that all the entries
+ // for the same package will fit in one screen at some time during the scroll.
+ } while (listOfMatchingTextViews.size() != mProfiles.size() &&
+ preferenceList.scroll(Direction.DOWN, 0.2f));
+ assertEquals("Test app not listed for each profile", mProfiles.size(),
+ listOfMatchingTextViews.size());
+
+ for (UiObject2 matchingObject : listOfMatchingTextViews) {
+ matchingObject.click();
+ findAndVerifySwitchState(true);
+ mUiDevice.pressBack();
+ }
+ }
+
+ private void testAppDetailScreenForAppOp(int appOpMode, int userId) throws Exception {
+ final String testAppLabel = getApplicationLabel(mPackageName);
+ final BySelector appDetailTitleSelector = By.clazz(TextView.class)
+ .res("com.android.settings:id/app_detail_title")
+ .text(testAppLabel);
+
+ mAppOpsManager.setMode(mAppOpCode,
+ mPackageManager.getPackageUidAsUser(mPackageName, userId), mPackageName, appOpMode);
+ mContext.startActivityAsUser(createManageSingleAppIntent(mPackageName),
+ UserHandle.of(userId));
+ mUiDevice.wait(Until.findObject(appDetailTitleSelector), START_ACTIVITY_TIMEOUT);
+ findAndVerifySwitchState(appOpMode == MODE_ALLOWED || appOpMode == MODE_DEFAULT);
+ mUiDevice.pressBack();
+ }
+
+ @Test
+ public void testSingleApp() throws Exception {
+ // App op MODE_DEFAULT is already tested in #testAppList
+ for (UserInfo user : mProfiles) {
+ testAppDetailScreenForAppOp(MODE_ALLOWED, user.id);
+ testAppDetailScreenForAppOp(MODE_ERRORED, user.id);
+ }
+ }
+
+ private void testSwitchToggle(int fromAppOp, int toAppOp) throws Exception {
+ final int packageUid = mPackageManager.getPackageUid(mPackageName, 0);
+ final boolean initialState = (fromAppOp == MODE_ALLOWED || fromAppOp == MODE_DEFAULT);
+
+ mAppOpsManager.setMode(mAppOpCode, packageUid, mPackageName, fromAppOp);
+ mContext.startActivity(createManageSingleAppIntent(mPackageName));
+ final UiObject2 switchPref = findAndVerifySwitchState(initialState);
+ switchPref.click();
+ Thread.sleep(1000);
+ assertEquals("Toggling switch did not change app op", toAppOp,
+ mAppOpsManager.checkOpNoThrow(mAppOpCode, packageUid,
+ mPackageName));
+ mUiDevice.pressBack();
+ }
+
+ @Test
+ public void testIfSwitchTogglesAppOp() throws Exception {
+ testSwitchToggle(MODE_ALLOWED, MODE_ERRORED);
+ testSwitchToggle(MODE_ERRORED, MODE_ALLOWED);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mUiDevice.pressHome();
+ resetAppOpModeForAllProfiles();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java b/tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java
new file mode 100644
index 0000000..24760ae
--- /dev/null
+++ b/tests/unit/src/com/android/settings/applications/DrawOverlaySettingsTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.app.AppOpsManager;
+import android.provider.Settings;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class DrawOverlaySettingsTest extends AppOpsSettingsTest {
+
+ public DrawOverlaySettingsTest() {
+ super(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, AppOpsManager.OP_SYSTEM_ALERT_WINDOW);
+ }
+
+ // Test cases are in the superclass.
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java b/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java
index f7e956b..6ac21af 100644
--- a/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java
+++ b/tests/unit/src/com/android/settings/applications/ExternalSourcesSettingsTest.java
@@ -16,187 +16,21 @@
package com.android.settings.applications;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
-import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
-import android.widget.ListView;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
-@Suppress
@RunWith(AndroidJUnit4.class)
@LargeTest
-public class ExternalSourcesSettingsTest {
+public class ExternalSourcesSettingsTest extends AppOpsSettingsTest {
- private static final String TAG = ExternalSourcesSettingsTest.class.getSimpleName();
- private static final String WM_DISMISS_KEYGUARD_COMMAND = "wm dismiss-keyguard";
- private static final long START_ACTIVITY_TIMEOUT = 5000;
-
- private Context mContext;
- private UiDevice mUiDevice;
- private PackageManager mPackageManager;
- private AppOpsManager mAppOpsManager;
- private List<UserInfo> mProfiles;
- private String mPackageName;
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mPackageName = InstrumentationRegistry.getContext().getPackageName();
- mPackageManager = mContext.getPackageManager();
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- mProfiles = mContext.getSystemService(UserManager.class).getProfiles(UserHandle.myUserId());
- resetAppOpModeForAllProfiles();
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- mUiDevice.wakeUp();
- mUiDevice.executeShellCommand(WM_DISMISS_KEYGUARD_COMMAND);
+ public ExternalSourcesSettingsTest() {
+ super(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
+ AppOpsManager.OP_REQUEST_INSTALL_PACKAGES);
}
- private void resetAppOpModeForAllProfiles() throws Exception {
- for (UserInfo user : mProfiles) {
- final int uid = mPackageManager.getPackageUidAsUser(mPackageName, user.id);
- mAppOpsManager.setMode(OP_REQUEST_INSTALL_PACKAGES, uid, mPackageName, MODE_DEFAULT);
- }
- }
-
- private Intent createManageExternalSourcesListIntent() {
- final Intent manageExternalSourcesIntent = new Intent();
- manageExternalSourcesIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
- return manageExternalSourcesIntent;
- }
-
- private Intent createManageExternalSourcesAppIntent(String packageName) {
- final Intent intent = createManageExternalSourcesListIntent();
- intent.setData(Uri.parse("package:" + packageName));
- return intent;
- }
-
- private String getApplicationLabel(String packageName) throws Exception {
- final ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0);
- return mPackageManager.getApplicationLabel(info).toString();
- }
-
- private UiObject2 findAndVerifySwitchState(boolean checked) {
- final BySelector switchSelector = By.clazz(Switch.class).res("android:id/switch_widget");
- final UiObject2 switchPref = mUiDevice.wait(Until.findObject(switchSelector),
- START_ACTIVITY_TIMEOUT);
- assertNotNull("Switch not shown", switchPref);
- assertTrue("Switch in invalid state", switchPref.isChecked() == checked);
- return switchPref;
- }
-
- @Test
- public void testManageExternalSourcesList() throws Exception {
- final String testAppLabel = getApplicationLabel(mPackageName);
-
- mContext.startActivity(createManageExternalSourcesListIntent());
- final BySelector preferenceListSelector = By.clazz(ListView.class).res("android:id/list");
- final UiObject2 preferenceList = mUiDevice.wait(Until.findObject(preferenceListSelector),
- START_ACTIVITY_TIMEOUT);
- assertNotNull("App list not shown", preferenceList);
-
- final BySelector appLabelTextViewSelector = By.clazz(TextView.class)
- .res("android:id/title")
- .text(testAppLabel);
- List<UiObject2> listOfMatchingTextViews;
- do {
- listOfMatchingTextViews = preferenceList.findObjects(appLabelTextViewSelector);
- // assuming the number of profiles will be sufficiently small so that all the entries
- // for the same package will fit in one screen at some time during the scroll.
- } while (listOfMatchingTextViews.size() != mProfiles.size() &&
- preferenceList.scroll(Direction.DOWN, 0.2f));
- assertEquals("Test app not listed for each profile", mProfiles.size(),
- listOfMatchingTextViews.size());
-
- for (UiObject2 matchingObject : listOfMatchingTextViews) {
- matchingObject.click();
- findAndVerifySwitchState(true);
- mUiDevice.pressBack();
- }
- }
-
- private void testAppDetailScreenForAppOp(int appOpMode, int userId) throws Exception {
- final String testAppLabel = getApplicationLabel(mPackageName);
- final BySelector appDetailTitleSelector = By.clazz(TextView.class)
- .res("com.android.settings:id/app_detail_title")
- .text(testAppLabel);
-
- mAppOpsManager.setMode(OP_REQUEST_INSTALL_PACKAGES,
- mPackageManager.getPackageUidAsUser(mPackageName, userId), mPackageName, appOpMode);
- mContext.startActivityAsUser(createManageExternalSourcesAppIntent(mPackageName),
- UserHandle.of(userId));
- mUiDevice.wait(Until.findObject(appDetailTitleSelector), START_ACTIVITY_TIMEOUT);
- findAndVerifySwitchState(appOpMode == MODE_ALLOWED || appOpMode == MODE_DEFAULT);
- mUiDevice.pressBack();
- }
-
- @Test
- public void testManageExternalSourcesForApp() throws Exception {
- // App op MODE_DEFAULT is already tested in #testManageExternalSourcesList
- for (UserInfo user : mProfiles) {
- testAppDetailScreenForAppOp(MODE_ALLOWED, user.id);
- testAppDetailScreenForAppOp(MODE_ERRORED, user.id);
- }
- }
-
- private void testSwitchToggle(int fromAppOp, int toAppOp) throws Exception {
- final int packageUid = mPackageManager.getPackageUid(mPackageName, 0);
- final boolean initialState = (fromAppOp == MODE_ALLOWED || fromAppOp == MODE_DEFAULT);
-
- mAppOpsManager.setMode(OP_REQUEST_INSTALL_PACKAGES, packageUid, mPackageName, fromAppOp);
- mContext.startActivity(createManageExternalSourcesAppIntent(mPackageName));
- final UiObject2 switchPref = findAndVerifySwitchState(initialState);
- switchPref.click();
- Thread.sleep(1000);
- assertEquals("Toggling switch did not change app op", toAppOp,
- mAppOpsManager.checkOpNoThrow(OP_REQUEST_INSTALL_PACKAGES, packageUid,
- mPackageName));
- mUiDevice.pressBack();
- }
-
- @Test
- public void testIfSwitchTogglesAppOp() throws Exception {
- testSwitchToggle(MODE_ALLOWED, MODE_ERRORED);
- testSwitchToggle(MODE_ERRORED, MODE_ALLOWED);
- }
-
- @After
- public void tearDown() throws Exception {
- mUiDevice.pressHome();
- resetAppOpModeForAllProfiles();
- }
+ // Test cases are in the superclass.
}
diff --git a/tests/unit/src/com/android/settings/applications/PackageUtilTest.java b/tests/unit/src/com/android/settings/applications/PackageUtilTest.java
index 1c064ae..0e3c402 100644
--- a/tests/unit/src/com/android/settings/applications/PackageUtilTest.java
+++ b/tests/unit/src/com/android/settings/applications/PackageUtilTest.java
@@ -35,6 +35,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Deprecated
public class PackageUtilTest {
private static final String ALL_USERS_APP_NAME = "com.google.allusers.app";
private static final String ONE_USER_APP_NAME = "com.google.oneuser.app";
diff --git a/tests/unit/src/com/android/settings/applications/SpecialAppAccessSettingsTest.java b/tests/unit/src/com/android/settings/applications/SpecialAppAccessSettingsTest.java
index 4d92cf9..4165d06 100644
--- a/tests/unit/src/com/android/settings/applications/SpecialAppAccessSettingsTest.java
+++ b/tests/unit/src/com/android/settings/applications/SpecialAppAccessSettingsTest.java
@@ -20,6 +20,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
@@ -79,9 +80,15 @@
final String titleSpecialApps = mTargetContext.getResources().getString(
R.string.special_access);
- final UiScrollable settings = new UiScrollable(
- new UiSelector().packageName(mTargetContext.getPackageName()).scrollable(true));
- settings.scrollTextIntoView(titleSpecialApps);
+ try {
+ // scollbar may or may not be present, depending on how many recents app are there. If
+ // the page is scrollable, scroll to the bottom to show the special app access settings.
+ final UiScrollable settings = new UiScrollable(
+ new UiSelector().packageName(mTargetContext.getPackageName()).scrollable(true));
+ settings.scrollTextIntoView(titleSpecialApps);
+ } catch (UiObjectNotFoundException e) {
+ // ignore
+ }
mDevice.findObject(new UiSelector().text(titleSpecialApps)).click();
}
diff --git a/tests/unit/src/com/android/settings/vpn2/PreferenceListTest.java b/tests/unit/src/com/android/settings/vpn2/PreferenceListTest.java
index bb12efa..2accbf2 100644
--- a/tests/unit/src/com/android/settings/vpn2/PreferenceListTest.java
+++ b/tests/unit/src/com/android/settings/vpn2/PreferenceListTest.java
@@ -36,9 +36,9 @@
import java.util.Map;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.compat.ArgumentMatcher;
public class PreferenceListTest extends AndroidTestCase {
private static final String TAG = "PreferenceListTest";
@@ -135,13 +135,10 @@
/* lockdownVpnKey */ null);
updater.run();
- final ArgumentMatcher<VpnProfile> equalsFake = new ArgumentMatcher<VpnProfile>() {
- @Override
- public boolean matchesObject(final Object arg) {
- if (arg == vpnProfile) return true;
- if (arg == null) return false;
- return TextUtils.equals(((VpnProfile) arg).key, vpnProfile.key);
- }
+ final ArgumentMatcher<VpnProfile> equalsFake = arg -> {
+ if (arg == vpnProfile) return true;
+ if (arg == null) return false;
+ return TextUtils.equals(arg.key, vpnProfile.key);
};
// The VPN profile should have been used to create a preference and set up at laest once