Accessibility improvements.

- Do not use a list for a single row.
- Use content description in a few more places.

Bug: 4989128
Change-Id: I056237843dc6bca7987b18543f18f917babe2a4f
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index 987a787..99186e5 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -109,21 +109,87 @@
         android:layout_alignBottom="@id/contact_background_sizer"
         android:background="?android:attr/selectableItemBackground"
     />
-    <ListView
-        android:id="@android:id/list"
+    <FrameLayout android:id="@+id/call_and_sms_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/voicemail_container"
         android:layout_marginTop="@dimen/call_log_icon_margin"
-        android:background="?attr/call_log_primary_background_color"
-    />
+    >
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/call_and_sms"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/call_log_list_item_height"
+            android:orientation="horizontal"
+            android:layout_marginTop="@dimen/call_log_icon_margin"
+            android:gravity="center_vertical"
+            android:background="@drawable/dialpad_background"
+        >
+
+            <LinearLayout android:id="@+id/call_and_sms_main_action"
+                android:layout_width="0dip"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:paddingLeft="@dimen/call_log_indent_margin"
+                android:orientation="vertical"
+                android:gravity="center_vertical"
+                android:focusable="true"
+                android:background="@drawable/btn_dial"
+            >
+
+                <TextView android:id="@+id/call_and_sms_text1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textAppearance="?android:attr/textAppearanceLarge"
+                />
+
+                <LinearLayout android:id="@+id/call_and_sms_line2"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:orientation="horizontal"
+                >
+                    <TextView android:id="@+id/call_and_sms_label"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_marginRight="5dip"
+                        android:textAppearance="?android:attr/textAppearanceSmall"
+                        android:textStyle="bold"
+                    />
+
+                    <TextView android:id="@+id/call_and_sms_number"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="?android:attr/textAppearanceSmall"
+                    />
+                </LinearLayout>
+
+            </LinearLayout>
+
+            <View android:id="@+id/call_and_sms_divider"
+                android:layout_width="1px"
+                android:layout_height="32dip"
+                android:background="@drawable/ic_divider_dashed_holo_dark"
+                android:layout_gravity="center_vertical"
+            />
+
+            <ImageView android:id="@+id/call_and_sms_icon"
+                android:layout_width="@color/call_log_voicemail_highlight_color"
+                android:layout_height="match_parent"
+                android:paddingLeft="@dimen/call_log_inner_margin"
+                android:paddingRight="@dimen/call_log_outer_margin"
+                android:gravity="center"
+                android:scaleType="centerInside"
+                android:focusable="true"
+                android:background="@drawable/btn_dial"
+            />
+        </LinearLayout>
+    </FrameLayout>
     <ListView
         android:id="@+id/history"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/call_log_icon_margin"
         android:layout_alignParentLeft="true"
-        android:layout_below="@android:id/list"
+        android:layout_below="@id/call_and_sms_container"
         android:background="@android:color/black"
     />
 </RelativeLayout>
diff --git a/res/layout/call_detail_list_item.xml b/res/layout/call_detail_list_item.xml
deleted file mode 100644
index e2bf83c..0000000
--- a/res/layout/call_detail_list_item.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/call_log_list_item_height"
-    android:orientation="horizontal"
-    android:layout_marginTop="@dimen/call_log_icon_margin"
-    android:gravity="center_vertical"
-    android:background="@drawable/dialpad_background"
->
-
-    <LinearLayout android:id="@+id/main_action"
-        android:layout_width="0dip"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:paddingLeft="@dimen/call_log_indent_margin"
-        android:orientation="vertical"
-        android:gravity="center_vertical"
-        android:focusable="true"
-        android:background="@drawable/btn_dial"
-    >
-
-        <TextView android:id="@android:id/text1"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-        />
-
-
-        <LinearLayout android:id="@+id/line2"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-        >
-            <TextView android:id="@+id/label"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginRight="5dip"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:textStyle="bold"
-            />
-
-            <TextView android:id="@+id/number"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-            />
-        </LinearLayout>
-
-    </LinearLayout>
-
-    <View android:id="@+id/divider"
-        android:layout_width="1px"
-        android:layout_height="32dip"
-        android:background="@drawable/ic_divider_dashed_holo_dark"
-        android:layout_gravity="center_vertical"
-    />
-
-    <ImageView android:id="@+id/icon"
-        android:layout_width="@color/call_log_voicemail_highlight_color"
-        android:layout_height="match_parent"
-        android:paddingLeft="@dimen/call_log_inner_margin"
-        android:paddingRight="@dimen/call_log_outer_margin"
-        android:gravity="center"
-        android:scaleType="centerInside"
-        android:focusable="true"
-        android:background="@drawable/btn_dial"
-    />
-
-</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2e8983f..9460a9f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1732,6 +1732,34 @@
     -->
     <string name="description_call_log_voicemail">Voicemail</string>
 
+    <!-- String describing the button to add a contact for the current number.
+
+        Note: AccessibilityServices use this attribute to announce what the view represents.
+              This is especially valuable for views without textual representation like ImageView.
+    -->
+    <string name="description_add_contact">Add contact</string>
+
+    <!-- String describing the button to view the contact for the current number.
+
+        Note: AccessibilityServices use this attribute to announce what the view represents.
+              This is especially valuable for views without textual representation like ImageView.
+    -->
+    <string name="description_view_contact">View contact <xliff:g id="name">%1$s</xliff:g></string>
+
+    <!-- String describing the button to call a number or contact.
+
+        Note: AccessibilityServices use this attribute to announce what the view represents.
+              This is especially valuable for views without textual representation like ImageView.
+    -->
+    <string name="description_call">Call <xliff:g id="name">%1$s</xliff:g></string>
+
+    <!-- String describing the button to SMS a number or contact.
+
+        Note: AccessibilityServices use this attribute to announce what the view represents.
+              This is especially valuable for views without textual representation like ImageView.
+    -->
+    <string name="description_send_text_message">Send text message to <xliff:g id="name">%1$s</xliff:g></string>
+
     <!-- String describing the icon in the call log used to represent an unheard voicemail left to
          the user.
 
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index 5a2e9ea..5f682fa 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -28,7 +28,7 @@
 import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
 
 import android.app.ActionBar;
-import android.app.ListActivity;
+import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -54,15 +54,12 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -71,7 +68,7 @@
  * This activity can be either started with the URI of a single call log entry, or with the
  * {@link #EXTRA_CALL_LOG_IDS} extra to specify a group of call log entries.
  */
-public class CallDetailActivity extends ListActivity {
+public class CallDetailActivity extends Activity {
     private static final String TAG = "CallDetail";
 
     /** A long array extra containing ids of call log entries to display. */
@@ -142,6 +139,20 @@
     static final int COLUMN_INDEX_NORMALIZED_NUMBER = 5;
     static final int COLUMN_INDEX_PHOTO_URI = 6;
 
+    private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            startActivity(((ViewEntry) view.getTag()).primaryIntent);
+        }
+    };
+
+    private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            startActivity(((ViewEntry) view.getTag()).secondaryIntent);
+        }
+    };
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -322,14 +333,25 @@
                 // contact from this number.
                 final Intent mainActionIntent;
                 final int mainActionIcon;
+                final String mainActionDescription;
+
+                final CharSequence nameOrNumber;
+                if (!TextUtils.isEmpty(firstDetails.name)) {
+                    nameOrNumber = firstDetails.name;
+                } else {
+                    nameOrNumber = firstDetails.number;
+                }
 
                 if (firstDetails.personId != -1) {
                     Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, personId);
                     mainActionIntent = new Intent(Intent.ACTION_VIEW, personUri);
                     mainActionIcon = R.drawable.ic_contacts_holo_dark;
+                    mainActionDescription =
+                            getString(R.string.description_view_contact, nameOrNumber);
                 } else if (isVoicemailNumber) {
                     mainActionIntent = null;
                     mainActionIcon = 0;
+                    mainActionDescription = null;
                 } else if (isSipNumber) {
                     // TODO: This item is currently disabled for SIP addresses, because
                     // the Insert.PHONE extra only works correctly for PSTN numbers.
@@ -342,16 +364,19 @@
                     // and then we can remove the "!isSipNumber" check above.
                     mainActionIntent = null;
                     mainActionIcon = 0;
+                    mainActionDescription = null;
                 } else if (canPlaceCallsTo) {
                     mainActionIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
                     mainActionIntent.setType(Contacts.CONTENT_ITEM_TYPE);
                     mainActionIntent.putExtra(Insert.PHONE, mNumber);
                     mainActionIcon = R.drawable.ic_add_contact_holo_dark;
+                    mainActionDescription = getString(R.string.description_add_contact);
                 } else {
                     // If we cannot call the number, when we probably cannot add it as a contact either.
                     // This is usually the case of private, unknown, or payphone numbers.
                     mainActionIntent = null;
                     mainActionIcon = 0;
+                    mainActionDescription = null;
                 }
 
                 if (mainActionIntent == null) {
@@ -367,11 +392,9 @@
                             startActivity(mainActionIntent);
                         }
                     });
+                    mMainActionPushLayerView.setContentDescription(mainActionDescription);
                 }
 
-                // Build list of various available actions.
-                final List<ViewEntry> actions = new ArrayList<ViewEntry>();
-
                 // This action allows to call the number that places the call.
                 if (canPlaceCallsTo) {
                     final CharSequence displayNumber =
@@ -380,7 +403,8 @@
 
                     ViewEntry entry = new ViewEntry(
                             getString(R.string.menu_callNumber, displayNumber),
-                            new Intent(Intent.ACTION_CALL_PRIVILEGED, numberCallUri));
+                            new Intent(Intent.ACTION_CALL_PRIVILEGED, numberCallUri),
+                            getString(R.string.description_call, nameOrNumber));
 
                     // Only show a label if the number is shown and it is not a SIP address.
                     if (!TextUtils.isEmpty(firstDetails.number)
@@ -392,25 +416,20 @@
                     // The secondary action allows to send an SMS to the number that placed the
                     // call.
                     if (mPhoneNumberHelper.canSendSmsTo(mNumber)) {
-                        entry.setSecondaryAction(R.drawable.ic_text_holo_dark,
+                        entry.setSecondaryAction(
+                                R.drawable.ic_text_holo_dark,
                                 new Intent(Intent.ACTION_SENDTO,
-                                           Uri.fromParts("sms", mNumber, null)));
+                                           Uri.fromParts("sms", mNumber, null)),
+                                getString(R.string.description_send_text_message, nameOrNumber));
                     }
 
-                    actions.add(entry);
+                    configureCallButton(entry);
+                } else {
+                    disableCallButton();
                 }
 
                 mHasEditNumberBeforeCall = canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
 
-                if (actions.size() != 0) {
-                    // Set the actions for this phone number.
-                    setListAdapter(new ViewAdapter(CallDetailActivity.this, actions));
-                    getListView().setVisibility(View.VISIBLE);
-                    getListView().setItemsCanFocus(true);
-                } else {
-                    getListView().setVisibility(View.GONE);
-                }
-
                 ListView historyList = (ListView) findViewById(R.id.history);
                 historyList.setAdapter(
                         new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater,
@@ -506,6 +525,8 @@
     static final class ViewEntry {
         public final String text;
         public final Intent primaryIntent;
+        /** The description for accessibility of the primary action. */
+        public final String primaryDescription;
 
         public CharSequence label = null;
         public String number = null;
@@ -513,111 +534,72 @@
         public int secondaryIcon = 0;
         /** Intent for the secondary action. If not null, an icon must be defined. */
         public Intent secondaryIntent = null;
+        /** The description for accessibility of the secondary action. */
+        public String secondaryDescription = null;
 
-        public ViewEntry(String text, Intent intent) {
+        public ViewEntry(String text, Intent intent, String description) {
             this.text = text;
-            this.primaryIntent = intent;
+            primaryIntent = intent;
+            primaryDescription = description;
         }
 
-        public void setSecondaryAction(int icon, Intent intent) {
+        public void setSecondaryAction(int icon, Intent intent, String description) {
             secondaryIcon = icon;
             secondaryIntent = intent;
+            secondaryDescription = description;
         }
     }
 
-    private static final class ViewAdapter extends BaseAdapter {
-        private final Context mContext;
-        private final List<ViewEntry> mActions;
-        private final LayoutInflater mInflater;
+    /** Disables the call button area, e.g., for private numbers. */
+    private void disableCallButton() {
+        findViewById(R.id.call_and_sms).setVisibility(View.GONE);
+    }
 
-        private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                ViewEntry entry = (ViewEntry) view.getTag();
-                mContext.startActivity(entry.primaryIntent);
-            }
-        };
+    /** Configures the call button area using the given entry. */
+    private void configureCallButton(ViewEntry entry) {
+        View convertView = findViewById(R.id.call_and_sms);
+        convertView.setVisibility(View.VISIBLE);
 
-        private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                ViewEntry entry = (ViewEntry) view.getTag();
-                mContext.startActivity(entry.secondaryIntent);
-            }
-        };
+        ImageView icon = (ImageView) convertView.findViewById(R.id.call_and_sms_icon);
+        View divider = convertView.findViewById(R.id.call_and_sms_divider);
+        TextView text = (TextView) convertView.findViewById(R.id.call_and_sms_text1);
 
-        public ViewAdapter(Context context, List<ViewEntry> actions) {
-            mContext = context;
-            mActions = actions;
-            mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View mainAction = convertView.findViewById(R.id.call_and_sms_main_action);
+        mainAction.setOnClickListener(mPrimaryActionListener);
+        mainAction.setTag(entry);
+        mainAction.setContentDescription(entry.primaryDescription);
+
+        if (entry.secondaryIntent != null) {
+            icon.setOnClickListener(mSecondaryActionListener);
+            icon.setImageResource(entry.secondaryIcon);
+            icon.setVisibility(View.VISIBLE);
+            icon.setTag(entry);
+            icon.setContentDescription(entry.secondaryDescription);
+            divider.setVisibility(View.VISIBLE);
+        } else {
+            icon.setVisibility(View.GONE);
+            divider.setVisibility(View.GONE);
         }
+        text.setText(entry.text);
 
-        @Override
-        public int getCount() {
-            return mActions.size();
-        }
+        View line2 = convertView.findViewById(R.id.call_and_sms_line2);
+        boolean numberEmpty = TextUtils.isEmpty(entry.number);
+        boolean labelEmpty = TextUtils.isEmpty(entry.label) || numberEmpty;
+        if (labelEmpty && numberEmpty) {
+            line2.setVisibility(View.GONE);
+        } else {
+            line2.setVisibility(View.VISIBLE);
 
-        @Override
-        public Object getItem(int position) {
-            return mActions.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            // Make sure we have a valid convertView to start with
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.call_detail_list_item, parent, false);
-            }
-
-            // Fill action with icon and text.
-            ViewEntry entry = mActions.get(position);
-
-            ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
-            View divider = convertView.findViewById(R.id.divider);
-            TextView text = (TextView) convertView.findViewById(android.R.id.text1);
-
-            View mainAction = convertView.findViewById(R.id.main_action);
-            mainAction.setOnClickListener(mPrimaryActionListener);
-            mainAction.setTag(entry);
-
-            if (entry.secondaryIntent != null) {
-                icon.setOnClickListener(mSecondaryActionListener);
-                icon.setImageResource(entry.secondaryIcon);
-                icon.setVisibility(View.VISIBLE);
-                icon.setTag(entry);
-                divider.setVisibility(View.VISIBLE);
+            TextView label = (TextView) convertView.findViewById(R.id.call_and_sms_label);
+            if (labelEmpty) {
+                label.setVisibility(View.GONE);
             } else {
-                icon.setVisibility(View.GONE);
-                divider.setVisibility(View.GONE);
-            }
-            text.setText(entry.text);
-
-            View line2 = convertView.findViewById(R.id.line2);
-            boolean numberEmpty = TextUtils.isEmpty(entry.number);
-            boolean labelEmpty = TextUtils.isEmpty(entry.label) || numberEmpty;
-            if (labelEmpty && numberEmpty) {
-                line2.setVisibility(View.GONE);
-            } else {
-                line2.setVisibility(View.VISIBLE);
-
-                TextView label = (TextView) convertView.findViewById(R.id.label);
-                if (labelEmpty) {
-                    label.setVisibility(View.GONE);
-                } else {
-                    label.setText(entry.label);
-                    label.setVisibility(View.VISIBLE);
-                }
-
-                TextView number = (TextView) convertView.findViewById(R.id.number);
-                number.setText(entry.number);
+                label.setText(entry.label);
+                label.setVisibility(View.VISIBLE);
             }
 
-            return convertView;
+            TextView number = (TextView) convertView.findViewById(R.id.call_and_sms_number);
+            number.setText(entry.number);
         }
     }