Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit db42a6ce authored by Salvador Martinez's avatar Salvador Martinez
Browse files

Added survey triggering mechanisms

Surveys can now be triggered on a subset of Settings screens.
Surveys can also be modified/created for this subset of
screens remotely.

Test: RoboTests && JUnitTests
Bug: 27823357
Change-Id: I1534af5573bef6f6c65c9c99b6f5a2917c3325b9
parent 4e8b78a2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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
+33 −3
Original line number Diff line number Diff line
@@ -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);
}
+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);
        }
    }
}
+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());
    }

}