Add search providers for Connected Devices in new IA

Also add code inspection tests to ensure search provider is added
properly.

Bug: 33252252
Test: make RunSettingsRoboTests
Change-Id: I192e1d9fe0498b76013c4d43b5624d1ef2beb6f9
This commit is contained in:
Fan Zhang
2016-11-30 15:56:17 -08:00
parent 8a898fe430
commit 762b4969d9
11 changed files with 152 additions and 77 deletions

View File

@@ -4517,7 +4517,7 @@
<string name="battery_saver_turn_on_automatically_never">Never</string> <string name="battery_saver_turn_on_automatically_never">Never</string>
<!-- [CHAR_LIMIT=40] Battery saver: Value for automatic entry option: pct% battery --> <!-- [CHAR_LIMIT=40] Battery saver: Value for automatic entry option: pct% battery -->
<string name="battery_saver_turn_on_automatically_pct">at <xliff:g id="percent">%1$s</xliff:g>battery</string> <string name="battery_saver_turn_on_automatically_pct">at <xliff:g id="percent">%1$s</xliff:g> battery</string>
<!-- Process Stats strings --> <!-- Process Stats strings -->
<skip /> <skip />

View File

@@ -14,7 +14,9 @@
limitations under the License. limitations under the License.
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/connected_devices_dashboard_title">
<SwitchPreference <SwitchPreference
android:key="toggle_nfc" android:key="toggle_nfc"

View File

@@ -16,15 +16,20 @@
package com.android.settings.connecteddevice; package com.android.settings.connecteddevice;
import android.content.Context; import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.PreferenceController; import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.UsbBackend; import com.android.settings.deviceinfo.UsbBackend;
import com.android.settings.nfc.NfcPreferenceController; import com.android.settings.nfc.NfcPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
public class ConnectedDeviceDashboardFragment extends DashboardFragment { public class ConnectedDeviceDashboardFragment extends DashboardFragment {
@@ -65,4 +70,21 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment {
return controllers; return controllers;
} }
/**
* For Search.
*/
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
if (!FeatureFactory.getFactory(context).getDashboardFeatureProvider(context)
.isEnabled()) {
return null;
}
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.connected_devices;
return Arrays.asList(sir);
}
};
} }

View File

@@ -16,7 +16,6 @@
package com.android.settings.core.instrumentation; package com.android.settings.core.instrumentation;
import android.content.Context; import android.content.Context;
import android.support.annotation.VisibleForTesting;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -92,9 +91,4 @@ public class MetricsFeatureProviderImpl implements MetricsFeatureProvider {
writer.histogram(context, name, bucket); writer.histogram(context, name, bucket);
} }
} }
@VisibleForTesting
public void addLogWriter(LogWriter logWriter) {
mLoggerWriters.add(logWriter);
}
} }

View File

@@ -32,6 +32,7 @@ import com.android.settings.accounts.AccountSettings;
import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.AdvancedAppSettings;
import com.android.settings.applications.SpecialAccessSettings; import com.android.settings.applications.SpecialAccessSettings;
import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.datausage.DataUsageMeteredSettings; import com.android.settings.datausage.DataUsageMeteredSettings;
import com.android.settings.datausage.DataUsageSummary; import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.StorageDashboardFragment; import com.android.settings.deviceinfo.StorageDashboardFragment;
@@ -108,6 +109,7 @@ public final class Ranking {
// BT // BT
sRankMap.put(BluetoothSettings.class.getName(), RANK_BT); sRankMap.put(BluetoothSettings.class.getName(), RANK_BT);
sRankMap.put(ConnectedDeviceDashboardFragment.class.getName(), RANK_BT);
// SIM Cards // SIM Cards
sRankMap.put(SimSettings.class.getName(), RANK_SIM); sRankMap.put(SimSettings.class.getName(), RANK_SIM);

View File

@@ -20,6 +20,7 @@ import android.provider.SearchIndexableResource;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.annotation.XmlRes; import android.support.annotation.XmlRes;
import com.android.settings.DateTimeSettings; import com.android.settings.DateTimeSettings;
import com.android.settings.DevelopmentSettings; import com.android.settings.DevelopmentSettings;
import com.android.settings.DeviceInfoSettings; import com.android.settings.DeviceInfoSettings;
@@ -36,6 +37,7 @@ import com.android.settings.accounts.AccountSettings;
import com.android.settings.applications.AdvancedAppSettings; import com.android.settings.applications.AdvancedAppSettings;
import com.android.settings.applications.SpecialAccessSettings; import com.android.settings.applications.SpecialAccessSettings;
import com.android.settings.bluetooth.BluetoothSettings; import com.android.settings.bluetooth.BluetoothSettings;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.datausage.DataUsageMeteredSettings; import com.android.settings.datausage.DataUsageMeteredSettings;
import com.android.settings.datausage.DataUsageSummary; import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.StorageDashboardFragment; import com.android.settings.deviceinfo.StorageDashboardFragment;
@@ -131,6 +133,7 @@ public final class SearchIndexableResources {
R.drawable.ic_settings_notifications); R.drawable.ic_settings_notifications);
addIndex(SystemDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_about); addIndex(SystemDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_about);
addIndex(StorageDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_storage); addIndex(StorageDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_storage);
addIndex(ConnectedDeviceDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_bt_laptop);
addIndex(EnterprisePrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_about); addIndex(EnterprisePrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_about);
} }

View File

@@ -0,0 +1,3 @@
com.android.settings.display.ScreenZoomPreferenceFragmentForSetupWizard
com.android.settings.wifi.WifiSettingsForSetupWizard
com.android.settings.print.PrintServiceSettingsFragment

View File

@@ -16,10 +16,13 @@
package com.android.settings.connecteddevice; package com.android.settings.connecteddevice;
import android.content.Context; import android.content.Context;
import android.provider.SearchIndexableResource;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.CategoryKey;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -27,8 +30,12 @@ import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import java.util.List;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -43,6 +50,9 @@ public class ConnectedDeviceDashboardFragmentTest {
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest(mContext); FakeFeatureFactory.setupForTest(mContext);
final FakeFeatureFactory factory =
(FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
when(factory.dashboardFeatureProvider.isEnabled()).thenReturn(true);
mFragment = new ConnectedDeviceDashboardFragment(); mFragment = new ConnectedDeviceDashboardFragment();
} }
@@ -51,4 +61,14 @@ public class ConnectedDeviceDashboardFragmentTest {
assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_DEVICE); assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_DEVICE);
} }
@Test
public void testSearchIndexProvider_shouldIndexResource() {
final List<SearchIndexableResource> indexRes =
ConnectedDeviceDashboardFragment.SEARCH_INDEX_DATA_PROVIDER.getXmlResourcesToIndex(
ShadowApplication.getInstance().getApplicationContext(),
true /* enabled */);
assertThat(indexRes).isNotNull();
assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId());
}
} }

View File

@@ -32,12 +32,12 @@ import static com.google.common.truth.Truth.assertWithMessage;
*/ */
public class InstrumentableFragmentCodeInspector extends CodeInspector { public class InstrumentableFragmentCodeInspector extends CodeInspector {
private final List<String> grandfather_notImplmentingInstrumentable; private final List<String> grandfather_notImplementingInstrumentable;
public InstrumentableFragmentCodeInspector(List<Class<?>> classes) { public InstrumentableFragmentCodeInspector(List<Class<?>> classes) {
super(classes); super(classes);
grandfather_notImplmentingInstrumentable = new ArrayList<>(); grandfather_notImplementingInstrumentable = new ArrayList<>();
initializeGrandfatherList(grandfather_notImplmentingInstrumentable, initializeGrandfatherList(grandfather_notImplementingInstrumentable,
"grandfather_not_implementing_instrumentable"); "grandfather_not_implementing_instrumentable");
} }
@@ -53,7 +53,7 @@ public class InstrumentableFragmentCodeInspector extends CodeInspector {
// If it's a fragment, it must also be instrumentable. // If it's a fragment, it must also be instrumentable.
if (Fragment.class.isAssignableFrom(clazz) if (Fragment.class.isAssignableFrom(clazz)
&& !Instrumentable.class.isAssignableFrom(clazz) && !Instrumentable.class.isAssignableFrom(clazz)
&& !grandfather_notImplmentingInstrumentable.contains(className)) { && !grandfather_notImplementingInstrumentable.contains(className)) {
broken.add(className); broken.add(className);
} }
} }

View File

@@ -17,16 +17,18 @@ package com.android.settings.core.instrumentation;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyInt;
@@ -41,23 +43,21 @@ public class SharedPreferenceLoggerTest {
private static final String TEST_TAG = "tag"; private static final String TEST_TAG = "tag";
private static final String TEST_KEY = "key"; private static final String TEST_KEY = "key";
@Mock @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LogWriter mLogWriter; private Context mContext;
private FakeFeatureFactory mFactory;
private MetricsFeatureProvider mMetricsFeature; private MetricsFeatureProvider mMetricsFeature;
private ShadowApplication mApplication;
private SharedPreferencesLogger mSharedPrefLogger; private SharedPreferencesLogger mSharedPrefLogger;
@Before @Before
public void init() { public void init() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mApplication = ShadowApplication.getInstance(); FakeFeatureFactory.setupForTest(mContext);
Context context = mApplication.getApplicationContext(); mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mMetricsFeature = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); mMetricsFeature = mFactory.metricsFeatureProvider;
((MetricsFeatureProviderImpl) mMetricsFeature).addLogWriter(mLogWriter);
mSharedPrefLogger = new SharedPreferencesLogger( mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG);
mApplication.getApplicationContext(), TEST_TAG);
} }
@Test @Test
@@ -71,7 +71,7 @@ public class SharedPreferenceLoggerTest {
editor.putInt(TEST_KEY, 2); editor.putInt(TEST_KEY, 2);
editor.putInt(TEST_KEY, 2); editor.putInt(TEST_KEY, 2);
verify(mLogWriter, times(6)).count(any(Context.class), anyString(), anyInt()); verify(mMetricsFeature, times(6)).count(any(Context.class), anyString(), anyInt());
} }
@Test @Test
@@ -83,7 +83,7 @@ public class SharedPreferenceLoggerTest {
editor.putBoolean(TEST_KEY, false); editor.putBoolean(TEST_KEY, false);
editor.putBoolean(TEST_KEY, false); editor.putBoolean(TEST_KEY, false);
verify(mLogWriter, times(4)).count(any(Context.class), anyString(), anyInt()); verify(mMetricsFeature, times(4)).count(any(Context.class), anyString(), anyInt());
} }
@Test @Test
@@ -95,7 +95,7 @@ public class SharedPreferenceLoggerTest {
editor.putLong(TEST_KEY, 1); editor.putLong(TEST_KEY, 1);
editor.putLong(TEST_KEY, 2); editor.putLong(TEST_KEY, 2);
verify(mLogWriter, times(4)).count(any(Context.class), anyString(), anyInt()); verify(mMetricsFeature, times(4)).count(any(Context.class), anyString(), anyInt());
} }
@Test @Test
@@ -107,7 +107,7 @@ public class SharedPreferenceLoggerTest {
editor.putFloat(TEST_KEY, 1); editor.putFloat(TEST_KEY, 1);
editor.putFloat(TEST_KEY, 2); editor.putFloat(TEST_KEY, 2);
verify(mLogWriter, times(4)).count(any(Context.class), anyString(), anyInt()); verify(mMetricsFeature, times(4)).count(any(Context.class), anyString(), anyInt());
} }
} }

View File

@@ -35,23 +35,37 @@ import static com.google.common.truth.Truth.assertWithMessage;
public class SearchIndexProviderCodeInspector extends CodeInspector { public class SearchIndexProviderCodeInspector extends CodeInspector {
private static final String TAG = "SearchCodeInspector"; private static final String TAG = "SearchCodeInspector";
private final List<String> notImplementingIndexableWhitelist; private static final String NOT_IMPLEMENTING_INDEXABLE_ERROR =
private final List<String> notImplementingIndexProviderWhitelist; "SettingsPreferenceFragment should implement Indexable, but these are not:\n";
private static final String NOT_CONTAINING_PROVIDER_OBJECT_ERROR =
"Indexable should have public field " + Index.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER
+ " but these are not:\n";
private static final String NOT_IN_INDEXABLE_PROVIDER_REGISTRY =
"Class containing " + Index.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER + " must be added to "
+ SearchIndexableResources.class.getName() + " but these are not: \n";
private final List<String> notImplementingIndexableGrandfatherList;
private final List<String> notImplementingIndexProviderGrandfatherList;
private final List<String> notInSearchIndexableRegistryGrandfatherList;
public SearchIndexProviderCodeInspector(List<Class<?>> classes) { public SearchIndexProviderCodeInspector(List<Class<?>> classes) {
super(classes); super(classes);
notImplementingIndexableWhitelist = new ArrayList<>(); notImplementingIndexableGrandfatherList = new ArrayList<>();
notImplementingIndexProviderWhitelist = new ArrayList<>(); notImplementingIndexProviderGrandfatherList = new ArrayList<>();
initializeGrandfatherList(notImplementingIndexableWhitelist, notInSearchIndexableRegistryGrandfatherList = new ArrayList<>();
initializeGrandfatherList(notImplementingIndexableGrandfatherList,
"grandfather_not_implementing_indexable"); "grandfather_not_implementing_indexable");
initializeGrandfatherList(notImplementingIndexProviderWhitelist, initializeGrandfatherList(notImplementingIndexProviderGrandfatherList,
"grandfather_not_implementing_index_provider"); "grandfather_not_implementing_index_provider");
initializeGrandfatherList(notInSearchIndexableRegistryGrandfatherList,
"grandfather_not_in_search_index_provider_registry");
} }
@Override @Override
public void run() { public void run() {
final Set<String> notImplementingIndexable = new ArraySet<>(); final Set<String> notImplementingIndexable = new ArraySet<>();
final Set<String> notImplementingIndexProvider = new ArraySet<>(); final Set<String> notImplementingIndexProvider = new ArraySet<>();
final Set<String> notInSearchProviderRegistry = new ArraySet<>();
for (Class clazz : mClasses) { for (Class clazz : mClasses) {
if (!isConcreteSettingsClass(clazz)) { if (!isConcreteSettingsClass(clazz)) {
@@ -64,44 +78,59 @@ public class SearchIndexProviderCodeInspector extends CodeInspector {
} }
// If it's a SettingsPreferenceFragment, it must also be Indexable. // If it's a SettingsPreferenceFragment, it must also be Indexable.
final boolean implementsIndexable = Indexable.class.isAssignableFrom(clazz); final boolean implementsIndexable = Indexable.class.isAssignableFrom(clazz);
if (!implementsIndexable && !notImplementingIndexableWhitelist.contains(className)) { if (!implementsIndexable
&& !notImplementingIndexableGrandfatherList.contains(className)) {
notImplementingIndexable.add(className); notImplementingIndexable.add(className);
} }
final boolean hasSearchIndexProvider = hasSearchIndexProvider(clazz);
// If it implements Indexable, it must also implement the index provider field. // If it implements Indexable, it must also implement the index provider field.
if (implementsIndexable && !hasSearchIndexProvider(clazz) if (implementsIndexable && !hasSearchIndexProvider
&& !notImplementingIndexProviderWhitelist.contains(className)) { && !notImplementingIndexProviderGrandfatherList.contains(className)) {
notImplementingIndexProvider.add(className); notImplementingIndexProvider.add(className);
} }
if (hasSearchIndexProvider
&& SearchIndexableResources.getResourceByName(className) == null
&& !notInSearchIndexableRegistryGrandfatherList.contains(className)) {
notInSearchProviderRegistry.add(className);
}
} }
// Build error messages // Build error messages
final StringBuilder indexableError = new StringBuilder( final String indexableError = buildErrorMessage(NOT_IMPLEMENTING_INDEXABLE_ERROR,
"SettingsPreferenceFragment should implement Indexable, but these are not:\n"); notImplementingIndexable);
for (String c : notImplementingIndexable) { final String indexProviderError = buildErrorMessage(NOT_CONTAINING_PROVIDER_OBJECT_ERROR,
indexableError.append(c).append("\n"); notImplementingIndexProvider);
} final String notInProviderRegistryError =
final StringBuilder indexProviderError = new StringBuilder( buildErrorMessage(NOT_IN_INDEXABLE_PROVIDER_REGISTRY, notInSearchProviderRegistry);
"Indexable should have public field " + Index.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER assertWithMessage(indexableError)
+ " but these are not:\n"); .that(notImplementingIndexable)
for (String c : notImplementingIndexProvider) { .isEmpty();
indexProviderError.append(c).append("\n");
}
assertWithMessage(indexableError.toString())
.that(notImplementingIndexable.isEmpty())
.isTrue();
assertWithMessage(indexProviderError.toString()) assertWithMessage(indexProviderError.toString())
.that(notImplementingIndexProvider.isEmpty()) .that(notImplementingIndexProvider)
.isTrue(); .isEmpty();
assertWithMessage(notInProviderRegistryError.toString())
.that(notInSearchProviderRegistry)
.isEmpty();
} }
private boolean hasSearchIndexProvider(Class clazz) { private boolean hasSearchIndexProvider(Class clazz) {
try { try {
final Field f = clazz.getField(Index.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER); final Field f = clazz.getField(Index.FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
return f != null; return f != null;
} catch (NoClassDefFoundError e) {
// Cannot find class def, ignore
return true;
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
Log.e(TAG, "error fetching search provider from class " + clazz.getName()); Log.e(TAG, "error fetching search provider from class " + clazz.getName());
return false; return false;
} }
} }
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();
}
} }