| /* |
| * 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.emergency.preferences; |
| |
| import android.app.AlertDialog; |
| import android.content.ActivityNotFoundException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceViewHolder; |
| import android.telecom.TelecomManager; |
| import android.text.BidiFormatter; |
| import android.text.TextDirectionHeuristics; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.View; |
| import android.widget.ImageView; |
| import android.widget.Toast; |
| |
| import com.android.emergency.CircleFramedDrawable; |
| import com.android.emergency.EmergencyContactManager; |
| import com.android.emergency.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto.MetricsEvent; |
| |
| import java.util.List; |
| |
| |
| /** |
| * A {@link Preference} to display or call a contact using the specified URI string. |
| */ |
| public class ContactPreference extends Preference { |
| |
| private static final String TAG = "ContactPreference"; |
| |
| static final ContactFactory DEFAULT_CONTACT_FACTORY = new ContactFactory() { |
| @Override |
| public EmergencyContactManager.Contact getContact(Context context, Uri phoneUri) { |
| return EmergencyContactManager.getContact(context, phoneUri); |
| } |
| }; |
| |
| private final ContactFactory mContactFactory; |
| private EmergencyContactManager.Contact mContact; |
| @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener; |
| @Nullable private AlertDialog mRemoveContactDialog; |
| |
| /** |
| * Listener for removing a contact. |
| */ |
| public interface RemoveContactPreferenceListener { |
| /** |
| * Callback to remove a contact preference. |
| */ |
| void onRemoveContactPreference(ContactPreference preference); |
| } |
| |
| /** |
| * Interface for getting a contact for a phone number Uri. |
| */ |
| public interface ContactFactory { |
| /** |
| * Gets a {@link EmergencyContactManager.Contact} for a phone {@link Uri}. |
| * |
| * @param context The context to use. |
| * @param phoneUri The phone uri. |
| * @return a contact for the given phone uri. |
| */ |
| EmergencyContactManager.Contact getContact(Context context, Uri phoneUri); |
| } |
| |
| public ContactPreference(Context context, AttributeSet attributes) { |
| super(context, attributes); |
| mContactFactory = DEFAULT_CONTACT_FACTORY; |
| } |
| |
| /** |
| * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and |
| * the Uri. |
| */ |
| public ContactPreference(Context context, @NonNull Uri phoneUri) { |
| this(context, phoneUri, DEFAULT_CONTACT_FACTORY); |
| } |
| |
| @VisibleForTesting |
| ContactPreference(Context context, @NonNull Uri phoneUri, |
| @NonNull ContactFactory contactFactory) { |
| super(context); |
| mContactFactory = contactFactory; |
| setOrder(DEFAULT_ORDER); |
| |
| setPhoneUri(phoneUri); |
| |
| setWidgetLayoutResource(R.layout.preference_user_action_widget); |
| setPersistent(false); |
| } |
| |
| public void setPhoneUri(@NonNull Uri phoneUri) { |
| if (mContact != null && !phoneUri.equals(mContact.getPhoneUri()) && |
| mRemoveContactDialog != null) { |
| mRemoveContactDialog.dismiss(); |
| } |
| mContact = mContactFactory.getContact(getContext(), phoneUri); |
| |
| setTitle(mContact.getName()); |
| setKey(mContact.getPhoneUri().toString()); |
| String summary = mContact.getPhoneType() == null ? |
| mContact.getPhoneNumber() : |
| String.format( |
| getContext().getResources().getString(R.string.phone_type_and_phone_number), |
| mContact.getPhoneType(), |
| BidiFormatter.getInstance().unicodeWrap(mContact.getPhoneNumber(), |
| TextDirectionHeuristics.LTR)); |
| setSummary(summary); |
| |
| // Update the message to show the correct name. |
| if (mRemoveContactDialog != null) { |
| mRemoveContactDialog.setMessage( |
| String.format(getContext().getString(R.string.remove_contact), |
| mContact.getName())); |
| } |
| |
| //TODO: Consider doing the following in a non-UI thread. |
| Drawable icon; |
| if (mContact.getPhoto() != null) { |
| icon = new CircleFramedDrawable(mContact.getPhoto(), |
| (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size)); |
| } else { |
| icon = getContext().getDrawable(R.drawable.ic_account_circle_filled_24dp); |
| } |
| setIcon(icon); |
| } |
| |
| /** Listener to be informed when a contact preference should be deleted. */ |
| public void setRemoveContactPreferenceListener( |
| RemoveContactPreferenceListener removeContactListener) { |
| mRemoveContactPreferenceListener = removeContactListener; |
| if (mRemoveContactPreferenceListener == null) { |
| mRemoveContactDialog = null; |
| return; |
| } |
| if (mRemoveContactDialog != null) { |
| return; |
| } |
| // Create the remove contact dialog |
| AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); |
| builder.setNegativeButton(getContext().getString(R.string.cancel), null); |
| builder.setPositiveButton(getContext().getString(R.string.remove), |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialogInterface, |
| int which) { |
| if (mRemoveContactPreferenceListener != null) { |
| mRemoveContactPreferenceListener |
| .onRemoveContactPreference(ContactPreference.this); |
| } |
| } |
| }); |
| builder.setMessage(String.format(getContext().getString(R.string.remove_contact), |
| mContact.getName())); |
| mRemoveContactDialog = builder.create(); |
| } |
| |
| @Override |
| public void onBindViewHolder(PreferenceViewHolder holder) { |
| super.onBindViewHolder(holder); |
| View deleteContactIcon = holder.findViewById(R.id.delete_contact); |
| View callContactIcon = holder.findViewById(R.id.call_contact); |
| if (mRemoveContactPreferenceListener == null) { |
| // Default icon is delete, change icon to phone when ContactPreference binding |
| // ViewEmergencyContactsFragment. |
| deleteContactIcon.setVisibility(View.GONE); |
| callContactIcon.setVisibility(View.VISIBLE); |
| } else { |
| deleteContactIcon.setOnClickListener((View view) -> { |
| showRemoveContactDialog(null); |
| }); |
| } |
| } |
| |
| public Uri getPhoneUri() { |
| return mContact.getPhoneUri(); |
| } |
| |
| @VisibleForTesting |
| EmergencyContactManager.Contact getContact() { |
| return mContact; |
| } |
| |
| @VisibleForTesting |
| AlertDialog getRemoveContactDialog() { |
| return mRemoveContactDialog; |
| } |
| |
| /** |
| * Calls the contact. |
| */ |
| public void callContact() { |
| // Use TelecomManager to place the call; this APK has CALL_PRIVILEGED permission so it will |
| // be able to call emergency numbers. |
| TelecomManager tm = (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE); |
| tm.placeCall(Uri.parse("tel:" + mContact.getPhoneNumber()), null); |
| MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT); |
| } |
| |
| /** |
| * Displays a contact card for the contact. |
| */ |
| public void displayContact() { |
| Intent displayIntent = new Intent(Intent.ACTION_VIEW) |
| .setData(mContact.getContactLookupUri()) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| try { |
| getContext().startActivity(displayIntent); |
| } catch (ActivityNotFoundException e) { |
| Toast.makeText(getContext(), |
| getContext().getString(R.string.fail_display_contact), |
| Toast.LENGTH_LONG).show(); |
| Log.w(TAG, "No contact app available to display the contact", e); |
| return; |
| } |
| |
| } |
| |
| /** Shows the dialog to remove the contact, restoring it from {@code state} if it's not null. */ |
| private void showRemoveContactDialog(Bundle state) { |
| if (mRemoveContactDialog == null) { |
| return; |
| } |
| if (state != null) { |
| mRemoveContactDialog.onRestoreInstanceState(state); |
| } |
| mRemoveContactDialog.show(); |
| } |
| |
| @Override |
| protected Parcelable onSaveInstanceState() { |
| final Parcelable superState = super.onSaveInstanceState(); |
| if (mRemoveContactDialog == null || !mRemoveContactDialog.isShowing()) { |
| return superState; |
| } |
| final SavedState myState = new SavedState(superState); |
| myState.isDialogShowing = true; |
| myState.dialogBundle = mRemoveContactDialog.onSaveInstanceState(); |
| return myState; |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Parcelable state) { |
| if (state == null || !state.getClass().equals(SavedState.class)) { |
| // Didn't save state for us in onSaveInstanceState |
| super.onRestoreInstanceState(state); |
| return; |
| } |
| SavedState myState = (SavedState) state; |
| super.onRestoreInstanceState(myState.getSuperState()); |
| if (myState.isDialogShowing) { |
| showRemoveContactDialog(myState.dialogBundle); |
| } |
| } |
| |
| private static class SavedState extends BaseSavedState { |
| boolean isDialogShowing; |
| Bundle dialogBundle; |
| |
| public SavedState(Parcel source) { |
| super(source); |
| isDialogShowing = source.readInt() == 1; |
| dialogBundle = source.readBundle(); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| super.writeToParcel(dest, flags); |
| dest.writeInt(isDialogShowing ? 1 : 0); |
| dest.writeBundle(dialogBundle); |
| } |
| |
| public SavedState(Parcelable superState) { |
| super(superState); |
| } |
| |
| public static final Parcelable.Creator<SavedState> CREATOR = |
| new Parcelable.Creator<SavedState>() { |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in); |
| } |
| |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| } |