Add CallHistory activity to CallSubjectDialog, plus others.

- Shows last 5 call subjects.
- Add OnPhoneNumberPickerActionListener for handling callbacks from
search results to trigger making a call with a subject.

Bug: 22685114
Change-Id: I3b1e6660c8fa4b3c289e538d673a82caabb57a73
diff --git a/res/layout/call_subject_history.xml b/res/layout/call_subject_history.xml
new file mode 100644
index 0000000..5f6ef3a
--- /dev/null
+++ b/res/layout/call_subject_history.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent" >
+
+    <ListView
+        android:id="@+id/subject_list"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_alignParentBottom="true"
+        android:background="@color/call_subject_history_background"
+        android:divider="@null"
+        android:elevation="8dp" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/dialog_call_subject.xml b/res/layout/dialog_call_subject.xml
index 3b7addd..005f440 100644
--- a/res/layout/dialog_call_subject.xml
+++ b/res/layout/dialog_call_subject.xml
@@ -19,7 +19,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="wrap_content" >
 
     <LinearLayout
         android:layout_width="match_parent"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 7c21268..0dc85b5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -163,4 +163,7 @@
 
     <!-- Text color for the SEND & CALL button on the call subject dialog. -->
     <color name="call_subject_button">#00c853</color>
+
+    <!-- Background color for the call subject history view. -->
+    <color name="call_subject_history_background">#ffffff</color>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 94916b8..6ab07d3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -98,4 +98,16 @@
         <item name="android:windowFullscreen">true</item>
         <item name="android:windowIsFloating">true</item>
     </style>
+
+    <!-- Theme used for the call subjection history selection activity.  -->
+    <style name="Theme.CallSubjectSelector" parent="android:Theme.Material.Light">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowTranslucentStatus">false</item>
+        <item name="android:windowTranslucentNavigation">false</item>
+        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
 </resources>
diff --git a/src/com/android/contacts/common/CallUtil.java b/src/com/android/contacts/common/CallUtil.java
index 4f94552..a5587ca 100644
--- a/src/com/android/contacts/common/CallUtil.java
+++ b/src/com/android/contacts/common/CallUtil.java
@@ -116,4 +116,29 @@
         }
         return false;
     }
+
+    /**
+     * Determines if one of the call capable phone accounts defined supports calling with a subject
+     * specified.
+     *
+     * @param context The context.
+     * @return {@code true} if one of the call capable phone accounts supports calling with a
+     *      subject specified, {@code false} otherwise.
+     */
+    public static boolean isCallWithSubjectSupported(Context context) {
+        TelecomManager telecommMgr = (TelecomManager)
+                context.getSystemService(Context.TELECOM_SERVICE);
+        if (telecommMgr == null) {
+            return false;
+        }
+
+        List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
+        for (PhoneAccountHandle accountHandle : accountHandles) {
+            PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
+            if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/src/com/android/contacts/common/dialog/CallSubjectDialog.java b/src/com/android/contacts/common/dialog/CallSubjectDialog.java
index fb13940..622be60 100644
--- a/src/com/android/contacts/common/dialog/CallSubjectDialog.java
+++ b/src/com/android/contacts/common/dialog/CallSubjectDialog.java
@@ -23,17 +23,24 @@
 import android.app.FragmentTransaction;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.text.Editable;
+import android.text.InputFilter;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
@@ -43,6 +50,9 @@
 import com.android.contacts.common.R;
 import com.android.contacts.common.util.UriUtils;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Implements a dialog which prompts for a call subject for an outgoing call.
  */
@@ -50,19 +60,25 @@
     private static final String TAG = "CallSubjectDialog";
     private static final String FRAGMENT_TAG = "callSubject";
     private static final int CALL_SUBJECT_LIMIT = 16;
+    private static final int CALL_SUBJECT_HISTORY_SIZE = 5;
+
+    private static final int REQUEST_SUBJECT = 1001;
+
+    public static final String PREF_KEY_SUBJECT_HISTORY_COUNT = "subject_history_count";
+    public static final String PREF_KEY_SUBJECT_HISTORY_ITEM = "subject_history_item";
 
     /**
      * Fragment argument bundle keys:
      */
-    private static final String ARG_PHOTO_ID = "PHOTO_ID";
-    private static final String ARG_PHOTO_URI = "PHOTO_URI";
-    private static final String ARG_CONTACT_URI = "CONTACT_URI";
-    private static final String ARG_NAME_OR_NUMBER = "NAME_OR_NUMBER";
-    private static final String ARG_IS_BUSINESS = "IS_BUSINESS";
-    private static final String ARG_NUMBER = "NUMBER";
-    private static final String ARG_DISPLAY_NUMBER = "DISPLAY_NUMBER";
-    private static final String ARG_NUMBER_LABEL = "NUMBER_LABEL";
-    private static final String ARG_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
+    public static final String ARG_PHOTO_ID = "PHOTO_ID";
+    public static final String ARG_PHOTO_URI = "PHOTO_URI";
+    public static final String ARG_CONTACT_URI = "CONTACT_URI";
+    public static final String ARG_NAME_OR_NUMBER = "NAME_OR_NUMBER";
+    public static final String ARG_IS_BUSINESS = "IS_BUSINESS";
+    public static final String ARG_NUMBER = "NUMBER";
+    public static final String ARG_DISPLAY_NUMBER = "DISPLAY_NUMBER";
+    public static final String ARG_NUMBER_LABEL = "NUMBER_LABEL";
+    public static final String ARG_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
 
     private Context mContext;
     private QuickContactBadge mContactPhoto;
@@ -75,6 +91,8 @@
 
     private int mLimit = CALL_SUBJECT_LIMIT;
     private int mPhotoSize;
+    private SharedPreferences mPrefs;
+    private List<String> mSubjectHistory;
 
     private long mPhotoID;
     private Uri mPhotoUri;
@@ -112,7 +130,10 @@
     private final View.OnClickListener mHistoryOnClickListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            // TODO: Implement this (future CL).
+            hideSoftKeyboard(mContext, mCallSubjectView);
+            Intent intent = new Intent(mContext, CallSubjectHistory.class);
+            setTargetFragment(CallSubjectDialog.this, REQUEST_SUBJECT);
+            startActivityForResult(intent, REQUEST_SUBJECT);
         }
     };
 
@@ -122,17 +143,41 @@
     private final View.OnClickListener mSendAndCallOnClickListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
+            hideSoftKeyboard(mContext, mCallSubjectView);
+            String subject = mCallSubjectView.getText().toString();
             Intent intent = CallUtil.getCallWithSubjectIntent(mNumber, mPhoneAccountHandle,
-                    mCallSubjectView.getText().toString());
+                    subject);
 
             final TelecomManager tm =
                     (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
             tm.placeCall(intent.getData(), intent.getExtras());
+
+            mSubjectHistory.add(subject);
+            saveSubjectHistory(mSubjectHistory);
             getDialog().dismiss();
         }
     };
 
     /**
+     * Show the call subhect dialog given a phone number to dial (e.g. from the dialpad).
+     *
+     * @param activity The activity.
+     * @param number The number to dial.
+     */
+    public static void start(Activity activity, String number) {
+        start(activity,
+                -1 /* photoId */,
+                null /* photoUri */,
+                null /* contactUri */,
+                number /* nameOrNumber */,
+                false /* isBusiness */,
+                number /* number */,
+                null /* displayNumber */,
+                null /* numberLabel */,
+                null /* phoneAccountHandle */);
+    }
+
+    /**
      * Creates a call subject dialog.
      *
      * @param activity The current activity.
@@ -149,8 +194,6 @@
     public static void start(Activity activity, long photoId, Uri photoUri, Uri contactUri,
             String nameOrNumber, boolean isBusiness, String number, String displayNumber,
             String numberLabel, PhoneAccountHandle phoneAccountHandle) {
-        final FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
-        final CallSubjectDialog fragment = new CallSubjectDialog();
         Bundle arguments = new Bundle();
         arguments.putLong(ARG_PHOTO_ID, photoId);
         arguments.putParcelable(ARG_PHOTO_URI, photoUri);
@@ -161,6 +204,19 @@
         arguments.putString(ARG_DISPLAY_NUMBER, displayNumber);
         arguments.putString(ARG_NUMBER_LABEL, numberLabel);
         arguments.putParcelable(ARG_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+        start(activity, arguments);
+    }
+
+    /**
+     * Shows the call subject dialog given a Bundle containing all the arguments required to
+     * display the dialog (e.g. from Quick Contacts).
+     *
+     * @param activity The activity.
+     * @param arguments The arguments bundle.
+     */
+    public static void start(Activity activity, Bundle arguments) {
+        final FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
+        final CallSubjectDialog fragment = new CallSubjectDialog();
         fragment.setArguments(arguments);
         fragment.show(ft, FRAGMENT_TAG);
     }
@@ -176,9 +232,11 @@
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         mContext = getActivity();
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
         mPhotoSize = mContext.getResources().getDimensionPixelSize(
                 R.dimen.call_subject_dialog_contact_photo_size);
         readArguments();
+        mSubjectHistory = loadSubjectHistory(mPrefs);
 
         LayoutInflater inflater = getActivity().getLayoutInflater();
         View view = inflater.inflate(R.layout.dialog_call_subject, null);
@@ -188,21 +246,23 @@
         mNumberView = (TextView) view.findViewById(R.id.number);
         mCallSubjectView = (EditText) view.findViewById(R.id.call_subject);
         mCallSubjectView.addTextChangedListener(mTextWatcher);
+        InputFilter[] filters = new InputFilter[1];
+        filters[0] = new InputFilter.LengthFilter(mLimit);
+        mCallSubjectView.setFilters(filters);
+
         mCharacterLimitView = (TextView) view.findViewById(R.id.character_limit);
         mHistoryButton = view.findViewById(R.id.history_button);
         mHistoryButton.setOnClickListener(mHistoryOnClickListener);
+        mHistoryButton.setVisibility(mSubjectHistory.isEmpty() ? View.GONE : View.VISIBLE);
         mSendAndCallButton = view.findViewById(R.id.send_and_call_button);
         mSendAndCallButton.setOnClickListener(mSendAndCallOnClickListener);
 
+        showSoftKeyboard(mContext, mCallSubjectView);
         updateContactInfo();
         updateCharacterLimit();
 
         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
         dialogBuilder.setView(view);
-        final AlertDialog dialog = dialogBuilder.create();
-
-        dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT,
-                ViewGroup.LayoutParams.FILL_PARENT);
         return dialogBuilder.create();
     }
 
@@ -210,7 +270,11 @@
      * Populates the contact info fields based on the current contact information.
      */
     private void updateContactInfo() {
-        setPhoto(mPhotoID, mPhotoUri, mContactUri, mNameOrNumber, mIsBusiness);
+        if (mContactUri != null) {
+            setPhoto(mPhotoID, mPhotoUri, mContactUri, mNameOrNumber, mIsBusiness);
+        } else {
+            mContactPhoto.setVisibility(View.GONE);
+        }
         mNameView.setText(mNameOrNumber);
         if (!TextUtils.isEmpty(mNumberLabel) && !TextUtils.isEmpty(mDisplayNumber)) {
             mNumberView.setVisibility(View.VISIBLE);
@@ -229,6 +293,7 @@
         Bundle arguments = getArguments();
         if (arguments == null) {
             Log.e(TAG, "Arguments cannot be null.");
+            return;
         }
         mPhotoID = arguments.getLong(ARG_PHOTO_ID);
         mPhotoUri = arguments.getParcelable(ARG_PHOTO_URI);
@@ -296,4 +361,98 @@
                     false /* darkTheme */, true /* isCircular */, request);
         }
     }
+
+    /**
+     * Loads the subject history from shared preferences.
+     *
+     * @param prefs Shared preferences.
+     * @return List of subject history strings.
+     */
+    public static List<String> loadSubjectHistory(SharedPreferences prefs) {
+        int historySize = prefs.getInt(PREF_KEY_SUBJECT_HISTORY_COUNT, 0);
+        List<String> subjects = new ArrayList(historySize);
+
+        for (int ix = 0 ; ix < historySize; ix++) {
+            String historyItem = prefs.getString(PREF_KEY_SUBJECT_HISTORY_ITEM + ix, null);
+            if (!TextUtils.isEmpty(historyItem)) {
+                subjects.add(historyItem);
+            }
+        }
+
+        return subjects;
+    }
+
+    /**
+     * Saves the subject history list to shared prefs, removing older items so that there are only
+     * {@link #CALL_SUBJECT_HISTORY_SIZE} items at most.
+     *
+     * @param history The history.
+     */
+    private void saveSubjectHistory(List<String> history) {
+        // Remove oldest subject(s).
+        while (history.size() > CALL_SUBJECT_HISTORY_SIZE) {
+            history.remove(0);
+        }
+
+        SharedPreferences.Editor editor = mPrefs.edit();
+        int historyCount = 0;
+        for (String subject : history) {
+            if (!TextUtils.isEmpty(subject)) {
+                editor.putString(PREF_KEY_SUBJECT_HISTORY_ITEM + historyCount,
+                        subject);
+                historyCount++;
+            }
+        }
+        editor.putInt(PREF_KEY_SUBJECT_HISTORY_COUNT, historyCount);
+        editor.apply();
+    }
+
+    /**
+     * Handles results from the CallSubjectHistory activity.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     */
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode != REQUEST_SUBJECT || data == null) {
+            return;
+        }
+
+        if (resultCode != Activity.RESULT_OK) {
+            return;
+        }
+
+        String chosenSubject = data.getStringExtra(CallSubjectHistory.EXTRA_CHOSEN_SUBJECT);
+        mCallSubjectView.setText(chosenSubject);
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    /**
+     * Hide software keyboard for the given {@link View}.
+     */
+    public void hideSoftKeyboard(Context context, View view) {
+        InputMethodManager imm = (InputMethodManager) context.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        if (imm != null) {
+            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+        }
+    }
+
+    /**
+     * Show software keyboard for the given {@link View}.
+     */
+    public void showSoftKeyboard(Context context, View view) {
+        view.requestFocus();
+        InputMethodManager imm = (InputMethodManager) context.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        if (imm != null) {
+            imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,
+                    InputMethodManager.HIDE_IMPLICIT_ONLY);
+        }
+    }
 }
diff --git a/src/com/android/contacts/common/dialog/CallSubjectHistory.java b/src/com/android/contacts/common/dialog/CallSubjectHistory.java
new file mode 100644
index 0000000..1e983e6
--- /dev/null
+++ b/src/com/android/contacts/common/dialog/CallSubjectHistory.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.dialog;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.contacts.common.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * List activity which displays the call subject history.  Shows at the bottom of the screen,
+ * on top of existing content.
+ */
+public class CallSubjectHistory extends Activity {
+    public static final String EXTRA_CHOSEN_SUBJECT =
+            "com.android.contracts.common.dialog.extra.CHOSEN_SUBJECT";
+
+    private View mBackground;
+    private ListView mSubjectList;
+    private SharedPreferences mSharedPreferences;
+    private List<String> mSubjects;
+
+    /**
+     * Click listener which handles user clicks outside of the list view.  Dismisses the activity
+     * and returns a {@link Activity#RESULT_CANCELED} result code.
+     */
+    private View.OnClickListener mBackgroundListner = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            setResult(RESULT_CANCELED);
+            finish();
+        }
+    };
+
+    /**
+     * Item click listener which handles user clicks on the items in the list view.  Dismisses
+     * the activity, returning the subject to the caller and closing the activity with the
+     * {@link Activity#RESULT_OK} result code.
+     */
+    private AdapterView.OnItemClickListener mItemClickListener =
+            new AdapterView.OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> arg0, View view, int position, long arg3) {
+            returnSubject(mSubjects.get(position));
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        mSubjects = CallSubjectDialog.loadSubjectHistory(mSharedPreferences);
+
+        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.call_subject_history);
+
+        mBackground = findViewById(R.id.background);
+        mBackground.setOnClickListener(mBackgroundListner);
+
+        mSubjectList = (ListView) findViewById(R.id.subject_list);
+        mSubjectList.setOnItemClickListener(mItemClickListener);
+
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+                android.R.layout.simple_list_item_1, mSubjects);
+        mSubjectList.setAdapter(adapter);
+    }
+
+    /**
+     * Closes the activity and returns the subject chosen by the user to the caller.
+     *
+     * @param chosenSubject The chosen subject.
+     */
+    private void returnSubject(String chosenSubject) {
+        Intent intent = getIntent();
+        intent.putExtra(EXTRA_CHOSEN_SUBJECT, chosenSubject);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+}