Show sync-off alert am: 491cca5773
am: bcb07594eb

Change-Id: I4be709cf51d7053d4af2f9adffb0c7945f334d36
diff --git a/res/layout/contact_list_content.xml b/res/layout/contact_list_content.xml
index 8420b26..39f669f 100644
--- a/res/layout/contact_list_content.xml
+++ b/res/layout/contact_list_content.xml
@@ -28,6 +28,37 @@
     android:visibility="gone"
     android:background="?attr/contact_browser_background" >
 
+    <LinearLayout
+        android:id="@+id/alert_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="@color/alert_background"
+        android:paddingStart="20dp"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/alert_text"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:paddingTop="16dp"
+            android:paddingBottom="16dp"
+            android:layout_weight="1"
+            android:textColor="@android:color/black"
+            android:textSize="16sp"/>
+
+        <ImageView
+            android:id="@+id/alert_dismiss_icon"
+            android:layout_width="56dp"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical"
+            android:contentDescription="@string/dismiss_sync_alert"
+            android:background="?android:attr/selectableItemBackground"
+            android:scaleType="center"
+            android:src="@drawable/ic_cancel_black_24dp" />
+    </LinearLayout>
+
     <!-- Shown only when an Account filter is set.
          - paddingTop should be here to show "shade" effect correctly. -->
     <include layout="@layout/account_filter_header" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 98c522d..e2024fd 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -260,4 +260,7 @@
 
     <!-- tint color for device account icons -->
     <color name="device_account_tint_color">#7f7f7f</color>
+
+    <!-- Background color for sync-off alert. -->
+    <color name="alert_background">#e0e0e0</color>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e644960..3537e54 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1822,4 +1822,24 @@
     <!-- Content description of the cancel navigation icon shown in SIM import dialog toolbar -->
     <string name="sim_import_cancel_content_description">Cancel import</string>
 
+    <!-- Alert for letting user know that their device auto-sync setting is turned off,
+         in case they are wondering why they are not seeing any contact. [CHAR LIMIT=150] -->
+    <string name="auto_sync_off">Auto-sync is off. Tap to turn on.</string>
+
+    <!-- Content description for the "X" image icon for dismissing an alert.[CHAR LIMIT=50] -->
+    <string name="dismiss_sync_alert">Dismiss</string>
+
+    <!-- Alert for letting user know that their account level sync setting is turned off,
+         in case they are wondering why they are not seeing any contact. [CHAR LIMIT=150] -->
+    <string name="account_sync_off">Account sync is off. Tap to turn on.</string>
+
+    <!-- Title of dialog to turn auto-sync on [CHAR LIMIT=100] -->
+    <string name="turn_auto_sync_on_dialog_title">Turn auto-sync on?</string>
+
+    <!-- Text of dialog to turn auto-sync on [CHAR LIMIT=500] -->
+    <string name="turn_auto_sync_on_dialog_body">Changes you make to all apps and accounts,
+        not just Contacts, will be synchronized between the web and your devices.</string>
+
+    <!-- Confirm button text for dialog to turn auto-sync on [CHAR LIMIT=30] -->
+    <string name="turn_auto_sync_on_dialog_confirm_btn">Turn on</string>
 </resources>
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index c8fb863..6e1b7e4 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -26,6 +26,7 @@
 import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -95,7 +96,8 @@
  * Fragment containing a contact list used for browsing (as compared to
  * picking a contact with one of the PICK intents).
  */
-public class DefaultContactBrowseListFragment extends ContactBrowseListFragment {
+public class DefaultContactBrowseListFragment extends ContactBrowseListFragment
+        implements EnableGlobalSyncDialogFragment.Listener {
 
     private static final String TAG = "DefaultListFragment";
     private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!";
@@ -111,6 +113,11 @@
     private TextView mSearchProgressText;
     private SwipeRefreshLayout mSwipeRefreshLayout;
 
+    private View mAlertContainer;
+    private TextView mAlertText;
+    private ImageView mAlertDismissIcon;
+    private int mReasonSyncOff = SyncUtil.SYNC_SETTING_SYNC_ON;
+
     private boolean mContactsAvailable;
     private boolean mEnableDebugMenuOptions;
     private boolean mIsRecreatedInstance;
@@ -172,6 +179,10 @@
                     maybeHideCheckBoxes();
                     mActivity.invalidateOptionsMenu();
                     mActivity.showFabWithAnimation(/* showFab */ true);
+
+                    // Alert user if sync is off and not dismissed before
+                    setSyncOffAlert();
+
                     // Determine whether the account has pullToRefresh feature
                     if (Flags.getInstance(getContext()).getBoolean(Experiments.PULL_TO_REFRESH)) {
                         setSwipeRefreshLayoutEnabledOrNot(getFilter());
@@ -461,6 +472,66 @@
 
         mSearchProgress = getView().findViewById(R.id.search_progress);
         mSearchProgressText = (TextView) mSearchHeaderView.findViewById(R.id.totalContactsText);
+
+        mAlertContainer = getView().findViewById(R.id.alert_container);
+        mAlertText = (TextView) mAlertContainer.findViewById(R.id.alert_text);
+        mAlertDismissIcon = (ImageView) mAlertContainer.findViewById(R.id.alert_dismiss_icon);
+        mAlertText.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                turnSyncOn();
+            }
+        });
+        mAlertDismissIcon.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+
+        mAlertContainer.setVisibility(View.GONE);
+    }
+
+    private void turnSyncOn() {
+        final ContactListFilter filter = getFilter();
+        if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
+                && mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
+            ContentResolver.setSyncAutomatically(
+                    new Account(filter.accountName, filter.accountType),
+                    ContactsContract.AUTHORITY, true);
+            mAlertContainer.setVisibility(View.GONE);
+        } else {
+            final EnableGlobalSyncDialogFragment dialog = new
+                    EnableGlobalSyncDialogFragment();
+            dialog.show(this, filter);
+        }
+    }
+
+    @Override
+    public void onEnableAutoSync(ContactListFilter filter) {
+        // Turn on auto-sync
+        ContentResolver.setMasterSyncAutomatically(true);
+        // Also enable Contacts sync
+        final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(
+                getContext()).getAccounts(/* contactsWritableOnly */ true);
+        final List<Account> syncableAccounts = filter.getSyncableAccounts(accounts);
+        if (syncableAccounts != null && syncableAccounts.size() > 0) {
+            for (Account account : syncableAccounts) {
+                ContentResolver.setSyncAutomatically(new Account(account.name, account.type),
+                        ContactsContract.AUTHORITY, true);
+            }
+        }
+        mAlertContainer.setVisibility(View.GONE);
+    }
+
+    private void dismiss() {
+        if (mReasonSyncOff == SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF) {
+            SharedPreferenceUtil.incNumOfDismissesForAutoSyncOff(getContext());
+        } else if (mReasonSyncOff == SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF) {
+            SharedPreferenceUtil.incNumOfDismissesForAccountSyncOff(
+                    getContext(), getFilter().accountName);
+        }
+        mAlertContainer.setVisibility(View.GONE);
     }
 
     private void initSwipeRefreshLayout() {
@@ -512,6 +583,36 @@
         }
     }
 
+    private void setSyncOffAlert() {
+        final ContactListFilter filter = getFilter();
+        final Account account =  filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT
+                && filter.isGoogleAccountType()
+                ? new Account(filter.accountName, filter.accountType) : null;
+
+        if (account == null && !filter.isContactsFilterType()) {
+            mAlertContainer.setVisibility(View.GONE);
+        } else {
+            mReasonSyncOff = SyncUtil.calculateReasonSyncOff(getContext(), account);
+            final boolean isAlertVisible =
+                    SyncUtil.isAlertVisible(getContext(), account, mReasonSyncOff);
+            setSyncOffMsg(mReasonSyncOff);
+            mAlertContainer.setVisibility(isAlertVisible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private void setSyncOffMsg(int reason) {
+        final Resources resources = getResources();
+        switch (reason) {
+            case SyncUtil.SYNC_SETTING_GLOBAL_SYNC_OFF:
+                mAlertText.setText(resources.getString(R.string.auto_sync_off));
+                break;
+            case SyncUtil.SYNC_SETTING_ACCOUNT_SYNC_OFF:
+                mAlertText.setText(resources.getString(R.string.account_sync_off));
+                break;
+            default:
+        }
+    }
+
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
@@ -711,6 +812,9 @@
         updateListFilter(filter, restoreSelectedUri);
         mActivity.setTitle(AccountFilterUtil.getActionBarTitleForFilter(mActivity, filter));
 
+        // Alert user if sync is off and not dismissed before
+        setSyncOffAlert();
+
         // Determine whether the account has pullToRefresh feature
         if (Flags.getInstance(getContext()).getBoolean(Experiments.PULL_TO_REFRESH)) {
             setSwipeRefreshLayoutEnabledOrNot(filter);
diff --git a/src/com/android/contacts/list/EnableGlobalSyncDialogFragment.java b/src/com/android/contacts/list/EnableGlobalSyncDialogFragment.java
new file mode 100644
index 0000000..46df4ae
--- /dev/null
+++ b/src/com/android/contacts/list/EnableGlobalSyncDialogFragment.java
@@ -0,0 +1,85 @@
+/*
+ * 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.contacts.list;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import com.android.contacts.R;
+import com.android.contacts.common.list.ContactListFilter;
+
+/**
+ * Confirmation dialog for turning global auto-sync setting on.
+ */
+public class EnableGlobalSyncDialogFragment extends DialogFragment{
+
+    private static final String ARG_FILTER = "filter";
+    private ContactListFilter mFilter;
+
+    /**
+     * Callbacks for the dialog host.
+     */
+    public interface Listener {
+
+        /**
+         * Invoked after the user has confirmed that they want to turn on sync.
+         *
+         * @param filter the filter of current contacts list.
+         */
+        void onEnableAutoSync(ContactListFilter filter);
+    }
+
+    public static void show(DefaultContactBrowseListFragment fragment, ContactListFilter filter) {
+        final Bundle args = new Bundle();
+        args.putParcelable(ARG_FILTER, filter);
+
+        final EnableGlobalSyncDialogFragment dialog = new
+                EnableGlobalSyncDialogFragment();
+        dialog.setTargetFragment(fragment, 0);
+        dialog.setArguments(args);
+        dialog.show(fragment.getFragmentManager(), "globalSync");
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mFilter = getArguments().getParcelable(ARG_FILTER);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final Listener targetListener = (Listener) getTargetFragment();
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+        builder.setTitle(R.string.turn_auto_sync_on_dialog_title)
+                .setMessage(R.string.turn_auto_sync_on_dialog_body)
+                .setPositiveButton(R.string.turn_auto_sync_on_dialog_confirm_btn,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                    if (targetListener != null) {
+                                        targetListener.onEnableAutoSync(mFilter);
+                                    }
+                                }
+                            });
+        builder.setNegativeButton(android.R.string.cancel, null);
+        builder.setCancelable(false);
+        return builder.create();
+    }
+}
diff --git a/src/com/android/contacts/util/SharedPreferenceUtil.java b/src/com/android/contacts/util/SharedPreferenceUtil.java
index e33c7aa..3adbcc4 100644
--- a/src/com/android/contacts/util/SharedPreferenceUtil.java
+++ b/src/com/android/contacts/util/SharedPreferenceUtil.java
@@ -30,6 +30,12 @@
     private static final String PREFERENCE_KEY_HAMBURGER_PROMO_TRIGGER_ACTION_HAPPENED_BEFORE =
             "hamburgerPromoTriggerActionHappenedBefore";
 
+    public static final String PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES =
+            "num-of-dismisses-auto-sync-off";
+
+    public static final String PREFERENCE_KEY_ACCOUNT_SYNC_OFF_DISMISSES
+            = "num-of-dismisses-account-sync-off";
+
     public static boolean getHamburgerPromoDisplayedBefore(Context context) {
         return getSharedPreferences(context)
                 .getBoolean(PREFERENCE_KEY_HAMBURGER_PROMO_DISPLAYED_BEFORE, false);
@@ -81,4 +87,48 @@
     private static SharedPreferences getSharedPreferences(Context context) {
         return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
     }
+
+    public static int getNumOfDismissesForAutoSyncOff(Context context) {
+        return getSharedPreferences(context).getInt(PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES, 0);
+    }
+
+    public static void resetNumOfDismissesForAutoSyncOff(Context context) {
+        final int value = getSharedPreferences(context).getInt(
+                PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES, 0);
+        if (value != 0) {
+            getSharedPreferences(context).edit()
+                    .putInt(PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES, 0).apply();
+        }
+    }
+
+    public static void incNumOfDismissesForAutoSyncOff(Context context) {
+        final int value = getSharedPreferences(context).getInt(
+                PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES, 0);
+        getSharedPreferences(context).edit()
+                .putInt(PREFERENCE_KEY_GLOBAL_SYNC_OFF_DISMISSES, value + 1).apply();
+    }
+
+    private static String buildSharedPrefsName(String accountName) {
+        return accountName + "-" + PREFERENCE_KEY_ACCOUNT_SYNC_OFF_DISMISSES;
+    }
+
+    public static int getNumOfDismissesforAccountSyncOff(Context context, String accountName) {
+        return getSharedPreferences(context).getInt(buildSharedPrefsName(accountName), 0);
+    }
+
+    public static void resetNumOfDismissesForAccountSyncOff(Context context, String accountName) {
+        final int value = getSharedPreferences(context).getInt(
+                buildSharedPrefsName(accountName), 0);
+        if (value != 0) {
+            getSharedPreferences(context).edit()
+                    .putInt(buildSharedPrefsName(accountName), 0).apply();
+        }
+    }
+
+    public static void incNumOfDismissesForAccountSyncOff(Context context, String accountName) {
+        final int value = getSharedPreferences(context).getInt(
+                buildSharedPrefsName(accountName), 0);
+        getSharedPreferences(context).edit()
+                .putInt(buildSharedPrefsName(accountName), value + 1).apply();
+    }
 }
diff --git a/src/com/android/contacts/util/SyncUtil.java b/src/com/android/contacts/util/SyncUtil.java
index cef2223..6c17c05 100644
--- a/src/com/android/contacts/util/SyncUtil.java
+++ b/src/com/android/contacts/util/SyncUtil.java
@@ -17,6 +17,7 @@
 
 import android.accounts.Account;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.provider.ContactsContract;
 
 import com.android.contacts.common.model.account.GoogleAccountType;
@@ -29,6 +30,10 @@
 public final class SyncUtil {
     private static final String TAG = "SyncUtil";
 
+    public static final int SYNC_SETTING_SYNC_ON = 0;
+    public static final int SYNC_SETTING_GLOBAL_SYNC_OFF = 1;
+    public static final int SYNC_SETTING_ACCOUNT_SYNC_OFF = 2;
+
     private SyncUtil() {
     }
 
@@ -49,4 +54,40 @@
         }
         return ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) <= 0;
     }
-}
+
+    public static boolean isAlertVisible(Context context, Account account, int reason) {
+        if (reason == SYNC_SETTING_GLOBAL_SYNC_OFF) {
+            return (SharedPreferenceUtil.getNumOfDismissesForAutoSyncOff(context) == 0);
+        } else if (reason == SYNC_SETTING_ACCOUNT_SYNC_OFF && account != null) {
+            return (SharedPreferenceUtil.getNumOfDismissesforAccountSyncOff(
+                    context, account.name) == 0);
+        }
+        return false;
+    }
+
+    public static int calculateReasonSyncOff(Context context, Account account) {
+        // Global sync is turned off
+        if (!ContentResolver.getMasterSyncAutomatically()) {
+            if (account != null) {
+                SharedPreferenceUtil.resetNumOfDismissesForAccountSyncOff(
+                        context, account.name);
+            }
+            return SYNC_SETTING_GLOBAL_SYNC_OFF;
+        }
+
+        // Global sync is on, clear the number of times users has dismissed this
+        // alert so that next time global sync is off, alert gets displayed again.
+        SharedPreferenceUtil.resetNumOfDismissesForAutoSyncOff(context);
+        if (account != null) {
+            // Account level sync is off
+            if (!ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY)) {
+                return SYNC_SETTING_ACCOUNT_SYNC_OFF;
+            }
+            // Account sync is on, clear the number of times users has dismissed this
+            // alert so that next time sync is off, alert gets displayed again.
+            SharedPreferenceUtil.resetNumOfDismissesForAccountSyncOff(
+                    context, account.name);
+        }
+        return SYNC_SETTING_SYNC_ON;
+    }
+}
\ No newline at end of file