/* * 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); displayIntent.setData(mContact.getContactLookupUri()); 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 CREATOR = new Parcelable.Creator() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } }