Merge "Ask for confirmation before importing from vcard" into ub-contactsdialer-h-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index aeb1a42..015d612 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1378,6 +1378,9 @@
<!-- Action string for selecting a .vcf file to import contacts from [CHAR LIMIT=30] -->
<string name="import_from_vcf_file" product="default">Import from .vcf file</string>
+ <!-- Dialog message asking the user for confirmation before starting to import contacts from a .vcf file. [CHAR LIMIT=NONE] -->
+ <string name="import_from_vcf_file_confirmation_message" product="default">Import contacts from vCard?</string>
+
<!-- Message shown in a Dialog confirming a user's cancel request toward existing vCard import.
The argument is file name for the vCard import the user wants to cancel.
[CHAR LIMIT=128] -->
diff --git a/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java b/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
index df98d12..5bddc77 100644
--- a/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
+++ b/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java
@@ -43,9 +43,11 @@
* to prompt the user for these permissions. Moreover, finish the current activity.
*
* This is designed to be called inside {@link android.app.Activity#onCreate}
+ *
+ * @param isCallerSelf whether the vcard import was started from the contacts app itself.
*/
- public static boolean startPermissionActivity(Activity activity) {
- return startPermissionActivity(activity, REQUIRED_PERMISSIONS,
+ public static boolean startPermissionActivity(Activity activity, boolean isCallerSelf) {
+ return startPermissionActivity(activity, REQUIRED_PERMISSIONS, isCallerSelf,
RequestImportVCardPermissionsActivity.class);
}
}
\ No newline at end of file
diff --git a/src/com/android/contacts/common/activity/RequestPermissionsActivity.java b/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
index 4bbac73..7e75223 100644
--- a/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
+++ b/src/com/android/contacts/common/activity/RequestPermissionsActivity.java
@@ -76,7 +76,11 @@
if (permissions != null && permissions.length > 0
&& isAllGranted(permissions, grantResults)) {
mPreviousActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- startActivity(mPreviousActivityIntent);
+ if (mIsCallerSelf) {
+ startActivityForResult(mPreviousActivityIntent, 0);
+ } else {
+ startActivity(mPreviousActivityIntent);
+ }
finish();
overridePendingTransition(0, 0);
diff --git a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
index 999c545..ef591a5 100644
--- a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
+++ b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java
@@ -45,6 +45,8 @@
protected static final String EXTRA_STARTED_PERMISSIONS_ACTIVITY =
"started_permissions_activity";
+ protected static final String EXTRA_IS_CALLER_SELF = "is_caller_self";
+
private static final int PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1;
/**
@@ -55,10 +57,14 @@
protected Intent mPreviousActivityIntent;
+ /** If true then start the target activity "for result" after permissions are granted. */
+ protected boolean mIsCallerSelf;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPreviousActivityIntent = (Intent) getIntent().getExtras().get(PREVIOUS_ACTIVITY_INTENT);
+ mIsCallerSelf = getIntent().getBooleanExtra(EXTRA_IS_CALLER_SELF, false);
// Only start a requestPermissions() flow when first starting this activity the first time.
// The process is likely to be restarted during the permission flow (necessary to enable
@@ -76,10 +82,17 @@
*/
protected static boolean startPermissionActivity(Activity activity,
String[] requiredPermissions, Class<?> newActivityClass) {
+ return startPermissionActivity(activity, requiredPermissions, /* isCallerSelf */ false,
+ newActivityClass);
+ }
+
+ protected static boolean startPermissionActivity(Activity activity,
+ String[] requiredPermissions, boolean isCallerSelf, Class<?> newActivityClass) {
if (!hasPermissions(activity, requiredPermissions)) {
final Intent intent = new Intent(activity, newActivityClass);
activity.getIntent().putExtra(EXTRA_STARTED_PERMISSIONS_ACTIVITY, true);
intent.putExtra(PREVIOUS_ACTIVITY_INTENT, activity.getIntent());
+ intent.putExtra(EXTRA_IS_CALLER_SELF, isCallerSelf);
activity.startActivity(intent);
activity.finish();
return true;
diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java
index 4dc6ec9..9fca64d 100644
--- a/src/com/android/contacts/common/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java
@@ -16,6 +16,7 @@
package com.android.contacts.common.util;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
@@ -53,53 +54,53 @@
public static class AccountSelectedListener
implements DialogInterface.OnClickListener {
- final private Context mContext;
+ final private Activity mActivity;
final private int mResId;
final private int mSubscriptionId;
final protected List<AccountWithDataSet> mAccountList;
- public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
+ public AccountSelectedListener(Activity activity, List<AccountWithDataSet> accountList,
int resId, int subscriptionId) {
if (accountList == null || accountList.size() == 0) {
Log.e(LOG_TAG, "The size of Account list is 0.");
}
- mContext = context;
+ mActivity = activity;
mAccountList = accountList;
mResId = resId;
mSubscriptionId = subscriptionId;
}
- public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
+ public AccountSelectedListener(Activity activity, List<AccountWithDataSet> accountList,
int resId) {
// Subscription id is only needed for importing from SIM card. We can safely ignore
// its value for SD card importing.
- this(context, accountList, resId, /* subscriptionId = */ -1);
+ this(activity, accountList, resId, /* subscriptionId = */ -1);
}
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
- doImport(mContext, mResId, mAccountList.get(which), mSubscriptionId);
+ doImport(mActivity, mResId, mAccountList.get(which), mSubscriptionId);
}
}
- public static Dialog getSelectAccountDialog(Context context, int resId) {
- return getSelectAccountDialog(context, resId, null, null);
+ public static Dialog getSelectAccountDialog(Activity activity, int resId) {
+ return getSelectAccountDialog(activity, resId, null, null);
}
- public static Dialog getSelectAccountDialog(Context context, int resId,
+ public static Dialog getSelectAccountDialog(Activity activity, int resId,
DialogInterface.OnClickListener onClickListener) {
- return getSelectAccountDialog(context, resId, onClickListener, null);
+ return getSelectAccountDialog(activity, resId, onClickListener, null);
}
/**
* When OnClickListener or OnCancelListener is null, uses a default listener.
* The default OnCancelListener just closes itself with {@link Dialog#dismiss()}.
*/
- public static Dialog getSelectAccountDialog(Context context, int resId,
+ public static Dialog getSelectAccountDialog(Activity activity, int resId,
DialogInterface.OnClickListener onClickListener,
DialogInterface.OnCancelListener onCancelListener) {
- final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
+ final AccountTypeManager accountTypes = AccountTypeManager.getInstance(activity);
final List<AccountWithDataSet> writableAccountList = accountTypes.getAccounts(true);
Log.i(LOG_TAG, "The number of available accounts: " + writableAccountList.size());
@@ -108,12 +109,12 @@
// Wrap our context to inflate list items using correct theme
final Context dialogContext = new ContextThemeWrapper(
- context, android.R.style.Theme_Light);
+ activity, android.R.style.Theme_Light);
final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final ArrayAdapter<AccountWithDataSet> accountAdapter =
new ArrayAdapter<AccountWithDataSet>(
- context, R.layout.account_selector_list_item_condensed, writableAccountList) {
+ activity, R.layout.account_selector_list_item_condensed, writableAccountList) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
@@ -141,7 +142,7 @@
if (onClickListener == null) {
AccountSelectedListener accountSelectedListener =
- new AccountSelectedListener(context, writableAccountList, resId);
+ new AccountSelectedListener(activity, writableAccountList, resId);
onClickListener = accountSelectedListener;
}
if (onCancelListener == null) {
@@ -151,8 +152,8 @@
}
};
}
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- final TextView title = (TextView) View.inflate(context, R.layout.dialog_title, null);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final TextView title = (TextView) View.inflate(activity, R.layout.dialog_title, null);
title.setText(R.string.dialog_new_contact_account);
builder.setCustomTitle(title);
builder.setSingleChoiceItems(accountAdapter, 0, onClickListener);
@@ -161,12 +162,12 @@
return result;
}
- public static void doImport(Context context, int resId, AccountWithDataSet account,
+ public static void doImport(Activity activity, int resId, AccountWithDataSet account,
int subscriptionId) {
if (resId == R.string.import_from_sim) {
- doImportFromSim(context, account, subscriptionId);
+ doImportFromSim(activity, account, subscriptionId);
} else if (resId == R.string.import_from_vcf_file) {
- doImportFromVcfFile(context, account);
+ doImportFromVcfFile(activity, account);
}
}
@@ -184,8 +185,8 @@
context.startActivity(importIntent);
}
- public static void doImportFromVcfFile(Context context, AccountWithDataSet account) {
- Intent importIntent = new Intent(context, ImportVCardActivity.class);
+ public static void doImportFromVcfFile(Activity activity, AccountWithDataSet account) {
+ Intent importIntent = new Intent(activity, ImportVCardActivity.class);
if (account != null) {
importIntent.putExtra("account_name", account.name);
importIntent.putExtra("account_type", account.type);
@@ -198,6 +199,6 @@
}
mVCardShare = false;
mPath = null;
- context.startActivity(importIntent);
+ activity.startActivityForResult(importIntent, 0);
}
}
diff --git a/src/com/android/contacts/common/vcard/ImportVCardActivity.java b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
index 93ab944..d7b64b4 100644
--- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java
@@ -75,7 +75,7 @@
* any Dialog in the instance. So this code is careless about the management around managed
* dialogs stuffs (like how onCreateDialog() is used).
*/
-public class ImportVCardActivity extends Activity {
+public class ImportVCardActivity extends Activity implements ImportVCardDialogFragment.Listener {
private static final String LOG_TAG = "VCardImport";
private static final int SELECT_ACCOUNT = 0;
@@ -546,8 +546,8 @@
// Reading uris from non-storage needs the permission granted from the source intent,
// instead of permissions from RequestImportVCardPermissionActivity. So skipping requesting
// permissions from RequestImportVCardPermissionActivity for uris from non-storage source.
- if (isStorageUri(sourceUri)
- && RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
+ if (isStorageUri(sourceUri) && RequestImportVCardPermissionsActivity
+ .startPermissionActivity(this, isCallerSelf(this))) {
return;
}
@@ -571,7 +571,8 @@
}
// Always request required permission for contacts before importing the vcard.
- if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) {
+ if (RequestImportVCardPermissionsActivity.startPermissionActivity(this,
+ isCallerSelf(this))) {
return;
}
@@ -603,10 +604,44 @@
}
}
+ if (isCallerSelf(this)) {
+ startImport(sourceUri, sourceDisplayName);
+ } else {
+ ImportVCardDialogFragment.show(this, sourceUri, sourceDisplayName);
+ }
+ }
+
+ private static boolean isCallerSelf(Activity activity) {
+ // {@link Activity#getCallingActivity()} is a safer alternative to
+ // {@link Activity#getCallingPackage()} that works around a
+ // framework bug where getCallingPackage() can sometimes return null even when the
+ // current activity *was* in fact launched via a startActivityForResult() call.
+ //
+ // (The bug happens if the task stack needs to be re-created by the framework after
+ // having been killed due to memory pressure or by the "Don't keep activities"
+ // developer option; see bug 7494866 for the full details.)
+ //
+ // Turns out that {@link Activity#getCallingActivity()} *does* return correct info
+ // even in the case where getCallingPackage() is broken, so the workaround is simply
+ // to get the package name from getCallingActivity().getPackageName() instead.
+ final ComponentName callingActivity = activity.getCallingActivity();
+ if (callingActivity == null) return false;
+ final String packageName = callingActivity.getPackageName();
+ if (packageName == null) return false;
+ return packageName.equals(activity.getApplicationContext().getPackageName());
+ }
+
+ @Override
+ public void onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName) {
startImport(sourceUri, sourceDisplayName);
}
@Override
+ public void onImportVCardDenied() {
+ finish();
+ }
+
+ @Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == SELECT_ACCOUNT) {
if (resultCode == Activity.RESULT_OK) {
diff --git a/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java b/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java
new file mode 100644
index 0000000..b1f1c3b
--- /dev/null
+++ b/src/com/android/contacts/common/vcard/ImportVCardDialogFragment.java
@@ -0,0 +1,91 @@
+/*
+ * 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.common.vcard;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.contacts.R;
+
+/** Asks for confirmation before importing contacts from a vcard. */
+public class ImportVCardDialogFragment extends DialogFragment {
+
+ static final String TAG = "importVCardDialog";
+
+ private static final String ARG_SOURCE_URI = "sourceUri";
+ private static final String ARG_SOURCE_DISPLAY_NAME = "sourceDisplayName";
+
+ /** Callbacks for hosts of the {@link ImportVCardDialogFragment}. */
+ public interface Listener {
+
+ /** Invoked after the user has confirmed that contacts should be imported. */
+ void onImportVCardConfirmed(Uri sourceUri, String sourceDisplayName);
+
+ /** Invoked after the user has rejected importing contacts. */
+ void onImportVCardDenied();
+ }
+
+ /** Displays the dialog asking for confirmation before importing contacts. */
+ public static void show(Activity activity, Uri sourceUri,
+ String sourceDisplayName) {
+ if (!(activity instanceof Listener)) {
+ throw new IllegalArgumentException(
+ "Activity must implement " + Listener.class.getName());
+ }
+
+ final Bundle args = new Bundle();
+ args.putParcelable(ARG_SOURCE_URI, sourceUri);
+ args.putString(ARG_SOURCE_DISPLAY_NAME, sourceDisplayName);
+
+ final ImportVCardDialogFragment dialog = new ImportVCardDialogFragment();
+ dialog.setArguments(args);
+ dialog.show(activity.getFragmentManager(), TAG);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Uri sourceUri = getArguments().getParcelable(ARG_SOURCE_URI);
+ final String sourceDisplayName = getArguments().getString(ARG_SOURCE_DISPLAY_NAME);
+
+ return new AlertDialog.Builder(getActivity())
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .setMessage(R.string.import_from_vcf_file_confirmation_message)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ final Listener listener = (Listener) getActivity();
+ if (listener != null) {
+ listener.onImportVCardConfirmed(sourceUri, sourceDisplayName);
+ }
+ }
+ })
+ .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ final Listener listener = (Listener) getActivity();
+ if (listener != null) {
+ listener.onImportVCardDenied();
+ }
+ }
+ })
+ .create();
+ }
+}