Loading src/com/android/settings/core/InstrumentedPreferenceFragment.java +2 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.core.lifecycle.ObservablePreferenceFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PreferenceDividerDecoration; import com.android.settings.survey.SurveyMixin; /** * Instrumented fragment that logs visibility state. Loading Loading @@ -61,6 +62,7 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc public InstrumentedPreferenceFragment() { // Mixin that logs visibility change for activity. getLifecycle().addObserver(new VisibilityLoggerMixin(getMetricsCategory())); getLifecycle().addObserver(new SurveyMixin(this, getClass().getSimpleName())); } @Override Loading src/com/android/settings/overlay/SurveyFeatureProvider.java +33 −3 Original line number Diff line number Diff line Loading @@ -40,16 +40,46 @@ public interface SurveyFeatureProvider { * * @param activity The host activity to show the survey in. * @param surveyId A unique Id representing a survey to download. * @return A boolean indicating if a survey was shown or not. */ void showSurveyIfAvailable(Activity activity, String surveyId); boolean showSurveyIfAvailable(Activity activity, String surveyId); /** * A helper method to get the surveyId. Implementers should create a mapping of * keys to surveyIds and provide them via this function. * * @param context A valid context. * @param key The key to get the surveyId for. * @param simpleKey The simple name of the key to get the surveyId for. * @return The unique Id as a string or null on error. */ String getSurveyId(Context context, String key); String getSurveyId(Context context, String simpleKey); /** * Removes the survey for {@code siteId} if it expired, then returns the expiration date (as a * unix timestamp) for the remaining survey should it exist and be ready to show. Returns -1 if * no valid survey exists after removing the potentially expired one. * * @param context the calling context. * @param surveyId the site ID. * @return the unix timestamp for the available survey for the given {@coe siteId} or -1 if * there is none available. */ long getSurveyExpirationDate(Context context, String surveyId); /** * Registers an activity to show surveys/prompts as soon as they are downloaded. The receiver * should be unregistered prior to destroying the activity to avoid undefined behavior by * calling {@link #unregisterReceiver(Activity, BroadcastReceiver)}. * @param activity The activity that should show surveys once they are downloaded. * @return the broadcast receiver listening for survey downloads. Must be unregistered before * leaving the activity. */ BroadcastReceiver createAndRegisterReceiver(Activity activity); /** * Unregisters the broadcast receiver for this activity. Should only be called once per activity * after a call to {@link #createAndRegisterReceiver(Activity)}. * @param activity The activity that was used to register the BroadcastReceiver. */ void unregisterReceiver(Activity activity, BroadcastReceiver receiver); } src/com/android/settings/survey/SurveyMixin.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.survey; import android.app.Activity; import android.content.BroadcastReceiver; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.lifecycle.LifecycleObserver; import com.android.settings.core.lifecycle.events.OnPause; import com.android.settings.core.lifecycle.events.OnResume; import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.SurveyFeatureProvider; /** * attaches extra, survey related work to the onResume method of registered observable classes * in settings. This allows new classes to automatically support settings provided the extend * one of the relevant classes in com.android.settings.lifecycle. */ public class SurveyMixin implements LifecycleObserver, OnResume, OnPause { private String mName; private InstrumentedPreferenceFragment mFragment; private BroadcastReceiver mReceiver; /** * A mixin that attempts to perform survey related tasks right before onResume is called * in a Settings PreferenceFragment. This will allow for remote updating and creation of * surveys. * @param fragment The fragment that this mixin will be attached to. * @param fragmentName The simple name of the fragment. */ public SurveyMixin(InstrumentedPreferenceFragment fragment, String fragmentName) { mName = fragmentName; mFragment = fragment; } @Override public void onResume() { Activity activity = mFragment.getActivity(); // guard against the activity not existing yet or the feature being disabled if (activity != null) { SurveyFeatureProvider provider = FeatureFactory.getFactory(activity).getSurveyFeatureProvider(activity); if (provider != null) { // Try to download a survey if there is none available, show the survey otherwise String id = provider.getSurveyId(activity, mName); if (provider.getSurveyExpirationDate(activity, id) <= -1) { // register the receiver to show the survey on completion. mReceiver = provider.createAndRegisterReceiver(activity); provider.downloadSurvey(activity, id, "fakeData"); } else { provider.showSurveyIfAvailable(activity, id); } } } } @Override public void onPause() { Activity activity = mFragment.getActivity(); if (mReceiver != null && activity != null) { SurveyFeatureProvider provider = FeatureFactory.getFactory(activity).getSurveyFeatureProvider(activity); provider.unregisterReceiver(activity, mReceiver); } } } tests/robotests/src/com/android/settings/survey/SurveyMixinTest.java 0 → 100644 +143 −0 Original line number Diff line number Diff line package com.android.settings.survey; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; 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.Activity; import android.content.BroadcastReceiver; import android.content.Context; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.overlay.SurveyFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SurveyMixinTest { private static final String FAKE_KEY = "fake_key"; private static final String FAKE_SURVEY_ID = "fake_id"; private FakeFeatureFactory mFactory; private Context mContext; private SurveyFeatureProvider mProvider; @Mock private BroadcastReceiver mReceiver; @Mock private InstrumentedPreferenceFragment mFragment; @Before public void setUp() { // set up the fakefeature factory to mock out the survey provider MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application.getApplicationContext()); FakeFeatureFactory.setupForTest(mContext); mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); mProvider = mFactory.getSurveyFeatureProvider(mContext); when(mProvider.getSurveyId(any(), eq(FAKE_KEY))).thenReturn(FAKE_SURVEY_ID); } @Test public void onResume_triesRegisteringReceiverAndDownloadingWhenNoSurveyDetected() { // Pretend there is no survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that is starting up Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify that a download was attempted verify(mProvider, times(1)).downloadSurvey(any(), any(), any()); // Verify that we registered a receiver for download completion broadcasts verify(mProvider, times(1)).createAndRegisterReceiver(any()); // Verify we did not try to show a survey verify(mProvider, never()).showSurveyIfAvailable(any(), any()); } @Test public void onResume_triesShowingSurveyWhenOneIsPresent() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(0L); // Pretend we are an activity that is starting up Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify that a download was not attempted verify(mProvider, never()).downloadSurvey(any(), any(), any()); // Verify that we did not register a receiver verify(mProvider, never()).createAndRegisterReceiver(any()); // Verify we tried to show a survey verify(mProvider, times(1)).showSurveyIfAvailable(any(), any()); } @Test public void onResume_doesNothingWhenActivityIsNull() { // Pretend the activity died somewhere in the process when(mFragment.getActivity()).thenReturn(null); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify we don't try showing or downloading a survey verify(mProvider, never()).showSurveyIfAvailable(any(), any()); verify(mProvider, never()).downloadSurvey(any(), any(), any()); } @Test public void onPause_removesReceiverWhenInstantiated() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that starts and stops Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); when(mProvider.createAndRegisterReceiver(any())).thenReturn(mReceiver); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); mixin.onPause(); // Verify we remove the receiver verify(mProvider, times(1)).unregisterReceiver(any(), any()); } @Test public void onPause_doesNothingWhenActivityOrReceiverNull() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that fails to create a receiver properly Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onPause(); // Verify we do nothing verify(mProvider, never()).unregisterReceiver(any(), any()); // pretend the activity died before onPause when(mFragment.getActivity()).thenReturn(null); mixin.onPause(); // Verify we do nothing verify(mProvider, never()).unregisterReceiver(any(), any()); } } Loading
src/com/android/settings/core/InstrumentedPreferenceFragment.java +2 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.android.settings.core.instrumentation.VisibilityLoggerMixin; import com.android.settings.core.lifecycle.ObservablePreferenceFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PreferenceDividerDecoration; import com.android.settings.survey.SurveyMixin; /** * Instrumented fragment that logs visibility state. Loading Loading @@ -61,6 +62,7 @@ public abstract class InstrumentedPreferenceFragment extends ObservablePreferenc public InstrumentedPreferenceFragment() { // Mixin that logs visibility change for activity. getLifecycle().addObserver(new VisibilityLoggerMixin(getMetricsCategory())); getLifecycle().addObserver(new SurveyMixin(this, getClass().getSimpleName())); } @Override Loading
src/com/android/settings/overlay/SurveyFeatureProvider.java +33 −3 Original line number Diff line number Diff line Loading @@ -40,16 +40,46 @@ public interface SurveyFeatureProvider { * * @param activity The host activity to show the survey in. * @param surveyId A unique Id representing a survey to download. * @return A boolean indicating if a survey was shown or not. */ void showSurveyIfAvailable(Activity activity, String surveyId); boolean showSurveyIfAvailable(Activity activity, String surveyId); /** * A helper method to get the surveyId. Implementers should create a mapping of * keys to surveyIds and provide them via this function. * * @param context A valid context. * @param key The key to get the surveyId for. * @param simpleKey The simple name of the key to get the surveyId for. * @return The unique Id as a string or null on error. */ String getSurveyId(Context context, String key); String getSurveyId(Context context, String simpleKey); /** * Removes the survey for {@code siteId} if it expired, then returns the expiration date (as a * unix timestamp) for the remaining survey should it exist and be ready to show. Returns -1 if * no valid survey exists after removing the potentially expired one. * * @param context the calling context. * @param surveyId the site ID. * @return the unix timestamp for the available survey for the given {@coe siteId} or -1 if * there is none available. */ long getSurveyExpirationDate(Context context, String surveyId); /** * Registers an activity to show surveys/prompts as soon as they are downloaded. The receiver * should be unregistered prior to destroying the activity to avoid undefined behavior by * calling {@link #unregisterReceiver(Activity, BroadcastReceiver)}. * @param activity The activity that should show surveys once they are downloaded. * @return the broadcast receiver listening for survey downloads. Must be unregistered before * leaving the activity. */ BroadcastReceiver createAndRegisterReceiver(Activity activity); /** * Unregisters the broadcast receiver for this activity. Should only be called once per activity * after a call to {@link #createAndRegisterReceiver(Activity)}. * @param activity The activity that was used to register the BroadcastReceiver. */ void unregisterReceiver(Activity activity, BroadcastReceiver receiver); }
src/com/android/settings/survey/SurveyMixin.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.survey; import android.app.Activity; import android.content.BroadcastReceiver; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.core.lifecycle.LifecycleObserver; import com.android.settings.core.lifecycle.events.OnPause; import com.android.settings.core.lifecycle.events.OnResume; import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.SurveyFeatureProvider; /** * attaches extra, survey related work to the onResume method of registered observable classes * in settings. This allows new classes to automatically support settings provided the extend * one of the relevant classes in com.android.settings.lifecycle. */ public class SurveyMixin implements LifecycleObserver, OnResume, OnPause { private String mName; private InstrumentedPreferenceFragment mFragment; private BroadcastReceiver mReceiver; /** * A mixin that attempts to perform survey related tasks right before onResume is called * in a Settings PreferenceFragment. This will allow for remote updating and creation of * surveys. * @param fragment The fragment that this mixin will be attached to. * @param fragmentName The simple name of the fragment. */ public SurveyMixin(InstrumentedPreferenceFragment fragment, String fragmentName) { mName = fragmentName; mFragment = fragment; } @Override public void onResume() { Activity activity = mFragment.getActivity(); // guard against the activity not existing yet or the feature being disabled if (activity != null) { SurveyFeatureProvider provider = FeatureFactory.getFactory(activity).getSurveyFeatureProvider(activity); if (provider != null) { // Try to download a survey if there is none available, show the survey otherwise String id = provider.getSurveyId(activity, mName); if (provider.getSurveyExpirationDate(activity, id) <= -1) { // register the receiver to show the survey on completion. mReceiver = provider.createAndRegisterReceiver(activity); provider.downloadSurvey(activity, id, "fakeData"); } else { provider.showSurveyIfAvailable(activity, id); } } } } @Override public void onPause() { Activity activity = mFragment.getActivity(); if (mReceiver != null && activity != null) { SurveyFeatureProvider provider = FeatureFactory.getFactory(activity).getSurveyFeatureProvider(activity); provider.unregisterReceiver(activity, mReceiver); } } }
tests/robotests/src/com/android/settings/survey/SurveyMixinTest.java 0 → 100644 +143 −0 Original line number Diff line number Diff line package com.android.settings.survey; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; 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.Activity; import android.content.BroadcastReceiver; import android.content.Context; import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.core.InstrumentedPreferenceFragment; import com.android.settings.overlay.SurveyFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class SurveyMixinTest { private static final String FAKE_KEY = "fake_key"; private static final String FAKE_SURVEY_ID = "fake_id"; private FakeFeatureFactory mFactory; private Context mContext; private SurveyFeatureProvider mProvider; @Mock private BroadcastReceiver mReceiver; @Mock private InstrumentedPreferenceFragment mFragment; @Before public void setUp() { // set up the fakefeature factory to mock out the survey provider MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application.getApplicationContext()); FakeFeatureFactory.setupForTest(mContext); mFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext); mProvider = mFactory.getSurveyFeatureProvider(mContext); when(mProvider.getSurveyId(any(), eq(FAKE_KEY))).thenReturn(FAKE_SURVEY_ID); } @Test public void onResume_triesRegisteringReceiverAndDownloadingWhenNoSurveyDetected() { // Pretend there is no survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that is starting up Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify that a download was attempted verify(mProvider, times(1)).downloadSurvey(any(), any(), any()); // Verify that we registered a receiver for download completion broadcasts verify(mProvider, times(1)).createAndRegisterReceiver(any()); // Verify we did not try to show a survey verify(mProvider, never()).showSurveyIfAvailable(any(), any()); } @Test public void onResume_triesShowingSurveyWhenOneIsPresent() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(0L); // Pretend we are an activity that is starting up Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify that a download was not attempted verify(mProvider, never()).downloadSurvey(any(), any(), any()); // Verify that we did not register a receiver verify(mProvider, never()).createAndRegisterReceiver(any()); // Verify we tried to show a survey verify(mProvider, times(1)).showSurveyIfAvailable(any(), any()); } @Test public void onResume_doesNothingWhenActivityIsNull() { // Pretend the activity died somewhere in the process when(mFragment.getActivity()).thenReturn(null); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); // Verify we don't try showing or downloading a survey verify(mProvider, never()).showSurveyIfAvailable(any(), any()); verify(mProvider, never()).downloadSurvey(any(), any(), any()); } @Test public void onPause_removesReceiverWhenInstantiated() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that starts and stops Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); when(mProvider.createAndRegisterReceiver(any())).thenReturn(mReceiver); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onResume(); mixin.onPause(); // Verify we remove the receiver verify(mProvider, times(1)).unregisterReceiver(any(), any()); } @Test public void onPause_doesNothingWhenActivityOrReceiverNull() { // Pretend there is a survey in memory when(mProvider.getSurveyExpirationDate(any(), any())).thenReturn(-1L); // Pretend we are an activity that fails to create a receiver properly Activity temp = Robolectric.setupActivity(Activity.class); when(mFragment.getActivity()).thenReturn(temp); SurveyMixin mixin = new SurveyMixin(mFragment, FAKE_KEY); mixin.onPause(); // Verify we do nothing verify(mProvider, never()).unregisterReceiver(any(), any()); // pretend the activity died before onPause when(mFragment.getActivity()).thenReturn(null); mixin.onPause(); // Verify we do nothing verify(mProvider, never()).unregisterReceiver(any(), any()); } }