diff options
author | Yorke Lee <yorkelee@google.com> | 2015-10-02 16:36:59 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-10-02 16:36:59 +0000 |
commit | 3fb401435986fb0a8ef22a1293fe65913d6738f3 (patch) | |
tree | 8be2ccbb9030e267e4baa3878ed3e83e08ea4941 | |
parent | 9ecfefeabfc73305a97eee0db81a44530b834046 (diff) | |
parent | 2159803d76ee72cb2561b56c17c377f19e5fdf37 (diff) |
am 2159803d: Merge "First pass for Dialer onboarding flow" into ub-contactsdialer-a-dev
* commit '2159803d76ee72cb2561b56c17c377f19e5fdf37':
First pass for Dialer onboarding flow
-rw-r--r-- | AndroidManifest.xml | 6 | ||||
-rw-r--r-- | res/layout/onboarding_activity.xml | 21 | ||||
-rw-r--r-- | res/layout/onboarding_screen_fragment.xml | 66 | ||||
-rw-r--r-- | res/values/colors.xml | 5 | ||||
-rw-r--r-- | res/values/strings.xml | 18 | ||||
-rw-r--r-- | res/values/styles.xml | 7 | ||||
-rw-r--r-- | src/com/android/dialer/onboard/OnboardingActivity.java | 245 | ||||
-rw-r--r-- | src/com/android/dialer/onboard/OnboardingController.java | 85 | ||||
-rw-r--r-- | src/com/android/dialer/onboard/OnboardingFragment.java | 93 | ||||
-rw-r--r-- | src/com/android/dialer/onboard/PermissionsChecker.java | 27 | ||||
-rw-r--r-- | tests/Android.mk | 3 | ||||
-rw-r--r-- | tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java | 57 | ||||
-rw-r--r-- | tests/src/com/android/dialer/onboard/OnboardingControllerTest.java | 176 | ||||
-rw-r--r-- | tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java | 65 |
14 files changed, 874 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e58d2ce9c..94333eb1e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -244,6 +244,12 @@ <activity android:name="com.android.contacts.common.vcard.ExportVCardActivity" android:theme="@style/BackgroundOnlyTheme"/> + <activity + android:name="com.android.dialer.onboard.OnboardingActivity" + android:theme="@style/OnboardingFlowTheme" + android:screenOrientation="nosensor" + android:exported="false" /> + <service android:name="com.android.contacts.common.vcard.VCardService" android:exported="false"/> diff --git a/res/layout/onboarding_activity.xml b/res/layout/onboarding_activity.xml new file mode 100644 index 000000000..a893ce4e7 --- /dev/null +++ b/res/layout/onboarding_activity.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/onboarding_fragment_container"> +</FrameLayout> diff --git a/res/layout/onboarding_screen_fragment.xml b/res/layout/onboarding_screen_fragment.xml new file mode 100644 index 000000000..f4136aed7 --- /dev/null +++ b/res/layout/onboarding_screen_fragment.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="20dp" > + + <TextView + android:id="@+id/onboarding_screen_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_above="@+id/onboarding_screen_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textColor="@color/onboarding_primary_text_color" /> + + <TextView + android:id="@id/onboarding_screen_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_above="@+id/onboarding_buttons_container" + android:layout_marginTop="20dp" + android:textColor="@color/onboarding_primary_text_color" /> + + <LinearLayout + android:id="@id/onboarding_buttons_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_marginTop="20dp" > + + <Button + android:id="@+id/onboard_skip_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:text="@string/onboarding_skip_button" + android:textColor="@color/onboarding_primary_text_color" + style="?android:attr/borderlessButtonStyle" /> + + <Button + android:id="@+id/onboard_next_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:text="@string/onboarding_next_button" + android:textColor="@color/onboarding_primary_text_color" + style="?android:attr/borderlessButtonStyle" /> + + </LinearLayout> +</RelativeLayout> diff --git a/res/values/colors.xml b/res/values/colors.xml index a747927d1..ad493e147 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -111,4 +111,9 @@ <color name="delete_icon_tint">#6D6D6D</color> <color name="blocked_number_background">#E0E0E0</color> <color name="blocked_number_accent_color">#42A5F5</color> + + <!-- Colors for onboarding flow --> + <color name="onboarding_primary_text_color">#ffffff</color> + <color name="onboarding_default_dialer_screen_background_color">#e06055</color> + <color name="onboarding_permissions_screen_background_color">#689f38</color> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index d92471e95..e5d32ea6c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -884,4 +884,22 @@ <!-- Shown as a message that notifies the user that the Phone app cannot write to system settings, which is why the system settings app is being launched directly instead.--> <string name="toast_cannot_write_system_settings">Phone app does not have permission to write to system settings.</string> + + <!-- Title of the onboarding screen that asks the user to make Phone the default Phone app --> + <string name="request_default_dialer_screen_title">A better way of calling is calling</string> + + <!-- Content of the onboarding screen that asks the user to make Phone the default Phone app --> + <string name="request_default_dialer_screen_content">Make Phone your default phone app to be able to do things like see who\'s calling you, even when they\'re not in your contacts.</string> + + <!-- Title of the onboarding screen that asks the user to grant us the Contacts and Phone permissions --> + <string name="request_permissions_screen_title">Get talking to your friends and family</string> + + <!-- Content of the onboarding screen that asks the user to grant us the Contacts and Phone permissions --> + <string name="request_permissions_screen_content">Phone will need to access your phone and contacts to make calls to people in your contacts.</string> + + <!-- The label of the button used to skip a screen in the onboarding flow --> + <string name="onboarding_skip_button">Skip</string> + + <!-- The label of the button used to go to the next screen in the onboarding flow --> + <string name="onboarding_next_button">Next</string> </resources> diff --git a/res/values/styles.xml b/res/values/styles.xml index 17b4a2561..86c3ad47d 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -109,6 +109,13 @@ <item name="actionOverflowButtonStyle">@style/DialtactsActionBarOverflowWhite</item> </style> + <style name="OnboardingFlowTheme" parent="DialtactsThemeWithoutActionBarOverlay"> + <item name="android:windowActionBar">false</item> + <item name="windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + <item name="windowNoTitle">true</item> + </style> + <!-- Hide the actionbar title during the activity preview --> <style name="DialtactsActivityTheme" parent="DialtactsTheme"> <!-- Styles that require AppCompat compatibility, remember to update both sets --> diff --git a/src/com/android/dialer/onboard/OnboardingActivity.java b/src/com/android/dialer/onboard/OnboardingActivity.java new file mode 100644 index 000000000..75378e99d --- /dev/null +++ b/src/com/android/dialer/onboard/OnboardingActivity.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import static android.Manifest.permission.CALL_PHONE; +import static android.Manifest.permission.READ_CONTACTS; + +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.telecom.TelecomManager; + +import com.android.contacts.common.util.PermissionsUtil; +import com.android.dialer.TransactionSafeActivity; +import com.android.dialer.onboard.OnboardingController.OnboardingScreen; +import com.android.dialer.onboard.OnboardingController.OnboardingUi; +import com.android.dialer.util.TelecomUtil; +import com.android.dialer.R; + +/** + * Activity hosting the onboarding UX flow that appears when you launch Dialer and you don't have + * the necessary permissions to run the app. + */ +public class OnboardingActivity extends TransactionSafeActivity implements OnboardingUi, + PermissionsChecker, OnboardingFragment.HostInterface { + public static final String KEY_ALREADY_REQUESTED_DEFAULT_DIALER = + "key_already_requested_default_dialer"; + + public static final int SCREEN_DEFAULT_DIALER = 0; + public static final int SCREEN_PERMISSIONS = 1; + public static final int SCREEN_COUNT = 2; + + private OnboardingController mOnboardingController; + + private DefaultDialerOnboardingScreen mDefaultDialerOnboardingScreen; + private PermissionsOnboardingScreen mPermissionsOnboardingScreen; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.onboarding_activity); + mOnboardingController = new OnboardingController(this); + mDefaultDialerOnboardingScreen = new DefaultDialerOnboardingScreen(this); + mPermissionsOnboardingScreen = new PermissionsOnboardingScreen(this); + mOnboardingController.addScreen(mDefaultDialerOnboardingScreen); + mOnboardingController.addScreen(mPermissionsOnboardingScreen); + + mOnboardingController.showNextScreen(); + } + + @Override + public void showScreen(int screenId) { + if (!isSafeToCommitTransactions()) { + return; + } + final Fragment fragment; + switch (screenId) { + case SCREEN_DEFAULT_DIALER: + fragment = mDefaultDialerOnboardingScreen.getFragment(); + break; + case SCREEN_PERMISSIONS: + fragment = mPermissionsOnboardingScreen.getFragment(); + break; + default: + return; + } + + final FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out); + ft.replace(R.id.onboarding_fragment_container, fragment); + ft.commit(); + } + + @Override + public void completeOnboardingFlow() { + final Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.putBoolean(KEY_ALREADY_REQUESTED_DEFAULT_DIALER, true).apply(); + finish(); + } + + @Override + public boolean hasPhonePermissions() { + return PermissionsUtil.hasPhonePermissions(this); + } + + @Override + public boolean hasContactsPermissions() { + return PermissionsUtil.hasContactsPermissions(this); + } + + @Override + public boolean isDefaultOrSystemDialer() { + return TelecomUtil.hasModifyPhoneStatePermission(this); + } + + @Override + public boolean previouslyRequestedDefaultDialer() { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + return preferences.getBoolean(KEY_ALREADY_REQUESTED_DEFAULT_DIALER, false); + } + + /** + * Triggers the screen-specific logic that should occur when the next button is clicked. + */ + @Override + public void onNextClicked(int screenId) { + switch (screenId) { + case SCREEN_DEFAULT_DIALER: + mDefaultDialerOnboardingScreen.onNextClicked(this); + break; + case SCREEN_PERMISSIONS: + mPermissionsOnboardingScreen.onNextClicked(this); + break; + default: + return; + } + } + + @Override + public void onSkipClicked(int screenId) { + mOnboardingController.onScreenResult(screenId, false); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SCREEN_DEFAULT_DIALER + && resultCode == RESULT_OK) { + mOnboardingController.onScreenResult(SCREEN_DEFAULT_DIALER, true); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + boolean allPermissionsGranted = true; + if (requestCode == SCREEN_PERMISSIONS) { + if (permissions.length == 0 && grantResults.length == 0) { + // Cancellation of permissions dialog + allPermissionsGranted = false; + } else { + for (int result : grantResults) { + if (result == PackageManager.PERMISSION_DENIED) { + allPermissionsGranted = false; + } + } + } + + if (allPermissionsGranted) { + mOnboardingController.onScreenResult(SCREEN_PERMISSIONS, true); + } + } + } + + public static class DefaultDialerOnboardingScreen extends OnboardingScreen { + private PermissionsChecker mPermissionsChecker; + + public DefaultDialerOnboardingScreen(PermissionsChecker permissionsChecker) { + mPermissionsChecker = permissionsChecker; + } + + @Override + public boolean shouldShowScreen() { + return !mPermissionsChecker.previouslyRequestedDefaultDialer() + && !mPermissionsChecker.isDefaultOrSystemDialer(); + } + + @Override + public boolean canSkipScreen() { + return true; + } + + public Fragment getFragment() { + return new OnboardingFragment( + SCREEN_DEFAULT_DIALER, + canSkipScreen(), + R.color.onboarding_default_dialer_screen_background_color, + R.string.request_default_dialer_screen_title, + R.string.request_default_dialer_screen_content + ); + } + + @Override + public void onNextClicked(Activity activity) { + final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER); + intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, + activity.getPackageName()); + activity.startActivityForResult(intent, SCREEN_DEFAULT_DIALER); + } + } + + public static class PermissionsOnboardingScreen extends OnboardingScreen { + private PermissionsChecker mPermissionsChecker; + + public PermissionsOnboardingScreen(PermissionsChecker permissionsChecker) { + mPermissionsChecker = permissionsChecker; + } + + @Override + public boolean shouldShowScreen() { + return !(mPermissionsChecker.hasPhonePermissions() + && mPermissionsChecker.hasContactsPermissions()); + } + + @Override + public boolean canSkipScreen() { + return false; + } + + public Fragment getFragment() { + return new OnboardingFragment( + SCREEN_PERMISSIONS, + canSkipScreen(), + R.color.onboarding_permissions_screen_background_color, + R.string.request_permissions_screen_title, + R.string.request_permissions_screen_content + ); + } + + @Override + public void onNextClicked(Activity activity) { + activity.requestPermissions(new String[] {CALL_PHONE, READ_CONTACTS}, + SCREEN_PERMISSIONS); + } + } +} diff --git a/src/com/android/dialer/onboard/OnboardingController.java b/src/com/android/dialer/onboard/OnboardingController.java new file mode 100644 index 000000000..f799479ed --- /dev/null +++ b/src/com/android/dialer/onboard/OnboardingController.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import android.app.Activity; + +import java.util.ArrayList; + +/** + * Class that manages the display of various fragments that show the user prompts asking for + * certain privileged positions. + */ +public class OnboardingController { + public static abstract class OnboardingScreen { + public abstract boolean shouldShowScreen(); + public abstract boolean canSkipScreen(); + public abstract void onNextClicked(Activity activity); + } + + public interface OnboardingUi { + public void showScreen(int screenId); + /** + * Called when all the necessary permissions have been granted and the main activity + * can launch. + */ + public void completeOnboardingFlow(); + } + + private int mCurrentScreen = -1; + private OnboardingUi mOnboardingUi; + private ArrayList<OnboardingScreen> mScreens = new ArrayList<> (); + + public OnboardingController(OnboardingUi onBoardingUi) { + mOnboardingUi = onBoardingUi; + } + + public void addScreen(OnboardingScreen screen) { + mScreens.add(screen); + } + + public void showNextScreen() { + mCurrentScreen++; + + if (mCurrentScreen >= mScreens.size()) { + // Reached the end of onboarding flow + mOnboardingUi.completeOnboardingFlow(); + return; + } + + if (mScreens.get(mCurrentScreen).shouldShowScreen()) { + mOnboardingUi.showScreen(mCurrentScreen); + } else { + showNextScreen(); + } + } + + public void onScreenResult(int screenId, boolean success) { + if (screenId >= mScreens.size()) { + return; + } + + // Show the next screen in the onboarding flow only under the following situations: + // 1) Success was indicated, and the + // 2) The user tried to skip the screen, and the screen can be skipped + if (success && !mScreens.get(mCurrentScreen).shouldShowScreen()) { + showNextScreen(); + } else if (mScreens.get(mCurrentScreen).canSkipScreen()) { + showNextScreen(); + } + } +} diff --git a/src/com/android/dialer/onboard/OnboardingFragment.java b/src/com/android/dialer/onboard/OnboardingFragment.java new file mode 100644 index 000000000..77b265b2c --- /dev/null +++ b/src/com/android/dialer/onboard/OnboardingFragment.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.dialer.R; + +public class OnboardingFragment extends Fragment implements OnClickListener { + public static final String ARG_SCREEN_ID = "arg_screen_id"; + public static final String ARG_CAN_SKIP_SCREEN = "arg_can_skip_screen"; + public static final String ARG_BACKGROUND_COLOR_RESOURCE = "arg_background_color"; + public static final String ARG_TEXT_TITLE_RESOURCE = "arg_text_title_resource"; + public static final String ARG_TEXT_CONTENT_RESOURCE = "arg_text_content_resource"; + + private int mScreenId; + + public interface HostInterface { + public void onNextClicked(int screenId); + public void onSkipClicked(int screenId); + } + + public OnboardingFragment() {} + + public OnboardingFragment(int screenId, boolean canSkipScreen, int backgroundColorResourceId, + int textTitleResourceId, int textContentResourceId) { + final Bundle args = new Bundle(); + args.putInt(ARG_SCREEN_ID, screenId); + args.putBoolean(ARG_CAN_SKIP_SCREEN, canSkipScreen); + args.putInt(ARG_BACKGROUND_COLOR_RESOURCE, backgroundColorResourceId); + args.putInt(ARG_TEXT_TITLE_RESOURCE, textTitleResourceId); + args.putInt(ARG_TEXT_CONTENT_RESOURCE, textContentResourceId); + setArguments(args); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mScreenId = getArguments().getInt(ARG_SCREEN_ID); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.onboarding_screen_fragment, container, false); + view.setBackgroundColor(getResources().getColor( + getArguments().getInt(ARG_BACKGROUND_COLOR_RESOURCE), null)); + ((TextView) view.findViewById(R.id.onboarding_screen_content)). + setText(getArguments().getInt(ARG_TEXT_CONTENT_RESOURCE)); + ((TextView) view.findViewById(R.id.onboarding_screen_title)). + setText(getArguments().getInt(ARG_TEXT_TITLE_RESOURCE)); + if (!getArguments().getBoolean(ARG_CAN_SKIP_SCREEN)) { + view.findViewById(R.id.onboard_skip_button).setVisibility(View.INVISIBLE); + } + + view.findViewById(R.id.onboard_skip_button).setOnClickListener(this); + view.findViewById(R.id.onboard_next_button).setOnClickListener(this); + + return view; + } + + int getScreenId() { + return mScreenId; + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.onboard_skip_button) { + ((HostInterface) getActivity()).onSkipClicked(getScreenId()); + } else if (v.getId() == R.id.onboard_next_button) { + ((HostInterface) getActivity()).onNextClicked(getScreenId()); + } + } +} diff --git a/src/com/android/dialer/onboard/PermissionsChecker.java b/src/com/android/dialer/onboard/PermissionsChecker.java new file mode 100644 index 000000000..78d175e6f --- /dev/null +++ b/src/com/android/dialer/onboard/PermissionsChecker.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +/** + * Defines a mockable interface used to verify whether certain permissions/privileged operations + * are possible. + */ +public interface PermissionsChecker { + public boolean hasPhonePermissions(); + public boolean hasContactsPermissions(); + public boolean isDefaultOrSystemDialer(); + public boolean previouslyRequestedDefaultDialer(); +} diff --git a/tests/Android.mk b/tests/Android.mk index 21beca8fd..07f4f00d9 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -16,6 +16,9 @@ src_dirs := src \ # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs)) +LOCAL_STATIC_JAVA_LIBRARIES += \ + mockito-target + LOCAL_PACKAGE_NAME := DialerTests LOCAL_INSTRUMENTATION_FOR := Dialer diff --git a/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java b/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java new file mode 100644 index 000000000..f9724c14b --- /dev/null +++ b/tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import static org.mockito.Mockito.when; + +import android.test.AndroidTestCase; + +import com.android.dialer.onboard.OnboardingActivity.DefaultDialerOnboardingScreen; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class DefaultDialerOnboardScreenTest extends AndroidTestCase { + private DefaultDialerOnboardingScreen mScreen; + @Mock private PermissionsChecker mPermissionsChecker; + + @Override + protected void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mScreen = new DefaultDialerOnboardingScreen(mPermissionsChecker); + } + + public void testNeverRequestedForDefaultDialer_shouldShowScreen() { + when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(false); + assertTrue(mScreen.shouldShowScreen()); + } + + public void testAlreadyAskedForDefaultDialer_shouldNotShowScreen() { + when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(true); + assertFalse(mScreen.shouldShowScreen()); + } + + public void testAlreadySetAsDefaultDialer_shouldNotShowScreen() { + when(mPermissionsChecker.previouslyRequestedDefaultDialer()).thenReturn(false); + when(mPermissionsChecker.isDefaultOrSystemDialer()).thenReturn(true); + assertFalse(mScreen.shouldShowScreen()); + } + + public void testCanSkipScreen() { + assertTrue(mScreen.canSkipScreen()); + } +} diff --git a/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java b/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java new file mode 100644 index 000000000..d9c8ce5c0 --- /dev/null +++ b/tests/src/com/android/dialer/onboard/OnboardingControllerTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import android.app.Activity; +import android.test.AndroidTestCase; + +public class OnboardingControllerTest extends AndroidTestCase { + private MockOnboardUi mOnboardUi; + private OnboardingController mController; + + public class MockOnboardUi implements OnboardingController.OnboardingUi { + public int currentScreen = -1; + public boolean completedOnboardingFlow = false; + + @Override + public void showScreen(int screenId) { + currentScreen = screenId; + } + + @Override + public void completeOnboardingFlow() { + completedOnboardingFlow = true; + } + } + + public class MockScreen extends OnboardingController.OnboardingScreen { + boolean shouldShowScreen; + boolean canSkipScreen; + + public MockScreen(boolean shouldShowScreen, boolean canSkipScreen) { + this.shouldShowScreen = shouldShowScreen; + this.canSkipScreen = canSkipScreen; + } + + @Override + public boolean shouldShowScreen() { + return shouldShowScreen; + } + + @Override + public boolean canSkipScreen() { + return canSkipScreen; + } + + @Override + public void onNextClicked(Activity activity) { + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mOnboardUi = new MockOnboardUi(); + mController = new OnboardingController(mOnboardUi); + } + + public void testNoScreensToDisplay_OnboardingFlowImmediatelyCompleted() { + mController.showNextScreen(); + assertEquals(-1, mOnboardUi.currentScreen); + assertTrue(mOnboardUi.completedOnboardingFlow); + } + + public void testSkipAllScreens_OnboardingFlowImmediatelyCompleted() { + mController.addScreen(new MockScreen(false /* shouldShowScreen */, + true /* canSkipScreen */)); + mController.addScreen(new MockScreen(false /* shouldShowScreen */, + true /* canSkipScreen */)); + mController.addScreen(new MockScreen(false /* shouldShowScreen */, + true /* canSkipScreen */)); + mController.showNextScreen(); + assertEquals(-1, mOnboardUi.currentScreen); + assertTrue(mOnboardUi.completedOnboardingFlow); + } + + public void testFirstScreenNotNeeded_ShowsSecondScreen() { + mController.addScreen(new MockScreen(false /* shouldShowScreen */, + false /* canSkipScreen */)); + mController.addScreen(new MockScreen(true /* shouldShowScreen */, + false /* canSkipScreen */)); + mController.showNextScreen(); + assertEquals(1, mOnboardUi.currentScreen); + } + + public void testScreenRequired() { + final MockScreen mockScreen = + new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */); + mController.addScreen(mockScreen); + + mController.showNextScreen(); + assertEquals(0, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // User tried to skip an unskippable screen + mController.onScreenResult(0, false); + assertEquals(0, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // User said yes, but the underlying requirements have not been fulfilled yet, so don't + // show the next screen. Should be very rare in practice. + mController.onScreenResult(0, true); + assertEquals(0, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // Requirement has been fulfiled. + mockScreen.shouldShowScreen = false; + mController.onScreenResult(0, true); + assertTrue(mOnboardUi.completedOnboardingFlow); + } + + /** + * Verifies the use case where completing the first screen will provide the necessary conditions + * to skip the second screen as well. + * + * For example, setting the default dialer in the first screen will automatically grant + * permissions such that the second permissions screen is no longer needed. + */ + public void testFirstScreenCompleted_SkipsSecondScreen() { + final MockScreen mockScreen1 = + new MockScreen(true /* shouldShowScreen */, true /* canSkipScreen */); + final MockScreen mockScreen2 = + new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */); + mController.addScreen(mockScreen1); + mController.addScreen(mockScreen2); + + mController.showNextScreen(); + assertEquals(0, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // Screen 1 succeeded, screen 2 is no longer necessary + mockScreen2.shouldShowScreen = false; + mController.onScreenResult(0, true); + assertEquals(0, mOnboardUi.currentScreen); + assertTrue(mOnboardUi.completedOnboardingFlow); + } + + /** + * Verifies the use case where skipping the first screen will proceed to show the second screen + * since the necessary conditions to skip the second screen have not been met. + */ + public void testFirstScreenSkipped_ShowsSecondScreen() { + final MockScreen mockScreen1 = + new MockScreen(true /* shouldShowScreen */, true /* canSkipScreen */); + final MockScreen mockScreen2 = + new MockScreen(true /* shouldShowScreen */, false /* canSkipScreen */); + mController.addScreen(mockScreen1); + mController.addScreen(mockScreen2); + + mController.showNextScreen(); + assertEquals(0, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // Screen 1 skipped + mController.onScreenResult(0, false); + assertEquals(1, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + + // Repeatedly trying to skip screen 2 will not work since it is marked as unskippable. + mController.onScreenResult(1, false); + assertEquals(1, mOnboardUi.currentScreen); + assertFalse(mOnboardUi.completedOnboardingFlow); + } +} diff --git a/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java b/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java new file mode 100644 index 000000000..ff5e3d5ae --- /dev/null +++ b/tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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.dialer.onboard; + +import static org.mockito.Mockito.when; + +import android.test.AndroidTestCase; + +import com.android.dialer.onboard.OnboardingActivity.PermissionsOnboardingScreen; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class PermissionsOnboardScreenTest extends AndroidTestCase { + private PermissionsOnboardingScreen mScreen; + @Mock private PermissionsChecker mPermissionsChecker; + + @Override + protected void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mScreen = new PermissionsOnboardingScreen(mPermissionsChecker); + } + + public void testMissingContactsAndPhonePermissions_shouldShowScreen() { + when(mPermissionsChecker.hasContactsPermissions()).thenReturn(false); + when(mPermissionsChecker.hasPhonePermissions()).thenReturn(false); + assertTrue(mScreen.shouldShowScreen()); + } + + public void testMissingContactsPermission_shouldShowScreen() { + when(mPermissionsChecker.hasContactsPermissions()).thenReturn(false); + when(mPermissionsChecker.hasPhonePermissions()).thenReturn(true); + assertTrue(mScreen.shouldShowScreen()); + } + + public void testMissingPhonePermission_shouldShowScreen() { + when(mPermissionsChecker.hasContactsPermissions()).thenReturn(true); + when(mPermissionsChecker.hasPhonePermissions()).thenReturn(false); + assertTrue(mScreen.shouldShowScreen()); + } + + public void testHasAllPermissions_shouldNotShowScreen() { + when(mPermissionsChecker.hasContactsPermissions()).thenReturn(true); + when(mPermissionsChecker.hasPhonePermissions()).thenReturn(true); + assertFalse(mScreen.shouldShowScreen()); + } + + public void testCanSkipScreen() { + assertFalse(mScreen.canSkipScreen()); + } +} |