summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorke Lee <yorkelee@google.com>2015-10-02 16:36:59 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2015-10-02 16:36:59 +0000
commit3fb401435986fb0a8ef22a1293fe65913d6738f3 (patch)
tree8be2ccbb9030e267e4baa3878ed3e83e08ea4941
parent9ecfefeabfc73305a97eee0db81a44530b834046 (diff)
parent2159803d76ee72cb2561b56c17c377f19e5fdf37 (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.xml6
-rw-r--r--res/layout/onboarding_activity.xml21
-rw-r--r--res/layout/onboarding_screen_fragment.xml66
-rw-r--r--res/values/colors.xml5
-rw-r--r--res/values/strings.xml18
-rw-r--r--res/values/styles.xml7
-rw-r--r--src/com/android/dialer/onboard/OnboardingActivity.java245
-rw-r--r--src/com/android/dialer/onboard/OnboardingController.java85
-rw-r--r--src/com/android/dialer/onboard/OnboardingFragment.java93
-rw-r--r--src/com/android/dialer/onboard/PermissionsChecker.java27
-rw-r--r--tests/Android.mk3
-rw-r--r--tests/src/com/android/dialer/onboard/DefaultDialerOnboardScreenTest.java57
-rw-r--r--tests/src/com/android/dialer/onboard/OnboardingControllerTest.java176
-rw-r--r--tests/src/com/android/dialer/onboard/PermissionsOnboardScreenTest.java65
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());
+ }
+}