diff options
author | Walter Jang <wjang@google.com> | 2016-11-07 09:24:43 -0800 |
---|---|---|
committer | Feng Yu <feny@google.com> | 2016-12-08 20:51:24 -0800 |
commit | 34d9f2fa13b1d1e9875a11213d4f3428ec377e6e (patch) | |
tree | 84b40d40ec63ab96aada59819aee639a9232432c | |
parent | 1221f818b39e38f98fe4a5e3234ace8a20f6fde1 (diff) | |
download | ContactsCommon-34d9f2fa13b1d1e9875a11213d4f3428ec377e6e.tar.gz |
resolve merge conflicts of 9f523b4 to nyc-devandroid-7.1.1_r61android-7.1.1_r60android-7.1.1_r59nougat-mr1.8-releasenougat-mr1-security-release
Bug: 32219099
Merged-In: Ib141ad682488aeabf6d90e8d169d5fff6df7a8dd
Change-Id: I895b1f9f8ade2dcfa47d1c17e0aaac2edeb75a74
7 files changed, 179 insertions, 29 deletions
diff --git a/res/values/strings.xml b/res/values/strings.xml index da9ef977..7d5d9ab3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -440,6 +440,9 @@ a ren't members of any other group. [CHAR LIMIT=25] --> <!-- 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 8339d73e..137f44d3 100644 --- a/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java +++ b/src/com/android/contacts/common/activity/RequestImportVCardPermissionsActivity.java @@ -46,9 +46,11 @@ public class RequestImportVCardPermissionsActivity extends RequestPermissionsAct * 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 51dec0c5..03caadf2 100644 --- a/src/com/android/contacts/common/activity/RequestPermissionsActivity.java +++ b/src/com/android/contacts/common/activity/RequestPermissionsActivity.java @@ -64,7 +64,11 @@ public class RequestPermissionsActivity extends RequestPermissionsActivityBase { 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); } else { diff --git a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java index 104abbe2..edb2aa5a 100644 --- a/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java +++ b/src/com/android/contacts/common/activity/RequestPermissionsActivityBase.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Trace; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; +import android.widget.Toast; import java.util.ArrayList; import java.util.Arrays; @@ -46,6 +47,8 @@ public abstract class RequestPermissionsActivityBase extends Activity /** Whether the permissions activity was already started. */ protected static final String 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; /** @@ -62,10 +65,14 @@ public abstract class RequestPermissionsActivityBase extends Activity 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 @@ -83,10 +90,17 @@ public abstract class RequestPermissionsActivityBase extends Activity */ 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(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 33685c2f..492b3b56 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 class AccountSelectionUtil { 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 @@ public class AccountSelectionUtil { // 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 @@ public class AccountSelectionUtil { if (onClickListener == null) { AccountSelectedListener accountSelectedListener = - new AccountSelectedListener(context, writableAccountList, resId); + new AccountSelectedListener(activity, writableAccountList, resId); onClickListener = accountSelectedListener; } if (onCancelListener == null) { @@ -151,19 +152,19 @@ public class AccountSelectionUtil { } }; } - return new AlertDialog.Builder(context) + return new AlertDialog.Builder(activity) .setTitle(R.string.dialog_new_contact_account) .setSingleChoiceItems(accountAdapter, 0, onClickListener) .setOnCancelListener(onCancelListener) .create(); } - 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); } } @@ -181,8 +182,8 @@ public class AccountSelectionUtil { 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); @@ -195,6 +196,6 @@ public class AccountSelectionUtil { } 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 9da8c0b7..8f2e8167 100644 --- a/src/com/android/contacts/common/vcard/ImportVCardActivity.java +++ b/src/com/android/contacts/common/vcard/ImportVCardActivity.java @@ -74,7 +74,7 @@ import java.util.List; * 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; @@ -544,8 +544,8 @@ public class ImportVCardActivity extends Activity { // 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; } @@ -569,7 +569,8 @@ public class ImportVCardActivity extends Activity { } // Always request required permission for contacts before importing the vcard. - if (RequestImportVCardPermissionsActivity.startPermissionActivity(this)) { + if (RequestImportVCardPermissionsActivity.startPermissionActivity(this, + isCallerSelf(this))) { return; } @@ -601,10 +602,44 @@ public class ImportVCardActivity extends Activity { } } + 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 00000000..6a437c94 --- /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.common.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(); + } +} |