diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java index f651056ad90..3a4d0c2d49c 100644 --- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java +++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java @@ -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. @@ -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 diff --git a/src/com/android/settings/overlay/SurveyFeatureProvider.java b/src/com/android/settings/overlay/SurveyFeatureProvider.java index 79e907f74b3..907bed8f7bf 100644 --- a/src/com/android/settings/overlay/SurveyFeatureProvider.java +++ b/src/com/android/settings/overlay/SurveyFeatureProvider.java @@ -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); } diff --git a/src/com/android/settings/survey/SurveyMixin.java b/src/com/android/settings/survey/SurveyMixin.java new file mode 100644 index 00000000000..5e0754a68cc --- /dev/null +++ b/src/com/android/settings/survey/SurveyMixin.java @@ -0,0 +1,82 @@ +/* + * 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); + } + } +} diff --git a/tests/robotests/src/com/android/settings/survey/SurveyMixinTest.java b/tests/robotests/src/com/android/settings/survey/SurveyMixinTest.java new file mode 100644 index 00000000000..20c50cea7e4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/survey/SurveyMixinTest.java @@ -0,0 +1,143 @@ +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()); + } + +}