Initial removal of dialer features.
- Moved main activities and classes out including all necessary dependencies
for a first working version.
- There are still dialer dependencies in contacts after this check-in. Further
separation coming.
Bug: 6993891
Change-Id: I1761b554fe5daf29acddbb43532f571191db2eaf
diff --git a/Android.mk b/Android.mk
index 67859bd..c29c90d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,6 +8,7 @@
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := \
com.android.phone.common \
+ com.android.phone.shared \
com.android.vcard \
android-common \
guava \
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index babd439..4c527aa 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -60,126 +60,6 @@
android:hardwareAccelerated="true"
>
- <!-- Intercept Dialer Intents for devices without a phone.
- This activity should have the same intent filters as the DialtactsActivity,
- so that its capturing the same events. Omit android.intent.category.LAUNCHER, because we
- don't want this to show up in the Launcher. The priorities of the intent-filters
- are set lower, so that the user does not see a disambig dialog -->
- <activity
- android:name=".activities.NonPhoneActivity"
- android:theme="@style/NonPhoneActivityTheme"
- >
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:mimeType="vnd.android.cursor.item/phone" />
- <data android:mimeType="vnd.android.cursor.item/person" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="voicemail" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.VIEW" />
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="tel" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:mimeType="vnd.android.cursor.dir/calls" />
- </intent-filter>
- <intent-filter android:priority="-1">
- <action android:name="android.intent.action.CALL_BUTTON" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- </intent-filter>
- </activity>
-
- <!-- The entrance point for Phone UI.
- stateAlwaysHidden is set to suppress keyboard show up on
- dialpad screen. -->
- <activity android:name=".activities.DialtactsActivity"
- android:label="@string/launcherDialer"
- android:theme="@style/DialtactsTheme"
- android:uiOptions="splitActionBarWhenNarrow"
- android:launchMode="singleTask"
- android:clearTaskOnLaunch="true"
- android:icon="@mipmap/ic_launcher_phone"
- android:screenOrientation="nosensor"
- android:enabled="@*android:bool/config_voice_capable"
- android:taskAffinity="android.task.contacts.phone"
- android:windowSoftInputMode="stateAlwaysHidden|adjustNothing">
- <intent-filter>
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:mimeType="vnd.android.cursor.item/phone" />
- <data android:mimeType="vnd.android.cursor.item/person" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="voicemail" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- <category android:name="android.intent.category.BROWSABLE" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <action android:name="android.intent.action.DIAL" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="tel" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:mimeType="vnd.android.cursor.dir/calls" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.CALL_BUTTON" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- </intent-filter>
- <!-- This was never intended to be public, but is here for backward
- compatibility. Use Intent.ACTION_DIAL instead. -->
- <intent-filter>
- <action android:name="com.android.phone.action.TOUCH_DIALER" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.TAB" />
- </intent-filter>
- <intent-filter android:label="@string/recentCallsIconLabel">
- <action android:name="com.android.phone.action.RECENT_CALLS" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.TAB" />
- </intent-filter>
- </activity>
-
<!-- The main Contacts activity with the contact list, favorites, and groups. -->
<activity android:name=".activities.PeopleActivity"
android:label="@string/people"
@@ -321,24 +201,6 @@
android:exported="true"
/>
- <!-- Backwards compatibility: "Phone" from Gingerbread and earlier -->
- <activity-alias android:name="DialtactsActivity"
- android:targetActivity=".activities.DialtactsActivity"
- android:exported="true"
- />
-
- <!-- Backwards compatibility: "Call log" from Gingerbread and earlier -->
- <activity-alias android:name="RecentCallsListActivity"
- android:targetActivity=".activities.DialtactsActivity"
- android:exported="true"
- />
-
- <!-- Backwards compatibility: "Call log" from ICS -->
- <activity-alias android:name=".activities.CallLogActivity"
- android:targetActivity=".activities.DialtactsActivity"
- android:exported="true"
- />
-
<!-- An activity for joining contacts -->
<activity android:name=".activities.JoinContactActivity"
android:theme="@style/JoinContactActivityTheme"
@@ -461,20 +323,6 @@
</activity-alias>
- <activity android:name="CallDetailActivity"
- android:label="@string/callDetailTitle"
- android:theme="@style/CallDetailActivityTheme"
- android:screenOrientation="portrait"
- android:icon="@mipmap/ic_launcher_phone"
- android:taskAffinity="android.task.contacts.phone"
- >
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="vnd.android.cursor.item/calls" />
- </intent-filter>
- </activity>
-
<!-- Views the details of a single contact -->
<activity android:name=".activities.ContactDetailActivity"
android:label="@string/viewContactTitle"
@@ -527,7 +375,7 @@
</intent-filter>
</activity>
- <activity android:name=".test.FragmentTestActivity">
+ <activity android:name=".common.test.FragmentTestActivity">
<intent-filter>
<category android:name="android.intent.category.TEST" />
</intent-filter>
@@ -628,21 +476,6 @@
android:resource="@xml/social_widget_info" />
</receiver>
- <receiver android:name=".calllog.CallLogReceiver"
- android:enabled="@*android:bool/config_voice_capable">
- <intent-filter>
- <action android:name="android.intent.action.NEW_VOICEMAIL" />
- <data
- android:scheme="content"
- android:host="com.android.voicemail"
- android:mimeType="vnd.android.cursor.item/voicemail"
- />
- </intent-filter>
- <intent-filter android:priority="100">
- <action android:name="android.intent.action.BOOT_COMPLETED"/>
- </intent-filter>
- </receiver>
-
<activity
android:name=".socialwidget.SocialWidgetConfigureActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
@@ -651,20 +484,5 @@
</intent-filter>
</activity>
- <service
- android:name=".calllog.CallLogNotificationsService"
- android:exported="false"
- />
-
- <!-- Service that is exclusively for the Phone application that sends out a view
- notification. This service might be removed in future versions of the app -->
- <service android:name=".ViewNotificationService"
- android:permission="android.permission.WRITE_CONTACTS"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.contacts.VIEW_NOTIFICATION" />
- <data android:mimeType="vnd.android.cursor.item/contact" />
- </intent-filter>
- </service>
</application>
</manifest>
diff --git a/res/layout-land/dialpad_fragment.xml b/res/layout-land/dialpad_fragment.xml
deleted file mode 100644
index 63dd369..0000000
--- a/res/layout-land/dialpad_fragment.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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:id="@+id/top"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="3"
- android:orientation="vertical" >
-
- <LinearLayout
- android:id="@+id/digits_container"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="@integer/dialpad_layout_weight_digits"
- android:layout_marginTop="@dimen/dialpad_vertical_margin"
- android:background="@drawable/dialpad_background"
- android:gravity="center">
-
- <com.android.contacts.dialpad.DigitsEditText
- android:id="@+id/digits"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textAppearance="@style/DialtactsDigitsTextAppearance"
- android:textColor="?android:attr/textColorPrimary"
- android:nextFocusRight="@+id/overflow_menu"
- android:background="@android:color/transparent" />
-
- <ImageButton
- android:id="@+id/deleteButton"
- android:layout_width="56dip"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:gravity="center"
- android:state_enabled="false"
- android:background="?android:attr/selectableItemBackground"
- android:contentDescription="@string/description_delete_button"
- android:src="@drawable/ic_dial_action_delete" />
-
-
- </LinearLayout>
- <!-- "Dialpad chooser" UI, shown only when the user brings up the
- Dialer while a call is already in progress.
- When this UI is visible, the other Dialer elements
- (the textfield and button) are hidden. -->
- <ListView android:id="@+id/dialpadChooser"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:footerDividersEnabled="true" />
-
- <!-- Keypad section -->
- <include layout="@layout/dialpad" />
- </LinearLayout>
- <View
- android:layout_width="@dimen/dialpad_center_margin"
- android:layout_height="match_parent"
- android:background="#66000000"/>
- <RelativeLayout
- android:id="@+id/dialButtonContainer"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="2"
- android:background="@drawable/dialpad_background">
- <View
- android:layout_width="match_parent"
- android:layout_height="@dimen/dialpad_button_margin"
- android:layout_above="@id/dialButton"
- android:background="#33000000" />
- <ImageButton android:id="@+id/dialButton"
- android:layout_width="match_parent"
- android:layout_height="@dimen/call_button_height"
- android:layout_alignParentBottom="true"
- android:state_enabled="false"
- android:background="@drawable/btn_call"
- android:contentDescription="@string/description_dial_button"
- android:src="@drawable/ic_dial_action_call" />
- </RelativeLayout>
-</LinearLayout>
diff --git a/res/layout-land/dialtacts_activity.xml b/res/layout-land/dialtacts_activity.xml
deleted file mode 100644
index f43fe5f..0000000
--- a/res/layout-land/dialtacts_activity.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="?android:attr/actionBarSize"
- android:id="@+id/dialtacts_frame"
- >
- <android.support.v4.view.ViewPager
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-</FrameLayout>
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
deleted file mode 100644
index 8f38a19..0000000
--- a/res/layout/call_detail.xml
+++ /dev/null
@@ -1,218 +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.
--->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:ex="http://schemas.android.com/apk/res-auto"
- android:id="@+id/call_detail"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:background="@android:color/black"
->
- <!--
- The list view is under everything.
- It contains a first header element which is hidden under the controls UI.
- When scrolling, the controls move up until the name bar hits the top.
- -->
- <ListView
- android:id="@+id/history"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- />
-
- <!-- All the controls which are part of the pinned header are in this layout. -->
- <RelativeLayout
- android:id="@+id/controls"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- >
- <FrameLayout
- android:id="@+id/voicemail_status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:visibility="gone"
- >
- <include layout="@layout/call_log_voicemail_status"/>
- </FrameLayout>
-
- <view
- class="com.android.contacts.widget.ProportionalLayout"
- android:id="@+id/contact_background_sizer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_below="@id/voicemail_status"
- ex:ratio="0.5"
- ex:direction="widthToHeight"
- >
- <ImageView
- android:id="@+id/contact_background"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:adjustViewBounds="true"
- android:scaleType="centerCrop"
- />
- </view>
- <LinearLayout
- android:id="@+id/blue_separator"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:background="@android:color/holo_blue_light"
- android:layout_below="@+id/contact_background_sizer"
- />
- <View
- android:id="@+id/photo_text_bar"
- android:layout_width="match_parent"
- android:layout_height="42dip"
- android:background="#7F000000"
- android:layout_alignParentLeft="true"
- android:layout_alignBottom="@id/contact_background_sizer"
- />
- <ImageView
- android:id="@+id/main_action"
- android:layout_width="wrap_content"
- android:layout_height="0dip"
- android:scaleType="center"
- android:layout_alignRight="@id/photo_text_bar"
- android:layout_alignBottom="@id/photo_text_bar"
- android:layout_alignTop="@id/photo_text_bar"
- android:layout_marginRight="@dimen/call_log_outer_margin"
- />
- <TextView
- android:id="@+id/header_text"
- android:layout_width="wrap_content"
- android:layout_height="0dip"
- android:layout_alignLeft="@id/photo_text_bar"
- android:layout_toLeftOf="@id/main_action"
- android:layout_alignTop="@id/photo_text_bar"
- android:layout_alignBottom="@id/photo_text_bar"
- android:layout_marginRight="@dimen/call_log_inner_margin"
- android:layout_marginLeft="@dimen/call_detail_contact_name_margin"
- android:gravity="center_vertical"
- android:textColor="?attr/call_log_primary_text_color"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:singleLine="true"
- />
- <ImageButton
- android:id="@+id/main_action_push_layer"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignLeft="@id/contact_background_sizer"
- android:layout_alignTop="@id/contact_background_sizer"
- android:layout_alignRight="@id/contact_background_sizer"
- android:layout_alignBottom="@id/contact_background_sizer"
- android:background="?android:attr/selectableItemBackground"
- />
- <LinearLayout
- android:id="@+id/voicemail_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="@dimen/call_detail_button_spacing"
- android:layout_below="@id/blue_separator"
- >
- <!-- The voicemail fragment will be put here. -->
- </LinearLayout>
- <FrameLayout
- android:id="@+id/call_and_sms"
- android:layout_width="match_parent"
- android:layout_height="@dimen/call_log_list_item_height"
- android:layout_marginBottom="@dimen/call_detail_button_spacing"
- android:layout_below="@id/voicemail_container"
- android:gravity="center_vertical"
- android:background="@drawable/dialpad_background"
- >
- <LinearLayout
- android:id="@+id/call_and_sms_main_action"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackground"
- >
-
- <LinearLayout
- 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"
- >
-
- <TextView android:id="@+id/call_and_sms_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingRight="@dimen/call_log_icon_margin"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?attr/call_log_primary_text_color"
- android:singleLine="true"
- android:ellipsize="end"
- />
-
- <TextView android:id="@+id/call_and_sms_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingRight="@dimen/call_log_icon_margin"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?attr/call_log_primary_text_color"
- android:textAllCaps="true"
- android:singleLine="true"
- android:ellipsize="end"
- />
- </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="?android:attr/selectableItemBackground"
- />
- </LinearLayout>
- </FrameLayout>
- </RelativeLayout>
-
- <!--
- Used to hide the UI when playing a voicemail and the proximity sensor
- is detecting something near the screen.
- -->
- <View
- android:id="@+id/blank"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:background="@android:color/black"
- android:visibility="gone"
- android:clickable="true"
- />
-</RelativeLayout>
diff --git a/res/layout/call_detail_history_header.xml b/res/layout/call_detail_history_header.xml
deleted file mode 100644
index 09047c5..0000000
--- a/res/layout/call_detail_history_header.xml
+++ /dev/null
@@ -1,61 +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.
--->
-
-<!-- This layout is supposed to match the content of the controls in call_detail.xml -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:ex="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <!-- Contact photo. -->
- <view
- class="com.android.contacts.widget.ProportionalLayout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_below="@id/voicemail_status"
- ex:ratio="0.5"
- ex:direction="widthToHeight"
- >
- <!-- Proportional layout requires a view in it. -->
- <View
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- />
- </view>
- <!-- Separator line -->
- <View
- android:layout_width="match_parent"
- android:layout_height="1dip"
- />
- <!-- Voicemail controls -->
- <!-- TODO: Make the height be based on a constant. -->
- <View
- android:id="@+id/header_voicemail_container"
- android:layout_width="match_parent"
- android:layout_height="140dip"
- android:layout_marginBottom="@dimen/call_detail_button_spacing"
- />
- <!-- Call and SMS -->
- <View
- android:id="@+id/header_call_and_sms_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/call_log_list_item_height"
- />
-
-</LinearLayout>
diff --git a/res/layout/call_detail_history_item.xml b/res/layout/call_detail_history_item.xml
deleted file mode 100644
index 28a7da0..0000000
--- a/res/layout/call_detail_history_item.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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="wrap_content"
- android:minHeight="@dimen/call_log_list_item_height"
- android:paddingTop="@dimen/call_log_inner_margin"
- android:paddingBottom="@dimen/call_log_inner_margin"
- android:paddingLeft="@dimen/call_log_indent_margin"
- android:paddingRight="@dimen/call_log_outer_margin"
- android:orientation="vertical"
->
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- >
- <view
- class="com.android.contacts.calllog.CallTypeIconsView"
- android:id="@+id/call_type_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- />
- <TextView
- android:id="@+id/call_type_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/call_log_icon_margin"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/secondary_text_color"
- />
- </LinearLayout>
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/secondary_text_color"
- />
- <TextView
- android:id="@+id/duration"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/secondary_text_color"
- />
-</LinearLayout>
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
deleted file mode 100644
index 34b4b7f..0000000
--- a/res/layout/call_log_fragment.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<!-- Layout parameters are set programmatically. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/FragmentActionBarPadding"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:divider="?android:attr/dividerHorizontal"
- android:showDividers="end">
-
- <FrameLayout
- android:id="@+id/voicemail_status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone">
- <include layout="@layout/call_log_voicemail_status"
- />
- </FrameLayout>
-
- <FrameLayout>
- <TextView
- android:id="@+id/filter_status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="@dimen/call_log_outer_margin"
- android:paddingRight="@dimen/call_log_outer_margin"
- android:paddingTop="@dimen/call_log_inner_margin"
- android:paddingBottom="@dimen/call_log_inner_margin"
- android:layout_alignParentLeft="true"
- android:layout_alignParentBottom="true"
- android:visibility="gone"
- />
- <View
- android:id="@+id/call_log_divider"
- android:layout_width="match_parent"
- android:layout_height="1px"
- android:layout_marginLeft="@dimen/call_log_outer_margin"
- android:layout_marginRight="@dimen/call_log_outer_margin"
- android:layout_gravity="bottom"
- android:background="#55ffffff"
- />
- </FrameLayout>
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <ListView android:id="@android:id/list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fadingEdge="none"
- android:scrollbarStyle="outsideOverlay"
- android:divider="@null"
- />
- <TextView android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:text="@string/recentCalls_empty"
- android:gravity="center"
- android:layout_marginTop="@dimen/empty_message_top_margin"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
- </FrameLayout>
-</LinearLayout>
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
deleted file mode 100644
index 4040c28..0000000
--- a/res/layout/call_log_list_item.xml
+++ /dev/null
@@ -1,167 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<view
- xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.contacts.calllog.CallLogListItemView"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
->
- <!--
- This layout may represent either a call log item or one of the
- headers in the call log.
-
- The former will make the @id/call_log_item visible and the
- @id/call_log_header gone.
-
- The latter will make the @id/call_log_header visible and the
- @id/call_log_item gone
- -->
-
- <LinearLayout
- android:id="@+id/primary_action_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_marginLeft="@dimen/call_log_outer_margin"
- android:layout_marginRight="@dimen/call_log_outer_margin"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true"
- android:nextFocusRight="@+id/secondary_action_icon"
- android:nextFocusLeft="@+id/quick_contact_photo"
- >
- <QuickContactBadge
- android:id="@+id/quick_contact_photo"
- android:layout_width="@dimen/call_log_list_contact_photo_size"
- android:layout_height="@dimen/call_log_list_contact_photo_size"
- android:nextFocusRight="@id/primary_action_view"
- android:layout_alignParentLeft="true"
- android:layout_centerVertical="true"
- android:focusable="true"
- />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:paddingTop="@dimen/call_log_inner_margin"
- android:paddingBottom="@dimen/call_log_inner_margin"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:layout_marginLeft="@dimen/call_log_inner_margin"
- >
- <TextView
- android:id="@+id/name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- android:textColor="?attr/call_log_primary_text_color"
- android:textSize="18sp"
- android:singleLine="true"
- />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- >
- <TextView
- android:id="@+id/number"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- android:textColor="?attr/call_log_secondary_text_color"
- android:textSize="14sp"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
- <TextView
- android:id="@+id/label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- android:textColor="?attr/call_log_secondary_text_color"
- android:textStyle="bold"
- android:textSize="14sp"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
- </LinearLayout>
- <LinearLayout
- android:id="@+id/call_type"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- >
- <view
- class="com.android.contacts.calllog.CallTypeIconsView"
- android:id="@+id/call_type_icons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- android:layout_gravity="center_vertical"
- />
- <TextView
- android:id="@+id/call_count_and_date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/call_log_icon_margin"
- android:layout_gravity="center_vertical"
- android:textColor="?attr/call_log_secondary_text_color"
- android:textSize="14sp"
- android:singleLine="true"
- />
- </LinearLayout>
- </LinearLayout>
- <View
- android:id="@+id/divider"
- android:layout_width="1px"
- android:layout_height="@dimen/call_log_call_action_size"
- android:background="@drawable/ic_divider_dashed_holo_dark"
- android:layout_gravity="center_vertical"
- />
- <ImageButton
- android:id="@+id/secondary_action_icon"
- android:layout_width="@dimen/call_log_call_action_width"
- android:layout_height="match_parent"
- android:paddingLeft="@dimen/call_log_inner_margin"
- android:paddingTop="@dimen/call_log_inner_margin"
- android:paddingBottom="@dimen/call_log_inner_margin"
- android:paddingRight="@dimen/call_log_inner_margin"
- android:scaleType="center"
- android:background="?android:attr/selectableItemBackground"
- android:nextFocusLeft="@id/primary_action_view"
- />
- </LinearLayout>
-
- <TextView
- android:id="@+id/call_log_header"
- style="@style/ContactListSeparatorTextViewStyle"
- android:layout_marginLeft="@dimen/call_log_outer_margin"
- android:layout_marginRight="@dimen/call_log_outer_margin"
- android:paddingTop="@dimen/call_log_inner_margin"
- android:paddingBottom="@dimen/call_log_inner_margin" />
-
- <View
- android:id="@+id/call_log_divider"
- android:layout_width="match_parent"
- android:layout_height="1px"
- android:layout_marginLeft="@dimen/call_log_outer_margin"
- android:layout_marginRight="@dimen/call_log_outer_margin"
- android:background="#55ffffff"
- />
-</view>
diff --git a/res/layout/call_log_voicemail_status.xml b/res/layout/call_log_voicemail_status.xml
deleted file mode 100644
index 191c821..0000000
--- a/res/layout/call_log_voicemail_status.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="?attr/call_log_voicemail_status_height"
- android:background="?attr/call_log_voicemail_status_background_color"
- >
- <TextView
- android:id="@+id/voicemail_status_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_weight="1"
- android:paddingLeft="@dimen/call_log_outer_margin"
- android:paddingRight="@dimen/call_log_inner_margin"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?attr/call_log_voicemail_status_text_color"
- />
- <TextView
- android:id="@+id/voicemail_status_action"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:paddingLeft="@dimen/call_log_inner_margin"
- android:paddingRight="@dimen/call_log_outer_margin"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?attr/call_log_voicemail_status_action_text_color"
- android:background="?android:attr/selectableItemBackground"
- android:clickable="true"
- />
- </LinearLayout>
-</merge>
diff --git a/res/layout/dialpad.xml b/res/layout/dialpad.xml
deleted file mode 100644
index 54b7955..0000000
--- a/res/layout/dialpad.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 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.
--->
-
-<!-- Dialpad in the Phone app. -->
-<TableLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/dialpad"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="@integer/dialpad_layout_weight_dialpad"
- android:layout_gravity="center_horizontal"
- android:layout_marginTop="@dimen/dialpad_vertical_margin"
- android:paddingLeft="5dip"
- android:paddingRight="5dip"
- android:paddingBottom="10dip"
- android:background="@drawable/dialpad_background">
-
- <TableRow
- android:layout_height="0px"
- android:layout_weight="1">
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/one" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_1_wht"
- android:contentDescription="@string/description_image_button_one" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/two" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_2_wht"
- android:contentDescription="@string/description_image_button_two" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/three" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_3_wht"
- android:contentDescription="@string/description_image_button_three" />
- </TableRow>
-
- <TableRow
- android:layout_height="0px"
- android:layout_weight="1">
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/four" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_4_wht"
- android:contentDescription="@string/description_image_button_four" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/five" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_5_wht"
- android:contentDescription="@string/description_image_button_five" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/six" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_6_wht"
- android:contentDescription="@string/description_image_button_six" />
- </TableRow>
-
- <TableRow
- android:layout_height="0px"
- android:layout_weight="1">
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/seven" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_7_wht"
- android:contentDescription="@string/description_image_button_seven" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/eight" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_8_wht"
- android:contentDescription="@string/description_image_button_eight" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/nine" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_9_wht"
- android:contentDescription="@string/description_image_button_nine" />
- </TableRow>
-
- <TableRow
- android:layout_height="0px"
- android:layout_weight="1">
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/star" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_star_wht"
- android:contentDescription="@string/description_image_button_star" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/zero" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_0_wht"
- android:contentDescription="@string/description_image_button_zero" />
- <com.android.contacts.dialpad.DialpadImageButton
- android:id="@+id/pound" style="@style/DialtactsDialpadButtonStyle"
- android:src="@drawable/dial_num_pound_wht"
- android:contentDescription="@string/description_image_button_pound" />
- </TableRow>
-</TableLayout>
diff --git a/res/layout/dialpad_chooser_list_item.xml b/res/layout/dialpad_chooser_list_item.xml
deleted file mode 100644
index 853ca47..0000000
--- a/res/layout/dialpad_chooser_list_item.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 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.
--->
-
-<!-- Layout of a single item in the Dialer's "Dialpad chooser" UI. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ImageView android:id="@+id/icon"
- android:layout_width="64dp"
- android:layout_height="64dp"
- android:scaleType="center" />
-
- <TextView android:id="@+id/text"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_gravity="center_vertical"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="wrap_content" />
-
-</LinearLayout>
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
deleted file mode 100644
index 796eb28..0000000
--- a/res/layout/dialpad_fragment.xml
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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:id="@+id/top"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:paddingLeft="@dimen/dialpad_horizontal_margin"
- android:paddingRight="@dimen/dialpad_horizontal_margin">
-
- <!-- Text field and possibly soft menu button above the keypad where
- the digits are displayed. -->
- <LinearLayout
- android:id="@+id/digits_container"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="@integer/dialpad_layout_weight_digits"
- android:layout_marginTop="@dimen/dialpad_vertical_margin"
- android:gravity="center"
- android:background="@drawable/dialpad_background" >
-
- <com.android.contacts.dialpad.DigitsEditText
- android:id="@+id/digits"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textAppearance="@style/DialtactsDigitsTextAppearance"
- android:textColor="?android:attr/textColorPrimary"
- android:nextFocusRight="@+id/overflow_menu"
- android:background="@android:color/transparent" />
-
- <ImageButton
- android:id="@+id/deleteButton"
- android:layout_width="56dip"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:gravity="center"
- android:state_enabled="false"
- android:background="?android:attr/selectableItemBackground"
- android:contentDescription="@string/description_delete_button"
- android:src="@drawable/ic_dial_action_delete" />
- </LinearLayout>
-
- <!-- Keypad section -->
- <include layout="@layout/dialpad" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="@dimen/dialpad_vertical_margin"
- android:background="#66000000"/>
-
- <!-- left and right paddings will be modified by the code. See DialpadFragment. -->
- <FrameLayout
- android:id="@+id/dialButtonContainer"
- android:layout_width="match_parent"
- android:layout_height="0px"
- android:layout_weight="@integer/dialpad_layout_weight_additional_buttons"
- android:layout_gravity="center_horizontal"
- android:background="@drawable/dialpad_background">
-
- <ImageButton
- android:id="@+id/dialButton"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:state_enabled="false"
- android:background="@drawable/btn_call"
- android:contentDescription="@string/description_dial_button"
- android:src="@drawable/ic_dial_action_call" />
-
- </FrameLayout>
-
- <!-- "Dialpad chooser" UI, shown only when the user brings up the
- Dialer while a call is already in progress.
- When this UI is visible, the other Dialer elements
- (the textfield/button and the dialpad) are hidden. -->
- <ListView android:id="@+id/dialpadChooser"
- android:layout_width="match_parent"
- android:layout_height="1dip"
- android:layout_weight="1"
- />
-
-</LinearLayout>
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
deleted file mode 100644
index 35fa00f..0000000
--- a/res/layout/dialtacts_activity.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="?android:attr/actionBarSize"
- android:id="@+id/dialtacts_frame"
- >
- <android.support.v4.view.ViewPager
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
-
- <ImageButton
- android:id="@+id/searchButton"
- android:layout_width="wrap_content"
- android:layout_height="?android:attr/actionBarSize"
- android:layout_gravity="bottom|left"
- android:state_enabled="false"
- android:background="?android:attr/selectableItemBackground"
- android:contentDescription="@string/description_search_button"
- android:src="@drawable/ic_dial_action_search"/>
-
- <ImageButton
- android:id="@+id/overflow_menu"
- android:layout_width="wrap_content"
- android:layout_height="?android:attr/actionBarSize"
- android:layout_gravity="bottom|right"
- android:src="@drawable/ic_menu_overflow"
- android:contentDescription="@string/action_menu_overflow_description"
- android:nextFocusLeft="@id/digits"
- android:background="?android:attr/selectableItemBackground"/>
-</FrameLayout>
diff --git a/res/layout/dialtacts_custom_action_bar.xml b/res/layout/dialtacts_custom_action_bar.xml
deleted file mode 100644
index 0af8eaa..0000000
--- a/res/layout/dialtacts_custom_action_bar.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 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.
--->
-
-<!-- Dimensions are set at runtime in ActionBarAdapter -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dip"
- android:layout_height="0dip"
- android:orientation="horizontal">
-
- <SearchView
- android:id="@+id/search_view"
- android:layout_width="0px"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:iconifiedByDefault="false"
- android:inputType="textFilter" />
-
- <ImageButton
- android:id="@+id/search_option"
- android:layout_width="wrap_content"
- android:paddingLeft="4dip"
- android:paddingRight="4dip"
- android:layout_height="match_parent"
- android:layout_alignParentRight="true"
- android:src="@drawable/ic_menu_overflow"
- android:background="?android:attr/selectableItemBackground"
- android:visibility="gone" />
-
-</LinearLayout>
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
deleted file mode 100644
index 7bb2157..0000000
--- a/src/com/android/contacts/CallDetailActivity.java
+++ /dev/null
@@ -1,936 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts;
-
-import android.app.ActionBar;
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.Contacts.Intents.Insert;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.Contacts;
-import android.provider.VoicemailContract.Voicemails;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ActionMode;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.contacts.BackScrollManager.ScrollableHeader;
-import com.android.contacts.calllog.CallDetailHistoryAdapter;
-import com.android.contacts.calllog.CallTypeHelper;
-import com.android.contacts.calllog.ContactInfo;
-import com.android.contacts.calllog.ContactInfoHelper;
-import com.android.contacts.calllog.PhoneNumberHelper;
-import com.android.contacts.format.FormatUtils;
-import com.android.contacts.util.AsyncTaskExecutor;
-import com.android.contacts.util.AsyncTaskExecutors;
-import com.android.contacts.util.ClipboardUtils;
-import com.android.contacts.util.Constants;
-import com.android.contacts.voicemail.VoicemailPlaybackFragment;
-import com.android.contacts.voicemail.VoicemailStatusHelper;
-import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
-import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
-
-import java.util.List;
-
-/**
- * Displays the details of a specific call log entry.
- * <p>
- * 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 Activity implements ProximitySensorAware {
- private static final String TAG = "CallDetail";
-
- /** The time to wait before enabling the blank the screen due to the proximity sensor. */
- private static final long PROXIMITY_BLANK_DELAY_MILLIS = 100;
- /** The time to wait before disabling the blank the screen due to the proximity sensor. */
- private static final long PROXIMITY_UNBLANK_DELAY_MILLIS = 500;
-
- /** The enumeration of {@link AsyncTask} objects used in this class. */
- public enum Tasks {
- MARK_VOICEMAIL_READ,
- DELETE_VOICEMAIL_AND_FINISH,
- REMOVE_FROM_CALL_LOG_AND_FINISH,
- UPDATE_PHONE_CALL_DETAILS,
- }
-
- /** A long array extra containing ids of call log entries to display. */
- public static final String EXTRA_CALL_LOG_IDS = "EXTRA_CALL_LOG_IDS";
- /** If we are started with a voicemail, we'll find the uri to play with this extra. */
- public static final String EXTRA_VOICEMAIL_URI = "EXTRA_VOICEMAIL_URI";
- /** If we should immediately start playback of the voicemail, this extra will be set to true. */
- public static final String EXTRA_VOICEMAIL_START_PLAYBACK = "EXTRA_VOICEMAIL_START_PLAYBACK";
- /** If the activity was triggered from a notification. */
- public static final String EXTRA_FROM_NOTIFICATION = "EXTRA_FROM_NOTIFICATION";
-
- private CallTypeHelper mCallTypeHelper;
- private PhoneNumberHelper mPhoneNumberHelper;
- private PhoneCallDetailsHelper mPhoneCallDetailsHelper;
- private TextView mHeaderTextView;
- private View mHeaderOverlayView;
- private ImageView mMainActionView;
- private ImageButton mMainActionPushLayerView;
- private ImageView mContactBackgroundView;
- private AsyncTaskExecutor mAsyncTaskExecutor;
- private ContactInfoHelper mContactInfoHelper;
-
- private String mNumber = null;
- private String mDefaultCountryIso;
-
- /* package */ LayoutInflater mInflater;
- /* package */ Resources mResources;
- /** Helper to load contact photos. */
- private ContactPhotoManager mContactPhotoManager;
- /** Helper to make async queries to content resolver. */
- private CallDetailActivityQueryHandler mAsyncQueryHandler;
- /** Helper to get voicemail status messages. */
- private VoicemailStatusHelper mVoicemailStatusHelper;
- // Views related to voicemail status message.
- private View mStatusMessageView;
- private TextView mStatusMessageText;
- private TextView mStatusMessageAction;
-
- /** Whether we should show "edit number before call" in the options menu. */
- private boolean mHasEditNumberBeforeCallOption;
- /** Whether we should show "trash" in the options menu. */
- private boolean mHasTrashOption;
- /** Whether we should show "remove from call log" in the options menu. */
- private boolean mHasRemoveFromCallLogOption;
-
- private ProximitySensorManager mProximitySensorManager;
- private final ProximitySensorListener mProximitySensorListener = new ProximitySensorListener();
-
- /**
- * The action mode used when the phone number is selected. This will be non-null only when the
- * phone number is selected.
- */
- private ActionMode mPhoneNumberActionMode;
-
- private CharSequence mPhoneNumberLabelToCopy;
- private CharSequence mPhoneNumberToCopy;
-
- /** Listener to changes in the proximity sensor state. */
- private class ProximitySensorListener implements ProximitySensorManager.Listener {
- /** Used to show a blank view and hide the action bar. */
- private final Runnable mBlankRunnable = new Runnable() {
- @Override
- public void run() {
- View blankView = findViewById(R.id.blank);
- blankView.setVisibility(View.VISIBLE);
- getActionBar().hide();
- }
- };
- /** Used to remove the blank view and show the action bar. */
- private final Runnable mUnblankRunnable = new Runnable() {
- @Override
- public void run() {
- View blankView = findViewById(R.id.blank);
- blankView.setVisibility(View.GONE);
- getActionBar().show();
- }
- };
-
- @Override
- public synchronized void onNear() {
- clearPendingRequests();
- postDelayed(mBlankRunnable, PROXIMITY_BLANK_DELAY_MILLIS);
- }
-
- @Override
- public synchronized void onFar() {
- clearPendingRequests();
- postDelayed(mUnblankRunnable, PROXIMITY_UNBLANK_DELAY_MILLIS);
- }
-
- /** Removed any delayed requests that may be pending. */
- public synchronized void clearPendingRequests() {
- View blankView = findViewById(R.id.blank);
- blankView.removeCallbacks(mBlankRunnable);
- blankView.removeCallbacks(mUnblankRunnable);
- }
-
- /** Post a {@link Runnable} with a delay on the main thread. */
- private synchronized void postDelayed(Runnable runnable, long delayMillis) {
- // Post these instead of executing immediately so that:
- // - They are guaranteed to be executed on the main thread.
- // - If the sensor values changes rapidly for some time, the UI will not be
- // updated immediately.
- View blankView = findViewById(R.id.blank);
- blankView.postDelayed(runnable, delayMillis);
- }
- }
-
- static final String[] CALL_LOG_PROJECTION = new String[] {
- CallLog.Calls.DATE,
- CallLog.Calls.DURATION,
- CallLog.Calls.NUMBER,
- CallLog.Calls.TYPE,
- CallLog.Calls.COUNTRY_ISO,
- CallLog.Calls.GEOCODED_LOCATION,
- };
-
- static final int DATE_COLUMN_INDEX = 0;
- static final int DURATION_COLUMN_INDEX = 1;
- static final int NUMBER_COLUMN_INDEX = 2;
- static final int CALL_TYPE_COLUMN_INDEX = 3;
- static final int COUNTRY_ISO_COLUMN_INDEX = 4;
- static final int GEOCODED_LOCATION_COLUMN_INDEX = 5;
-
- private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (finishPhoneNumerSelectedActionModeIfShown()) {
- return;
- }
- startActivity(((ViewEntry) view.getTag()).primaryIntent);
- }
- };
-
- private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (finishPhoneNumerSelectedActionModeIfShown()) {
- return;
- }
- startActivity(((ViewEntry) view.getTag()).secondaryIntent);
- }
- };
-
- private final View.OnLongClickListener mPrimaryLongClickListener =
- new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (finishPhoneNumerSelectedActionModeIfShown()) {
- return true;
- }
- startPhoneNumberSelectedActionMode(v);
- return true;
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- setContentView(R.layout.call_detail);
-
- mAsyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
- mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- mResources = getResources();
-
- mCallTypeHelper = new CallTypeHelper(getResources());
- mPhoneNumberHelper = new PhoneNumberHelper(mResources);
- mPhoneCallDetailsHelper = new PhoneCallDetailsHelper(mResources, mCallTypeHelper,
- mPhoneNumberHelper);
- mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
- mAsyncQueryHandler = new CallDetailActivityQueryHandler(this);
- mHeaderTextView = (TextView) findViewById(R.id.header_text);
- mHeaderOverlayView = findViewById(R.id.photo_text_bar);
- mStatusMessageView = findViewById(R.id.voicemail_status);
- mStatusMessageText = (TextView) findViewById(R.id.voicemail_status_message);
- mStatusMessageAction = (TextView) findViewById(R.id.voicemail_status_action);
- mMainActionView = (ImageView) findViewById(R.id.main_action);
- mMainActionPushLayerView = (ImageButton) findViewById(R.id.main_action_push_layer);
- mContactBackgroundView = (ImageView) findViewById(R.id.contact_background);
- mDefaultCountryIso = ContactsUtils.getCurrentCountryIso(this);
- mContactPhotoManager = ContactPhotoManager.getInstance(this);
- mProximitySensorManager = new ProximitySensorManager(this, mProximitySensorListener);
- mContactInfoHelper = new ContactInfoHelper(this, ContactsUtils.getCurrentCountryIso(this));
- configureActionBar();
- optionallyHandleVoicemail();
- if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
- closeSystemDialogs();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateData(getCallLogEntryUris());
- }
-
- /**
- * Handle voicemail playback or hide voicemail ui.
- * <p>
- * If the Intent used to start this Activity contains the suitable extras, then start voicemail
- * playback. If it doesn't, then hide the voicemail ui.
- */
- private void optionallyHandleVoicemail() {
- View voicemailContainer = findViewById(R.id.voicemail_container);
- if (hasVoicemail()) {
- // Has voicemail: add the voicemail fragment. Add suitable arguments to set the uri
- // to play and optionally start the playback.
- // Do a query to fetch the voicemail status messages.
- VoicemailPlaybackFragment playbackFragment = new VoicemailPlaybackFragment();
- Bundle fragmentArguments = new Bundle();
- fragmentArguments.putParcelable(EXTRA_VOICEMAIL_URI, getVoicemailUri());
- if (getIntent().getBooleanExtra(EXTRA_VOICEMAIL_START_PLAYBACK, false)) {
- fragmentArguments.putBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, true);
- }
- playbackFragment.setArguments(fragmentArguments);
- voicemailContainer.setVisibility(View.VISIBLE);
- getFragmentManager().beginTransaction()
- .add(R.id.voicemail_container, playbackFragment).commitAllowingStateLoss();
- mAsyncQueryHandler.startVoicemailStatusQuery(getVoicemailUri());
- markVoicemailAsRead(getVoicemailUri());
- } else {
- // No voicemail uri: hide the status view.
- mStatusMessageView.setVisibility(View.GONE);
- voicemailContainer.setVisibility(View.GONE);
- }
- }
-
- private boolean hasVoicemail() {
- return getVoicemailUri() != null;
- }
-
- private Uri getVoicemailUri() {
- return getIntent().getParcelableExtra(EXTRA_VOICEMAIL_URI);
- }
-
- private void markVoicemailAsRead(final Uri voicemailUri) {
- mAsyncTaskExecutor.submit(Tasks.MARK_VOICEMAIL_READ, new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- ContentValues values = new ContentValues();
- values.put(Voicemails.IS_READ, true);
- getContentResolver().update(voicemailUri, values,
- Voicemails.IS_READ + " = 0", null);
- return null;
- }
- });
- }
-
- /**
- * Returns the list of URIs to show.
- * <p>
- * There are two ways the URIs can be provided to the activity: as the data on the intent, or as
- * a list of ids in the call log added as an extra on the URI.
- * <p>
- * If both are available, the data on the intent takes precedence.
- */
- private Uri[] getCallLogEntryUris() {
- Uri uri = getIntent().getData();
- if (uri != null) {
- // If there is a data on the intent, it takes precedence over the extra.
- return new Uri[]{ uri };
- }
- long[] ids = getIntent().getLongArrayExtra(EXTRA_CALL_LOG_IDS);
- Uri[] uris = new Uri[ids.length];
- for (int index = 0; index < ids.length; ++index) {
- uris[index] = ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, ids[index]);
- }
- return uris;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_CALL: {
- // Make sure phone isn't already busy before starting direct call
- TelephonyManager tm = (TelephonyManager)
- getSystemService(Context.TELEPHONY_SERVICE);
- if (tm.getCallState() == TelephonyManager.CALL_STATE_IDLE) {
- startActivity(ContactsUtils.getCallIntent(
- Uri.fromParts(Constants.SCHEME_TEL, mNumber, null)));
- return true;
- }
- }
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Update user interface with details of given call.
- *
- * @param callUris URIs into {@link CallLog.Calls} of the calls to be displayed
- */
- private void updateData(final Uri... callUris) {
- class UpdateContactDetailsTask extends AsyncTask<Void, Void, PhoneCallDetails[]> {
- @Override
- public PhoneCallDetails[] doInBackground(Void... params) {
- // TODO: All phone calls correspond to the same person, so we can make a single
- // lookup.
- final int numCalls = callUris.length;
- PhoneCallDetails[] details = new PhoneCallDetails[numCalls];
- try {
- for (int index = 0; index < numCalls; ++index) {
- details[index] = getPhoneCallDetailsForUri(callUris[index]);
- }
- return details;
- } catch (IllegalArgumentException e) {
- // Something went wrong reading in our primary data.
- Log.w(TAG, "invalid URI starting call details", e);
- return null;
- }
- }
-
- @Override
- public void onPostExecute(PhoneCallDetails[] details) {
- if (details == null) {
- // Somewhere went wrong: we're going to bail out and show error to users.
- Toast.makeText(CallDetailActivity.this, R.string.toast_call_detail_error,
- Toast.LENGTH_SHORT).show();
- finish();
- return;
- }
-
- // We know that all calls are from the same number and the same contact, so pick the
- // first.
- PhoneCallDetails firstDetails = details[0];
- mNumber = firstDetails.number.toString();
- final Uri contactUri = firstDetails.contactUri;
- final Uri photoUri = firstDetails.photoUri;
-
- // Set the details header, based on the first phone call.
- mPhoneCallDetailsHelper.setCallDetailsHeader(mHeaderTextView, firstDetails);
-
- // Cache the details about the phone number.
- final boolean canPlaceCallsTo = mPhoneNumberHelper.canPlaceCallsTo(mNumber);
- final boolean isVoicemailNumber = mPhoneNumberHelper.isVoicemailNumber(mNumber);
- final boolean isSipNumber = mPhoneNumberHelper.isSipNumber(mNumber);
-
- // Let user view contact details if they exist, otherwise add option to create new
- // 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 (contactUri != null) {
- mainActionIntent = new Intent(Intent.ACTION_VIEW, contactUri);
- // This will launch People's detail contact screen, so we probably want to
- // treat it as a separate People task.
- mainActionIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- 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.
- //
- // To fix this for SIP addresses, we need to:
- // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
- // the current number is a SIP address
- // - update the contacts UI code to handle Insert.SIP_ADDRESS by
- // updating the SipAddress field
- // 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) {
- mMainActionView.setVisibility(View.INVISIBLE);
- mMainActionPushLayerView.setVisibility(View.GONE);
- mHeaderTextView.setVisibility(View.INVISIBLE);
- mHeaderOverlayView.setVisibility(View.INVISIBLE);
- } else {
- mMainActionView.setVisibility(View.VISIBLE);
- mMainActionView.setImageResource(mainActionIcon);
- mMainActionPushLayerView.setVisibility(View.VISIBLE);
- mMainActionPushLayerView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(mainActionIntent);
- }
- });
- mMainActionPushLayerView.setContentDescription(mainActionDescription);
- mHeaderTextView.setVisibility(View.VISIBLE);
- mHeaderOverlayView.setVisibility(View.VISIBLE);
- }
-
- // This action allows to call the number that places the call.
- if (canPlaceCallsTo) {
- final CharSequence displayNumber =
- mPhoneNumberHelper.getDisplayNumber(
- firstDetails.number, firstDetails.formattedNumber);
-
- ViewEntry entry = new ViewEntry(
- getString(R.string.menu_callNumber,
- FormatUtils.forceLeftToRight(displayNumber)),
- ContactsUtils.getCallIntent(mNumber),
- 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.name)
- && !TextUtils.isEmpty(firstDetails.number)
- && !PhoneNumberUtils.isUriNumber(firstDetails.number.toString())) {
- entry.label = Phone.getTypeLabel(mResources, firstDetails.numberType,
- firstDetails.numberLabel);
- }
-
- // 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,
- new Intent(Intent.ACTION_SENDTO,
- Uri.fromParts("sms", mNumber, null)),
- getString(R.string.description_send_text_message, nameOrNumber));
- }
-
- configureCallButton(entry);
- mPhoneNumberToCopy = displayNumber;
- mPhoneNumberLabelToCopy = entry.label;
- } else {
- disableCallButton();
- mPhoneNumberToCopy = null;
- mPhoneNumberLabelToCopy = null;
- }
-
- mHasEditNumberBeforeCallOption =
- canPlaceCallsTo && !isSipNumber && !isVoicemailNumber;
- mHasTrashOption = hasVoicemail();
- mHasRemoveFromCallLogOption = !hasVoicemail();
- invalidateOptionsMenu();
-
- ListView historyList = (ListView) findViewById(R.id.history);
- historyList.setAdapter(
- new CallDetailHistoryAdapter(CallDetailActivity.this, mInflater,
- mCallTypeHelper, details, hasVoicemail(), canPlaceCallsTo,
- findViewById(R.id.controls)));
- BackScrollManager.bind(
- new ScrollableHeader() {
- private View mControls = findViewById(R.id.controls);
- private View mPhoto = findViewById(R.id.contact_background_sizer);
- private View mHeader = findViewById(R.id.photo_text_bar);
- private View mSeparator = findViewById(R.id.blue_separator);
-
- @Override
- public void setOffset(int offset) {
- mControls.setY(-offset);
- }
-
- @Override
- public int getMaximumScrollableHeaderOffset() {
- // We can scroll the photo out, but we should keep the header if
- // present.
- if (mHeader.getVisibility() == View.VISIBLE) {
- return mPhoto.getHeight() - mHeader.getHeight();
- } else {
- // If the header is not present, we should also scroll out the
- // separator line.
- return mPhoto.getHeight() + mSeparator.getHeight();
- }
- }
- },
- historyList);
- loadContactPhotos(photoUri);
- findViewById(R.id.call_detail).setVisibility(View.VISIBLE);
- }
- }
- mAsyncTaskExecutor.submit(Tasks.UPDATE_PHONE_CALL_DETAILS, new UpdateContactDetailsTask());
- }
-
- /** Return the phone call details for a given call log URI. */
- private PhoneCallDetails getPhoneCallDetailsForUri(Uri callUri) {
- ContentResolver resolver = getContentResolver();
- Cursor callCursor = resolver.query(callUri, CALL_LOG_PROJECTION, null, null, null);
- try {
- if (callCursor == null || !callCursor.moveToFirst()) {
- throw new IllegalArgumentException("Cannot find content: " + callUri);
- }
-
- // Read call log specifics.
- String number = callCursor.getString(NUMBER_COLUMN_INDEX);
- long date = callCursor.getLong(DATE_COLUMN_INDEX);
- long duration = callCursor.getLong(DURATION_COLUMN_INDEX);
- int callType = callCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- String countryIso = callCursor.getString(COUNTRY_ISO_COLUMN_INDEX);
- final String geocode = callCursor.getString(GEOCODED_LOCATION_COLUMN_INDEX);
-
- if (TextUtils.isEmpty(countryIso)) {
- countryIso = mDefaultCountryIso;
- }
-
- // Formatted phone number.
- final CharSequence formattedNumber;
- // Read contact specifics.
- final CharSequence nameText;
- final int numberType;
- final CharSequence numberLabel;
- final Uri photoUri;
- final Uri lookupUri;
- // If this is not a regular number, there is no point in looking it up in the contacts.
- ContactInfo info =
- mPhoneNumberHelper.canPlaceCallsTo(number)
- && !mPhoneNumberHelper.isVoicemailNumber(number)
- ? mContactInfoHelper.lookupNumber(number, countryIso)
- : null;
- if (info == null) {
- formattedNumber = mPhoneNumberHelper.getDisplayNumber(number, null);
- nameText = "";
- numberType = 0;
- numberLabel = "";
- photoUri = null;
- lookupUri = null;
- } else {
- formattedNumber = info.formattedNumber;
- nameText = info.name;
- numberType = info.type;
- numberLabel = info.label;
- photoUri = info.photoUri;
- lookupUri = info.lookupUri;
- }
- return new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
- new int[]{ callType }, date, duration,
- nameText, numberType, numberLabel, lookupUri, photoUri);
- } finally {
- if (callCursor != null) {
- callCursor.close();
- }
- }
- }
-
- /** Load the contact photos and places them in the corresponding views. */
- private void loadContactPhotos(Uri photoUri) {
- mContactPhotoManager.loadPhoto(mContactBackgroundView, photoUri,
- mContactBackgroundView.getWidth(), true);
- }
-
- 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;
- /** Icon for the secondary action. */
- 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, String description) {
- this.text = text;
- primaryIntent = intent;
- primaryDescription = description;
- }
-
- public void setSecondaryAction(int icon, Intent intent, String description) {
- secondaryIcon = icon;
- secondaryIntent = intent;
- secondaryDescription = description;
- }
- }
-
- /** Disables the call button area, e.g., for private numbers. */
- private void disableCallButton() {
- findViewById(R.id.call_and_sms).setVisibility(View.GONE);
- }
-
- /** 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);
-
- 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_text);
-
- View mainAction = convertView.findViewById(R.id.call_and_sms_main_action);
- mainAction.setOnClickListener(mPrimaryActionListener);
- mainAction.setTag(entry);
- mainAction.setContentDescription(entry.primaryDescription);
- mainAction.setOnLongClickListener(mPrimaryLongClickListener);
-
- 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);
-
- TextView label = (TextView) convertView.findViewById(R.id.call_and_sms_label);
- if (TextUtils.isEmpty(entry.label)) {
- label.setVisibility(View.GONE);
- } else {
- label.setText(entry.label);
- label.setVisibility(View.VISIBLE);
- }
- }
-
- protected void updateVoicemailStatusMessage(Cursor statusCursor) {
- if (statusCursor == null) {
- mStatusMessageView.setVisibility(View.GONE);
- return;
- }
- final StatusMessage message = getStatusMessage(statusCursor);
- if (message == null || !message.showInCallDetails()) {
- mStatusMessageView.setVisibility(View.GONE);
- return;
- }
-
- mStatusMessageView.setVisibility(View.VISIBLE);
- mStatusMessageText.setText(message.callDetailsMessageId);
- if (message.actionMessageId != -1) {
- mStatusMessageAction.setText(message.actionMessageId);
- }
- if (message.actionUri != null) {
- mStatusMessageAction.setClickable(true);
- mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- startActivity(new Intent(Intent.ACTION_VIEW, message.actionUri));
- }
- });
- } else {
- mStatusMessageAction.setClickable(false);
- }
- }
-
- private StatusMessage getStatusMessage(Cursor statusCursor) {
- List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
- if (messages.size() == 0) {
- return null;
- }
- // There can only be a single status message per source package, so num of messages can
- // at most be 1.
- if (messages.size() > 1) {
- Log.w(TAG, String.format("Expected 1, found (%d) num of status messages." +
- " Will use the first one.", messages.size()));
- }
- return messages.get(0);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.call_details_options, menu);
- return super.onCreateOptionsMenu(menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- // This action deletes all elements in the group from the call log.
- // We don't have this action for voicemails, because you can just use the trash button.
- menu.findItem(R.id.menu_remove_from_call_log).setVisible(mHasRemoveFromCallLogOption);
- menu.findItem(R.id.menu_edit_number_before_call).setVisible(mHasEditNumberBeforeCallOption);
- menu.findItem(R.id.menu_trash).setVisible(mHasTrashOption);
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home: {
- onHomeSelected();
- return true;
- }
-
- // All the options menu items are handled by onMenu... methods.
- default:
- throw new IllegalArgumentException();
- }
- }
-
- public void onMenuRemoveFromCallLog(MenuItem menuItem) {
- final StringBuilder callIds = new StringBuilder();
- for (Uri callUri : getCallLogEntryUris()) {
- if (callIds.length() != 0) {
- callIds.append(",");
- }
- callIds.append(ContentUris.parseId(callUri));
- }
- mAsyncTaskExecutor.submit(Tasks.REMOVE_FROM_CALL_LOG_AND_FINISH,
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL,
- Calls._ID + " IN (" + callIds + ")", null);
- return null;
- }
-
- @Override
- public void onPostExecute(Void result) {
- finish();
- }
- });
- }
-
- public void onMenuEditNumberBeforeCall(MenuItem menuItem) {
- startActivity(new Intent(Intent.ACTION_DIAL, ContactsUtils.getCallUri(mNumber)));
- }
-
- public void onMenuTrashVoicemail(MenuItem menuItem) {
- final Uri voicemailUri = getVoicemailUri();
- mAsyncTaskExecutor.submit(Tasks.DELETE_VOICEMAIL_AND_FINISH,
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... params) {
- getContentResolver().delete(voicemailUri, null, null);
- return null;
- }
- @Override
- public void onPostExecute(Void result) {
- finish();
- }
- });
- }
-
- private void configureActionBar() {
- ActionBar actionBar = getActionBar();
- if (actionBar != null) {
- actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME);
- }
- }
-
- /** Invoked when the user presses the home button in the action bar. */
- private void onHomeSelected() {
- Intent intent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI);
- // This will open the call log even if the detail view has been opened directly.
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- finish();
- }
-
- @Override
- protected void onPause() {
- // Immediately stop the proximity sensor.
- disableProximitySensor(false);
- mProximitySensorListener.clearPendingRequests();
- super.onPause();
- }
-
- @Override
- public void enableProximitySensor() {
- mProximitySensorManager.enable();
- }
-
- @Override
- public void disableProximitySensor(boolean waitForFarState) {
- mProximitySensorManager.disable(waitForFarState);
- }
-
- /**
- * If the phone number is selected, unselect it and return {@code true}.
- * Otherwise, just {@code false}.
- */
- private boolean finishPhoneNumerSelectedActionModeIfShown() {
- if (mPhoneNumberActionMode == null) return false;
- mPhoneNumberActionMode.finish();
- return true;
- }
-
- private void startPhoneNumberSelectedActionMode(View targetView) {
- mPhoneNumberActionMode = startActionMode(new PhoneNumberActionModeCallback(targetView));
- }
-
- private void closeSystemDialogs() {
- sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
- }
-
- private class PhoneNumberActionModeCallback implements ActionMode.Callback {
- private final View mTargetView;
- private final Drawable mOriginalViewBackground;
-
- public PhoneNumberActionModeCallback(View targetView) {
- mTargetView = targetView;
-
- // Highlight the phone number view. Remember the old background, and put a new one.
- mOriginalViewBackground = mTargetView.getBackground();
- mTargetView.setBackgroundColor(getResources().getColor(R.color.item_selected));
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- if (TextUtils.isEmpty(mPhoneNumberToCopy)) return false;
-
- getMenuInflater().inflate(R.menu.call_details_cab, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.copy_phone_number:
- ClipboardUtils.copyText(CallDetailActivity.this, mPhoneNumberLabelToCopy,
- mPhoneNumberToCopy, true);
- mode.finish(); // Close the CAB
- return true;
- }
- return false;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mPhoneNumberActionMode = null;
-
- // Restore the view background.
- mTargetView.setBackground(mOriginalViewBackground);
- }
- }
-}
diff --git a/src/com/android/contacts/CallDetailActivityQueryHandler.java b/src/com/android/contacts/CallDetailActivityQueryHandler.java
deleted file mode 100644
index 41cf937..0000000
--- a/src/com/android/contacts/CallDetailActivityQueryHandler.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.AsyncQueryHandler;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.VoicemailContract.Status;
-import android.provider.VoicemailContract.Voicemails;
-import android.util.Log;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
-
-/**
- * Class used by {@link CallDetailActivity} to fire async content resolver queries.
- */
-public class CallDetailActivityQueryHandler extends AsyncQueryHandler {
- private static final String TAG = "CallDetail";
- private static final int QUERY_VOICEMAIL_CONTENT_TOKEN = 101;
- private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 102;
-
- private final String[] VOICEMAIL_CONTENT_PROJECTION = new String[] {
- Voicemails.SOURCE_PACKAGE,
- Voicemails.HAS_CONTENT
- };
- private static final int SOURCE_PACKAGE_COLUMN_INDEX = 0;
- private static final int HAS_CONTENT_COLUMN_INDEX = 1;
-
- private final CallDetailActivity mCallDetailActivity;
-
- public CallDetailActivityQueryHandler(CallDetailActivity callDetailActivity) {
- super(callDetailActivity.getContentResolver());
- mCallDetailActivity = callDetailActivity;
- }
-
- /**
- * Fires a query to update voicemail status for the given voicemail record. On completion of the
- * query a call to {@link CallDetailActivity#updateVoicemailStatusMessage(Cursor)} is made.
- * <p>
- * if this is a voicemail record then it makes up to two asynchronous content resolver queries.
- * The first one to fetch voicemail content details and check if the voicemail record has audio.
- * If the voicemail record does not have an audio yet then it fires the second query to get the
- * voicemail status of the associated source.
- */
- public void startVoicemailStatusQuery(Uri voicemailUri) {
- startQuery(QUERY_VOICEMAIL_CONTENT_TOKEN, null, voicemailUri, VOICEMAIL_CONTENT_PROJECTION,
- null, null, null);
- }
-
- @Override
- protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
- try {
- if (token == QUERY_VOICEMAIL_CONTENT_TOKEN) {
- // Query voicemail status only if this voicemail record does not have audio.
- if (moveToFirst(cursor) && hasNoAudio(cursor)) {
- startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null,
- Status.buildSourceUri(getSourcePackage(cursor)),
- VoicemailStatusHelperImpl.PROJECTION, null, null, null);
- } else {
- // nothing to show in status
- mCallDetailActivity.updateVoicemailStatusMessage(null);
- }
- } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {
- mCallDetailActivity.updateVoicemailStatusMessage(cursor);
- } else {
- Log.w(TAG, "Unknown query completed: ignoring: " + token);
- }
- } finally {
- MoreCloseables.closeQuietly(cursor);
- }
- }
-
- /** Check that the cursor is non-null and can be moved to first. */
- private boolean moveToFirst(Cursor cursor) {
- if (cursor == null || !cursor.moveToFirst()) {
- Log.e(TAG, "Cursor not valid, could not move to first");
- return false;
- }
- return true;
- }
-
- private boolean hasNoAudio(Cursor voicemailCursor) {
- return voicemailCursor.getInt(HAS_CONTENT_COLUMN_INDEX) == 0;
- }
-
- private String getSourcePackage(Cursor voicemailCursor) {
- return voicemailCursor.getString(SOURCE_PACKAGE_COLUMN_INDEX);
- }
-}
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 3380553..678e7db 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -104,14 +104,6 @@
return mContactPhotoManager;
}
- if (ContactListFilterController.CONTACT_LIST_FILTER_SERVICE.equals(name)) {
- if (mContactListFilterController == null) {
- mContactListFilterController =
- ContactListFilterController.createContactListFilterController(this);
- }
- return mContactListFilterController;
- }
-
return super.getSystemService(name);
}
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 15b33ab..226061e 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -33,12 +33,12 @@
import android.view.View;
import android.widget.TextView;
-import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountWithDataSet;
import com.android.contacts.test.NeededForTesting;
import com.android.contacts.util.Constants;
+import com.android.phone.common.PhoneConstants;
import java.util.List;
@@ -269,7 +269,7 @@
final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (callOrigin != null) {
- intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin);
+ intent.putExtra(PhoneConstants.EXTRA_CALL_ORIGIN, callOrigin);
}
return intent;
}
diff --git a/src/com/android/contacts/PhoneCallDetails.java b/src/com/android/contacts/PhoneCallDetails.java
deleted file mode 100644
index 547695c..0000000
--- a/src/com/android/contacts/PhoneCallDetails.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.net.Uri;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-
-/**
- * The details of a phone call to be shown in the UI.
- */
-public class PhoneCallDetails {
- /** The number of the other party involved in the call. */
- public final CharSequence number;
- /** The formatted version of {@link #number}. */
- public final CharSequence formattedNumber;
- /** The country corresponding with the phone number. */
- public final String countryIso;
- /** The geocoded location for the phone number. */
- public final String geocode;
- /**
- * The type of calls, as defined in the call log table, e.g., {@link Calls#INCOMING_TYPE}.
- * <p>
- * There might be multiple types if this represents a set of entries grouped together.
- */
- public final int[] callTypes;
- /** The date of the call, in milliseconds since the epoch. */
- public final long date;
- /** The duration of the call in milliseconds, or 0 for missed calls. */
- public final long duration;
- /** The name of the contact, or the empty string. */
- public final CharSequence name;
- /** The type of phone, e.g., {@link Phone#TYPE_HOME}, 0 if not available. */
- public final int numberType;
- /** The custom label associated with the phone number in the contact, or the empty string. */
- public final CharSequence numberLabel;
- /** The URI of the contact associated with this phone call. */
- public final Uri contactUri;
- /**
- * The photo URI of the picture of the contact that is associated with this phone call or
- * null if there is none.
- * <p>
- * This is meant to store the high-res photo only.
- */
- public final Uri photoUri;
-
- /** Create the details for a call with a number not associated with a contact. */
- public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
- String countryIso, String geocode, int[] callTypes, long date, long duration) {
- this(number, formattedNumber, countryIso, geocode, callTypes, date, duration, "", 0, "",
- null, null);
- }
-
- /** Create the details for a call with a number associated with a contact. */
- public PhoneCallDetails(CharSequence number, CharSequence formattedNumber,
- String countryIso, String geocode, int[] callTypes, long date, long duration,
- CharSequence name, int numberType, CharSequence numberLabel, Uri contactUri,
- Uri photoUri) {
- this.number = number;
- this.formattedNumber = formattedNumber;
- this.countryIso = countryIso;
- this.geocode = geocode;
- this.callTypes = callTypes;
- this.date = date;
- this.duration = duration;
- this.name = name;
- this.numberType = numberType;
- this.numberLabel = numberLabel;
- this.contactUri = contactUri;
- this.photoUri = photoUri;
- }
-}
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
deleted file mode 100644
index 8192975..0000000
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.telephony.PhoneNumberUtils;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.style.ForegroundColorSpan;
-import android.text.style.StyleSpan;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.contacts.calllog.CallTypeHelper;
-import com.android.contacts.calllog.PhoneNumberHelper;
-import com.android.contacts.test.NeededForTesting;
-
-/**
- * Helper class to fill in the views in {@link PhoneCallDetailsViews}.
- */
-public class PhoneCallDetailsHelper {
- /** The maximum number of icons will be shown to represent the call types in a group. */
- private static final int MAX_CALL_TYPE_ICONS = 3;
-
- private final Resources mResources;
- /** The injected current time in milliseconds since the epoch. Used only by tests. */
- private Long mCurrentTimeMillisForTest;
- // Helper classes.
- private final CallTypeHelper mCallTypeHelper;
- private final PhoneNumberHelper mPhoneNumberHelper;
-
- /**
- * Creates a new instance of the helper.
- * <p>
- * Generally you should have a single instance of this helper in any context.
- *
- * @param resources used to look up strings
- */
- public PhoneCallDetailsHelper(Resources resources, CallTypeHelper callTypeHelper,
- PhoneNumberHelper phoneNumberHelper) {
- mResources = resources;
- mCallTypeHelper = callTypeHelper;
- mPhoneNumberHelper = phoneNumberHelper;
- }
-
- /** Fills the call details views with content. */
- public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details,
- boolean isHighlighted) {
- // Display up to a given number of icons.
- views.callTypeIcons.clear();
- int count = details.callTypes.length;
- for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) {
- views.callTypeIcons.add(details.callTypes[index]);
- }
- views.callTypeIcons.setVisibility(View.VISIBLE);
-
- // Show the total call count only if there are more than the maximum number of icons.
- final Integer callCount;
- if (count > MAX_CALL_TYPE_ICONS) {
- callCount = count;
- } else {
- callCount = null;
- }
- // The color to highlight the count and date in, if any. This is based on the first call.
- Integer highlightColor =
- isHighlighted ? mCallTypeHelper.getHighlightedColor(details.callTypes[0]) : null;
-
- // The date of this call, relative to the current time.
- CharSequence dateText =
- DateUtils.getRelativeTimeSpanString(details.date,
- getCurrentTimeMillis(),
- DateUtils.MINUTE_IN_MILLIS,
- DateUtils.FORMAT_ABBREV_RELATIVE);
-
- // Set the call count and date.
- setCallCountAndDate(views, callCount, dateText, highlightColor);
-
- CharSequence numberFormattedLabel = null;
- // Only show a label if the number is shown and it is not a SIP address.
- if (!TextUtils.isEmpty(details.number)
- && !PhoneNumberUtils.isUriNumber(details.number.toString())) {
- numberFormattedLabel = Phone.getTypeLabel(mResources, details.numberType,
- details.numberLabel);
- }
-
- final CharSequence nameText;
- final CharSequence numberText;
- final CharSequence labelText;
- final CharSequence displayNumber =
- mPhoneNumberHelper.getDisplayNumber(details.number, details.formattedNumber);
- if (TextUtils.isEmpty(details.name)) {
- nameText = displayNumber;
- if (TextUtils.isEmpty(details.geocode)
- || mPhoneNumberHelper.isVoicemailNumber(details.number)) {
- numberText = mResources.getString(R.string.call_log_empty_gecode);
- } else {
- numberText = details.geocode;
- }
- labelText = null;
- } else {
- nameText = details.name;
- numberText = displayNumber;
- labelText = numberFormattedLabel;
- }
-
- views.nameView.setText(nameText);
- views.numberView.setText(numberText);
- views.labelView.setText(labelText);
- views.labelView.setVisibility(TextUtils.isEmpty(labelText) ? View.GONE : View.VISIBLE);
- }
-
- /** Sets the text of the header view for the details page of a phone call. */
- public void setCallDetailsHeader(TextView nameView, PhoneCallDetails details) {
- final CharSequence nameText;
- final CharSequence displayNumber =
- mPhoneNumberHelper.getDisplayNumber(details.number,
- mResources.getString(R.string.recentCalls_addToContact));
- if (TextUtils.isEmpty(details.name)) {
- nameText = displayNumber;
- } else {
- nameText = details.name;
- }
-
- nameView.setText(nameText);
- }
-
- @NeededForTesting
- public void setCurrentTimeForTest(long currentTimeMillis) {
- mCurrentTimeMillisForTest = currentTimeMillis;
- }
-
- /**
- * Returns the current time in milliseconds since the epoch.
- * <p>
- * It can be injected in tests using {@link #setCurrentTimeForTest(long)}.
- */
- private long getCurrentTimeMillis() {
- if (mCurrentTimeMillisForTest == null) {
- return System.currentTimeMillis();
- } else {
- return mCurrentTimeMillisForTest;
- }
- }
-
- /** Sets the call count and date. */
- private void setCallCountAndDate(PhoneCallDetailsViews views, Integer callCount,
- CharSequence dateText, Integer highlightColor) {
- // Combine the count (if present) and the date.
- final CharSequence text;
- if (callCount != null) {
- text = mResources.getString(
- R.string.call_log_item_count_and_date, callCount.intValue(), dateText);
- } else {
- text = dateText;
- }
-
- // Apply the highlight color if present.
- final CharSequence formattedText;
- if (highlightColor != null) {
- formattedText = addBoldAndColor(text, highlightColor);
- } else {
- formattedText = text;
- }
-
- views.callTypeAndDate.setText(formattedText);
- }
-
- /** Creates a SpannableString for the given text which is bold and in the given color. */
- private CharSequence addBoldAndColor(CharSequence text, int color) {
- int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
- SpannableString result = new SpannableString(text);
- result.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), flags);
- result.setSpan(new ForegroundColorSpan(color), 0, text.length(), flags);
- return result;
- }
-}
diff --git a/src/com/android/contacts/PhoneCallDetailsViews.java b/src/com/android/contacts/PhoneCallDetailsViews.java
deleted file mode 100644
index 8be7f0c..0000000
--- a/src/com/android/contacts/PhoneCallDetailsViews.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import android.content.Context;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.contacts.calllog.CallTypeIconsView;
-
-/**
- * Encapsulates the views that are used to display the details of a phone call in the call log.
- */
-public final class PhoneCallDetailsViews {
- public final TextView nameView;
- public final View callTypeView;
- public final CallTypeIconsView callTypeIcons;
- public final TextView callTypeAndDate;
- public final TextView numberView;
- public final TextView labelView;
-
- private PhoneCallDetailsViews(TextView nameView, View callTypeView,
- CallTypeIconsView callTypeIcons, TextView callTypeAndDate, TextView numberView,
- TextView labelView) {
- this.nameView = nameView;
- this.callTypeView = callTypeView;
- this.callTypeIcons = callTypeIcons;
- this.callTypeAndDate = callTypeAndDate;
- this.numberView = numberView;
- this.labelView = labelView;
- }
-
- /**
- * Create a new instance by extracting the elements from the given view.
- * <p>
- * The view should contain three text views with identifiers {@code R.id.name},
- * {@code R.id.date}, and {@code R.id.number}, and a linear layout with identifier
- * {@code R.id.call_types}.
- */
- public static PhoneCallDetailsViews fromView(View view) {
- return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name),
- view.findViewById(R.id.call_type),
- (CallTypeIconsView) view.findViewById(R.id.call_type_icons),
- (TextView) view.findViewById(R.id.call_count_and_date),
- (TextView) view.findViewById(R.id.number),
- (TextView) view.findViewById(R.id.label));
- }
-
- public static PhoneCallDetailsViews createForTest(Context context) {
- return new PhoneCallDetailsViews(
- new TextView(context),
- new View(context),
- new CallTypeIconsView(context),
- new TextView(context),
- new TextView(context),
- new TextView(context));
- }
-}
diff --git a/src/com/android/contacts/ViewNotificationService.java b/src/com/android/contacts/ViewNotificationService.java
deleted file mode 100644
index 3bc5ed2..0000000
--- a/src/com/android/contacts/ViewNotificationService.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 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;
-
-import android.app.Service;
-import android.content.Intent;
-import android.content.Loader;
-import android.content.Loader.OnLoadCompleteListener;
-import android.os.IBinder;
-import android.util.Log;
-
-import com.android.contacts.model.Contact;
-import com.android.contacts.model.ContactLoader;
-
-
-/**
- * Service that sends out a view notification for a contact. At the moment, this is only
- * supposed to be used by the Phone app
- */
-public class ViewNotificationService extends Service {
- private static final String TAG = ViewNotificationService.class.getSimpleName();
-
- private static final boolean DEBUG = false;
-
- @Override
- public int onStartCommand(Intent intent, int flags, final int startId) {
- if (DEBUG) { Log.d(TAG, "onHandleIntent(). Intent: " + intent); }
-
- // We simply need to start a Loader here. When its done, it will send out the
- // View-Notification automatically.
- final ContactLoader contactLoader = new ContactLoader(this, intent.getData(), true);
- contactLoader.registerListener(0, new OnLoadCompleteListener<Contact>() {
- @Override
- public void onLoadComplete(Loader<Contact> loader, Contact data) {
- try {
- loader.reset();
- } catch (RuntimeException e) {
- Log.e(TAG, "Error reseting loader", e);
- }
- try {
- // This is not 100% accurate actually. If we get several calls quickly,
- // we might be stopping out-of-order, in which case the call with the last
- // startId will stop this service. In practice, this shouldn't be a problem,
- // as this service is supposed to be called by the Phone app which only sends
- // out the notification once per phonecall. And even if there is a problem,
- // the worst that should happen is a missing view notification
- stopSelfResult(startId);
- } catch (RuntimeException e) {
- Log.e(TAG, "Error stopping service", e);
- }
- }
- });
- contactLoader.startLoading();
- return START_REDELIVER_INTENT;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-}
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
deleted file mode 100644
index ea68407..0000000
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ /dev/null
@@ -1,1270 +0,0 @@
-/*
- * Copyright (C) 2008 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.activities;
-
-import android.app.ActionBar;
-import android.app.ActionBar.LayoutParams;
-import android.app.ActionBar.Tab;
-import android.app.ActionBar.TabListener;
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.FragmentManager;
-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.os.RemoteException;
-import android.os.ServiceManager;
-import android.preference.PreferenceManager;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents.UI;
-import android.support.v13.app.FragmentPagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.support.v4.view.ViewPager.OnPageChangeListener;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnFocusChangeListener;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.PopupMenu;
-import android.widget.SearchView;
-import android.widget.SearchView.OnCloseListener;
-import android.widget.SearchView.OnQueryTextListener;
-
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.R;
-import com.android.contacts.calllog.CallLogFragment;
-import com.android.contacts.dialpad.DialpadFragment;
-import com.android.contacts.interactions.PhoneNumberInteraction;
-import com.android.contacts.list.ContactListFilterController;
-import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
-import com.android.contacts.list.ContactListItemView;
-import com.android.contacts.list.OnPhoneNumberPickerActionListener;
-import com.android.contacts.list.PhoneFavoriteFragment;
-import com.android.contacts.list.PhoneNumberPickerFragment;
-import com.android.contacts.util.AccountFilterUtil;
-import com.android.contacts.util.Constants;
-import com.android.internal.telephony.ITelephony;
-
-/**
- * The dialer activity that has one tab with the virtual 12key
- * dialer, a tab with recent calls in it, a tab with the contacts and
- * a tab with the favorite. This is the container and the tabs are
- * embedded using intents.
- * The dialer tab's title is 'phone', a more common name (see strings.xml).
- */
-public class DialtactsActivity extends TransactionSafeActivity
- implements View.OnClickListener {
- private static final String TAG = "DialtactsActivity";
-
- public static final boolean DEBUG = false;
-
- /** Used to open Call Setting */
- private static final String PHONE_PACKAGE = "com.android.phone";
- private static final String CALL_SETTINGS_CLASS_NAME =
- "com.android.phone.CallFeaturesSetting";
-
- /**
- * Copied from PhoneApp. See comments in Phone app for more detail.
- */
- public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
- /** @see #getCallOrigin() */
- private static final String CALL_ORIGIN_DIALTACTS =
- "com.android.contacts.activities.DialtactsActivity";
-
- /**
- * Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}.
- */
- private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
-
- /** Used both by {@link ActionBar} and {@link ViewPagerAdapter} */
- private static final int TAB_INDEX_DIALER = 0;
- private static final int TAB_INDEX_CALL_LOG = 1;
- private static final int TAB_INDEX_FAVORITES = 2;
-
- private static final int TAB_INDEX_COUNT = 3;
-
- private SharedPreferences mPrefs;
-
- /** Last manually selected tab index */
- private static final String PREF_LAST_MANUALLY_SELECTED_TAB =
- "DialtactsActivity_last_manually_selected_tab";
- private static final int PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT = TAB_INDEX_DIALER;
-
- private static final int SUBACTIVITY_ACCOUNT_FILTER = 1;
-
- public class ViewPagerAdapter extends FragmentPagerAdapter {
- public ViewPagerAdapter(FragmentManager fm) {
- super(fm);
- }
-
- @Override
- public Fragment getItem(int position) {
- switch (position) {
- case TAB_INDEX_DIALER:
- return new DialpadFragment();
- case TAB_INDEX_CALL_LOG:
- return new CallLogFragment();
- case TAB_INDEX_FAVORITES:
- return new PhoneFavoriteFragment();
- }
- throw new IllegalStateException("No fragment at position " + position);
- }
-
- @Override
- public void setPrimaryItem(ViewGroup container, int position, Object object) {
- // The parent's setPrimaryItem() also calls setMenuVisibility(), so we want to know
- // when it happens.
- if (DEBUG) {
- Log.d(TAG, "FragmentPagerAdapter#setPrimaryItem(), position: " + position);
- }
- super.setPrimaryItem(container, position, object);
- }
-
- @Override
- public int getCount() {
- return TAB_INDEX_COUNT;
- }
- }
-
- /**
- * True when the app detects user's drag event. This variable should not become true when
- * mUserTabClick is true.
- *
- * During user's drag or tab click, we shouldn't show fake buttons but just show real
- * ActionBar at the bottom of the screen, for transition animation.
- */
- boolean mDuringSwipe = false;
- /**
- * True when the app detects user's tab click (at the top of the screen). This variable should
- * not become true when mDuringSwipe is true.
- *
- * During user's drag or tab click, we shouldn't show fake buttons but just show real
- * ActionBar at the bottom of the screen, for transition animation.
- */
- boolean mUserTabClick = false;
-
- private class PageChangeListener implements OnPageChangeListener {
- private int mCurrentPosition = -1;
- /**
- * Used during page migration, to remember the next position {@link #onPageSelected(int)}
- * specified.
- */
- private int mNextPosition = -1;
-
- @Override
- public void onPageScrolled(
- int position, float positionOffset, int positionOffsetPixels) {
- }
-
- @Override
- public void onPageSelected(int position) {
- if (DEBUG) Log.d(TAG, "onPageSelected: position: " + position);
- final ActionBar actionBar = getActionBar();
- if (mDialpadFragment != null) {
- if (mDuringSwipe && position == TAB_INDEX_DIALER) {
- // TODO: Figure out if we want this or not. Right now
- // - with this call, both fake buttons and real action bar overlap
- // - without this call, there's tiny flicker happening to search/menu buttons.
- // If we can reduce the flicker without this call, it would be much better.
- // updateFakeMenuButtonsVisibility(true);
- }
- }
-
- if (mCurrentPosition == position) {
- Log.w(TAG, "Previous position and next position became same (" + position + ")");
- }
-
- actionBar.selectTab(actionBar.getTabAt(position));
- mNextPosition = position;
- }
-
- public void setCurrentPosition(int position) {
- mCurrentPosition = position;
- }
-
- public int getCurrentPosition() {
- return mCurrentPosition;
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- switch (state) {
- case ViewPager.SCROLL_STATE_IDLE: {
- if (mNextPosition == -1) {
- // This happens when the user drags the screen just after launching the
- // application, and settle down the same screen without actually swiping it.
- // At that moment mNextPosition is apparently -1 yet, and we expect it
- // being updated by onPageSelected(), which is *not* called if the user
- // settle down the exact same tab after the dragging.
- if (DEBUG) {
- Log.d(TAG, "Next position is not specified correctly. Use current tab ("
- + mViewPager.getCurrentItem() + ")");
- }
- mNextPosition = mViewPager.getCurrentItem();
- }
- if (DEBUG) {
- Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_IDLE. "
- + "mCurrentPosition: " + mCurrentPosition
- + ", mNextPosition: " + mNextPosition);
- }
- // Interpret IDLE as the end of migration (both swipe and tab click)
- mDuringSwipe = false;
- mUserTabClick = false;
-
- updateFakeMenuButtonsVisibility(mNextPosition == TAB_INDEX_DIALER);
- sendFragmentVisibilityChange(mCurrentPosition, false);
- sendFragmentVisibilityChange(mNextPosition, true);
-
- invalidateOptionsMenu();
-
- mCurrentPosition = mNextPosition;
- break;
- }
- case ViewPager.SCROLL_STATE_DRAGGING: {
- if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_DRAGGING");
- mDuringSwipe = true;
- mUserTabClick = false;
- break;
- }
- case ViewPager.SCROLL_STATE_SETTLING: {
- if (DEBUG) Log.d(TAG, "onPageScrollStateChanged() with SCROLL_STATE_SETTLING");
- mDuringSwipe = true;
- mUserTabClick = false;
- break;
- }
- default:
- break;
- }
- }
- }
-
- private String mFilterText;
-
- /** Enables horizontal swipe between Fragments. */
- private ViewPager mViewPager;
- private final PageChangeListener mPageChangeListener = new PageChangeListener();
- private DialpadFragment mDialpadFragment;
- private CallLogFragment mCallLogFragment;
- private PhoneFavoriteFragment mPhoneFavoriteFragment;
-
- private View mSearchButton;
- private View mMenuButton;
-
- private final ContactListFilterListener mContactListFilterListener =
- new ContactListFilterListener() {
- @Override
- public void onContactListFilterChanged() {
- boolean doInvalidateOptionsMenu = false;
-
- if (mPhoneFavoriteFragment != null && mPhoneFavoriteFragment.isAdded()) {
- mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
- doInvalidateOptionsMenu = true;
- }
-
- if (mSearchFragment != null && mSearchFragment.isAdded()) {
- mSearchFragment.setFilter(mContactListFilterController.getFilter());
- doInvalidateOptionsMenu = true;
- } else {
- Log.w(TAG, "Search Fragment isn't available when ContactListFilter is changed");
- }
-
- if (doInvalidateOptionsMenu) {
- invalidateOptionsMenu();
- }
- }
- };
-
- private final TabListener mTabListener = new TabListener() {
- @Override
- public void onTabUnselected(Tab tab, FragmentTransaction ft) {
- if (DEBUG) Log.d(TAG, "onTabUnselected(). tab: " + tab);
- }
-
- @Override
- public void onTabSelected(Tab tab, FragmentTransaction ft) {
- if (DEBUG) {
- Log.d(TAG, "onTabSelected(). tab: " + tab + ", mDuringSwipe: " + mDuringSwipe);
- }
- // When the user swipes the screen horizontally, this method will be called after
- // ViewPager.SCROLL_STATE_DRAGGING and ViewPager.SCROLL_STATE_SETTLING events, while
- // when the user clicks a tab at the ActionBar at the top, this will be called before
- // them. This logic interprets the order difference as a difference of the user action.
- if (!mDuringSwipe) {
- if (DEBUG) {
- Log.d(TAG, "Tab select. from: " + mPageChangeListener.getCurrentPosition()
- + ", to: " + tab.getPosition());
- }
- if (mDialpadFragment != null) {
- updateFakeMenuButtonsVisibility(tab.getPosition() == TAB_INDEX_DIALER);
- }
- mUserTabClick = true;
- }
-
- if (mViewPager.getCurrentItem() != tab.getPosition()) {
- mViewPager.setCurrentItem(tab.getPosition(), true);
- }
-
- // During the call, we don't remember the tab position.
- if (!DialpadFragment.phoneIsInUse()) {
- // Remember this tab index. This function is also called, if the tab is set
- // automatically in which case the setter (setCurrentTab) has to set this to its old
- // value afterwards
- mLastManuallySelectedFragment = tab.getPosition();
- }
- }
-
- @Override
- public void onTabReselected(Tab tab, FragmentTransaction ft) {
- if (DEBUG) Log.d(TAG, "onTabReselected");
- }
- };
-
- /**
- * Fragment for searching phone numbers. Unlike the other Fragments, this doesn't correspond
- * to tab but is shown by a search action.
- */
- private PhoneNumberPickerFragment mSearchFragment;
- /**
- * True when this Activity is in its search UI (with a {@link SearchView} and
- * {@link PhoneNumberPickerFragment}).
- */
- private boolean mInSearchUi;
- private SearchView mSearchView;
-
- private final OnClickListener mFilterOptionClickListener = new OnClickListener() {
- @Override
- public void onClick(View view) {
- final PopupMenu popupMenu = new PopupMenu(DialtactsActivity.this, view);
- final Menu menu = popupMenu.getMenu();
- popupMenu.inflate(R.menu.dialtacts_search_options);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
- addContactOptionMenuItem.setIntent(
- new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
- popupMenu.show();
- }
- };
-
- /**
- * The index of the Fragment (or, the tab) that has last been manually selected.
- * This value does not keep track of programmatically set Tabs (e.g. Call Log after a Call)
- */
- private int mLastManuallySelectedFragment;
-
- private ContactListFilterController mContactListFilterController;
- private OnMenuItemClickListener mFilterOptionsMenuItemClickListener =
- new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- AccountFilterUtil.startAccountFilterActivityForResult(
- DialtactsActivity.this, SUBACTIVITY_ACCOUNT_FILTER,
- mContactListFilterController.getFilter());
- return true;
- }
- };
-
- private OnMenuItemClickListener mSearchMenuItemClickListener =
- new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- enterSearchUi();
- return true;
- }
- };
-
- /**
- * Listener used when one of phone numbers in search UI is selected. This will initiate a
- * phone call using the phone number.
- */
- private final OnPhoneNumberPickerActionListener mPhoneNumberPickerActionListener =
- new OnPhoneNumberPickerActionListener() {
- @Override
- public void onPickPhoneNumberAction(Uri dataUri) {
- // Specify call-origin so that users will see the previous tab instead of
- // CallLog screen (search UI will be automatically exited).
- PhoneNumberInteraction.startInteractionForPhoneCall(
- DialtactsActivity.this, dataUri, getCallOrigin());
- }
-
- @Override
- public void onShortcutIntentCreated(Intent intent) {
- Log.w(TAG, "Unsupported intent has come (" + intent + "). Ignoring.");
- }
-
- @Override
- public void onHomeInActionBarSelected() {
- exitSearchUi();
- }
- };
-
- /**
- * Listener used to send search queries to the phone search fragment.
- */
- private final OnQueryTextListener mPhoneSearchQueryTextListener =
- new OnQueryTextListener() {
- @Override
- public boolean onQueryTextSubmit(String query) {
- View view = getCurrentFocus();
- if (view != null) {
- hideInputMethod(view);
- view.clearFocus();
- }
- return true;
- }
-
- @Override
- public boolean onQueryTextChange(String newText) {
- // Show search result with non-empty text. Show a bare list otherwise.
- if (mSearchFragment != null) {
- mSearchFragment.setQueryString(newText, true);
- }
- return true;
- }
- };
-
- /**
- * Listener used to handle the "close" button on the right side of {@link SearchView}.
- * If some text is in the search view, this will clean it up. Otherwise this will exit
- * the search UI and let users go back to usual Phone UI.
- *
- * This does _not_ handle back button.
- */
- private final OnCloseListener mPhoneSearchCloseListener =
- new OnCloseListener() {
- @Override
- public boolean onClose() {
- if (!TextUtils.isEmpty(mSearchView.getQuery())) {
- mSearchView.setQuery(null, true);
- }
- return true;
- }
- };
-
- private final View.OnLayoutChangeListener mFirstLayoutListener
- = new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- v.removeOnLayoutChangeListener(this); // Unregister self.
- addSearchFragment();
- }
- };
-
- @Override
- protected void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- final Intent intent = getIntent();
- fixIntent(intent);
-
- setContentView(R.layout.dialtacts_activity);
-
- mContactListFilterController = ContactListFilterController.getInstance(this);
- mContactListFilterController.addListener(mContactListFilterListener);
-
- findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);
-
- mViewPager = (ViewPager) findViewById(R.id.pager);
- mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
- mViewPager.setOnPageChangeListener(mPageChangeListener);
- mViewPager.setOffscreenPageLimit(2);
-
- // Do same width calculation as ActionBar does
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int minCellSize = getResources().getDimensionPixelSize(R.dimen.fake_menu_button_min_width);
- int cellCount = dm.widthPixels / minCellSize;
- int fakeMenuItemWidth = dm.widthPixels / cellCount;
- if (DEBUG) Log.d(TAG, "The size of fake menu buttons (in pixel): " + fakeMenuItemWidth);
-
- // Soft menu button should appear only when there's no hardware menu button.
- mMenuButton = findViewById(R.id.overflow_menu);
- if (mMenuButton != null) {
- mMenuButton.setMinimumWidth(fakeMenuItemWidth);
- if (ViewConfiguration.get(this).hasPermanentMenuKey()) {
- // This is required for dialpad button's layout, so must not use GONE here.
- mMenuButton.setVisibility(View.INVISIBLE);
- } else {
- mMenuButton.setOnClickListener(this);
- }
- }
- mSearchButton = findViewById(R.id.searchButton);
- if (mSearchButton != null) {
- mSearchButton.setMinimumWidth(fakeMenuItemWidth);
- mSearchButton.setOnClickListener(this);
- }
-
- // Setup the ActionBar tabs (the order matches the tab-index contants TAB_INDEX_*)
- setupDialer();
- setupCallLog();
- setupFavorites();
- getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
- getActionBar().setDisplayShowTitleEnabled(false);
- getActionBar().setDisplayShowHomeEnabled(false);
-
- // Load the last manually loaded tab
- mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- mLastManuallySelectedFragment = mPrefs.getInt(PREF_LAST_MANUALLY_SELECTED_TAB,
- PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT);
- if (mLastManuallySelectedFragment >= TAB_INDEX_COUNT) {
- // Stored value may have exceeded the number of current tabs. Reset it.
- mLastManuallySelectedFragment = PREF_LAST_MANUALLY_SELECTED_TAB_DEFAULT;
- }
-
- setCurrentTab(intent);
-
- if (UI.FILTER_CONTACTS_ACTION.equals(intent.getAction())
- && icicle == null) {
- setupFilterText(intent);
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
- if (mPhoneFavoriteFragment != null) {
- mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
- }
- if (mSearchFragment != null) {
- mSearchFragment.setFilter(mContactListFilterController.getFilter());
- }
-
- if (mDuringSwipe || mUserTabClick) {
- if (DEBUG) Log.d(TAG, "reset buggy flag state..");
- mDuringSwipe = false;
- mUserTabClick = false;
- }
-
- final int currentPosition = mPageChangeListener.getCurrentPosition();
- if (DEBUG) {
- Log.d(TAG, "onStart(). current position: " + mPageChangeListener.getCurrentPosition()
- + ". Reset all menu visibility state.");
- }
- updateFakeMenuButtonsVisibility(currentPosition == TAB_INDEX_DIALER && !mInSearchUi);
- for (int i = 0; i < TAB_INDEX_COUNT; i++) {
- sendFragmentVisibilityChange(i, i == currentPosition);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mContactListFilterController.removeListener(mContactListFilterListener);
- }
-
- @Override
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.searchButton: {
- enterSearchUi();
- break;
- }
- case R.id.overflow_menu: {
- if (mDialpadFragment != null) {
- PopupMenu popup = mDialpadFragment.constructPopupMenu(view);
- if (popup != null) {
- popup.show();
- }
- } else {
- Log.w(TAG, "DialpadFragment is null during onClick() event for " + view);
- }
- break;
- }
- default: {
- Log.wtf(TAG, "Unexpected onClick event from " + view);
- break;
- }
- }
- }
-
- /**
- * Add search fragment. Note this is called during onLayout, so there's some restrictions,
- * such as executePendingTransaction can't be used in it.
- */
- private void addSearchFragment() {
- // In order to take full advantage of "fragment deferred start", we need to create the
- // search fragment after all other fragments are created.
- // The other fragments are created by the ViewPager on the first onMeasure().
- // We use the first onLayout call, which is after onMeasure().
-
- // Just return if the fragment is already created, which happens after configuration
- // changes.
- if (mSearchFragment != null) return;
-
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final Fragment searchFragment = new PhoneNumberPickerFragment();
-
- searchFragment.setUserVisibleHint(false);
- ft.add(R.id.dialtacts_frame, searchFragment);
- ft.hide(searchFragment);
- ft.commitAllowingStateLoss();
- }
-
- private void prepareSearchView() {
- final View searchViewLayout =
- getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
- mSearchView = (SearchView) searchViewLayout.findViewById(R.id.search_view);
- mSearchView.setOnQueryTextListener(mPhoneSearchQueryTextListener);
- mSearchView.setOnCloseListener(mPhoneSearchCloseListener);
- // Since we're using a custom layout for showing SearchView instead of letting the
- // search menu icon do that job, we need to manually configure the View so it looks
- // "shown via search menu".
- // - it should be iconified by default
- // - it should not be iconified at this time
- // See also comments for onActionViewExpanded()/onActionViewCollapsed()
- mSearchView.setIconifiedByDefault(true);
- mSearchView.setQueryHint(getString(R.string.hint_findContacts));
- mSearchView.setIconified(false);
- mSearchView.setOnQueryTextFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- if (hasFocus) {
- showInputMethod(view.findFocus());
- }
- }
- });
-
- if (!ViewConfiguration.get(this).hasPermanentMenuKey()) {
- // Filter option menu should be shown on the right side of SearchView.
- final View filterOptionView = searchViewLayout.findViewById(R.id.search_option);
- filterOptionView.setVisibility(View.VISIBLE);
- filterOptionView.setOnClickListener(mFilterOptionClickListener);
- }
-
- getActionBar().setCustomView(searchViewLayout,
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- }
-
- @Override
- public void onAttachFragment(Fragment fragment) {
- // This method can be called before onCreate(), at which point we cannot rely on ViewPager.
- // In that case, we will setup the "current position" soon after the ViewPager is ready.
- final int currentPosition = mViewPager != null ? mViewPager.getCurrentItem() : -1;
-
- if (fragment instanceof DialpadFragment) {
- mDialpadFragment = (DialpadFragment) fragment;
- } else if (fragment instanceof CallLogFragment) {
- mCallLogFragment = (CallLogFragment) fragment;
- } else if (fragment instanceof PhoneFavoriteFragment) {
- mPhoneFavoriteFragment = (PhoneFavoriteFragment) fragment;
- mPhoneFavoriteFragment.setListener(mPhoneFavoriteListener);
- if (mContactListFilterController != null
- && mContactListFilterController.getFilter() != null) {
- mPhoneFavoriteFragment.setFilter(mContactListFilterController.getFilter());
- }
- } else if (fragment instanceof PhoneNumberPickerFragment) {
- mSearchFragment = (PhoneNumberPickerFragment) fragment;
- mSearchFragment.setOnPhoneNumberPickerActionListener(mPhoneNumberPickerActionListener);
- mSearchFragment.setQuickContactEnabled(true);
- mSearchFragment.setDarkTheme(true);
- mSearchFragment.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
- mSearchFragment.setUseCallableUri(true);
- if (mContactListFilterController != null
- && mContactListFilterController.getFilter() != null) {
- mSearchFragment.setFilter(mContactListFilterController.getFilter());
- }
- // Here we assume that we're not on the search mode, so let's hide the fragment.
- //
- // We get here either when the fragment is created (normal case), or after configuration
- // changes. In the former case, we're not in search mode because we can only
- // enter search mode if the fragment is created. (see enterSearchUi())
- // In the latter case we're not in search mode either because we don't retain
- // mInSearchUi -- ideally we should but at this point it's not supported.
- mSearchFragment.setUserVisibleHint(false);
- // After configuration changes fragments will forget their "hidden" state, so make
- // sure to hide it.
- if (!mSearchFragment.isHidden()) {
- final FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.hide(mSearchFragment);
- transaction.commitAllowingStateLoss();
- }
- }
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- mPrefs.edit().putInt(PREF_LAST_MANUALLY_SELECTED_TAB, mLastManuallySelectedFragment)
- .apply();
- }
-
- private void fixIntent(Intent intent) {
- // This should be cleaned up: the call key used to send an Intent
- // that just said to go to the recent calls list. It now sends this
- // abstract action, but this class hasn't been rewritten to deal with it.
- if (Intent.ACTION_CALL_BUTTON.equals(intent.getAction())) {
- intent.setDataAndType(Calls.CONTENT_URI, Calls.CONTENT_TYPE);
- intent.putExtra("call_key", true);
- setIntent(intent);
- }
- }
-
- private void setupDialer() {
- final Tab tab = getActionBar().newTab();
- tab.setContentDescription(R.string.dialerIconLabel);
- tab.setTabListener(mTabListener);
- tab.setIcon(R.drawable.ic_tab_dialer);
- getActionBar().addTab(tab);
- }
-
- private void setupCallLog() {
- final Tab tab = getActionBar().newTab();
- tab.setContentDescription(R.string.recentCallsIconLabel);
- tab.setIcon(R.drawable.ic_tab_recent);
- tab.setTabListener(mTabListener);
- getActionBar().addTab(tab);
- }
-
- private void setupFavorites() {
- final Tab tab = getActionBar().newTab();
- tab.setContentDescription(R.string.contactsFavoritesLabel);
- tab.setIcon(R.drawable.ic_tab_all);
- tab.setTabListener(mTabListener);
- getActionBar().addTab(tab);
- }
-
- /**
- * Returns true if the intent is due to hitting the green send key (hardware call button:
- * KEYCODE_CALL) while in a call.
- *
- * @param intent the intent that launched this activity
- * @param recentCallsRequest true if the intent is requesting to view recent calls
- * @return true if the intent is due to hitting the green send key while in a call
- */
- private boolean isSendKeyWhileInCall(final Intent intent,
- final boolean recentCallsRequest) {
- // If there is a call in progress go to the call screen
- if (recentCallsRequest) {
- final boolean callKey = intent.getBooleanExtra("call_key", false);
-
- try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (callKey && phone != null && phone.showCallScreen()) {
- return true;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to handle send while in call", e);
- }
- }
-
- return false;
- }
-
- /**
- * Sets the current tab based on the intent's request type
- *
- * @param intent Intent that contains information about which tab should be selected
- */
- private void setCurrentTab(Intent intent) {
- // If we got here by hitting send and we're in call forward along to the in-call activity
- boolean recentCallsRequest = Calls.CONTENT_TYPE.equals(intent.resolveType(
- getContentResolver()));
- if (isSendKeyWhileInCall(intent, recentCallsRequest)) {
- finish();
- return;
- }
-
- // Remember the old manually selected tab index so that it can be restored if it is
- // overwritten by one of the programmatic tab selections
- final int savedTabIndex = mLastManuallySelectedFragment;
-
- final int tabIndex;
- if (DialpadFragment.phoneIsInUse() || isDialIntent(intent)) {
- tabIndex = TAB_INDEX_DIALER;
- } else if (recentCallsRequest) {
- tabIndex = TAB_INDEX_CALL_LOG;
- } else {
- tabIndex = mLastManuallySelectedFragment;
- }
-
- final int previousItemIndex = mViewPager.getCurrentItem();
- mViewPager.setCurrentItem(tabIndex, false /* smoothScroll */);
- if (previousItemIndex != tabIndex) {
- sendFragmentVisibilityChange(previousItemIndex, false /* not visible */ );
- }
- mPageChangeListener.setCurrentPosition(tabIndex);
- sendFragmentVisibilityChange(tabIndex, true /* visible */ );
-
- // Restore to the previous manual selection
- mLastManuallySelectedFragment = savedTabIndex;
- mDuringSwipe = false;
- mUserTabClick = false;
- }
-
- @Override
- public void onNewIntent(Intent newIntent) {
- setIntent(newIntent);
- fixIntent(newIntent);
- setCurrentTab(newIntent);
- final String action = newIntent.getAction();
- if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
- setupFilterText(newIntent);
- }
- if (mInSearchUi || (mSearchFragment != null && mSearchFragment.isVisible())) {
- exitSearchUi();
- }
-
- if (mViewPager.getCurrentItem() == TAB_INDEX_DIALER) {
- if (mDialpadFragment != null) {
- mDialpadFragment.configureScreenFromIntent(newIntent);
- } else {
- Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
- }
- } else if (mViewPager.getCurrentItem() == TAB_INDEX_CALL_LOG) {
- if (mCallLogFragment != null) {
- mCallLogFragment.configureScreenFromIntent(newIntent);
- } else {
- Log.e(TAG, "CallLogFragment isn't ready yet when the tab is already selected.");
- }
- }
- invalidateOptionsMenu();
- }
-
- /** Returns true if the given intent contains a phone number to populate the dialer with */
- private boolean isDialIntent(Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
- return true;
- }
- if (Intent.ACTION_VIEW.equals(action)) {
- final Uri data = intent.getData();
- if (data != null && Constants.SCHEME_TEL.equals(data.getScheme())) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns an appropriate call origin for this Activity. May return null when no call origin
- * should be used (e.g. when some 3rd party application launched the screen. Call origin is
- * for remembering the tab in which the user made a phone call, so the external app's DIAL
- * request should not be counted.)
- */
- public String getCallOrigin() {
- return !isDialIntent(getIntent()) ? CALL_ORIGIN_DIALTACTS : null;
- }
-
- /**
- * Retrieves the filter text stored in {@link #setupFilterText(Intent)}.
- * This text originally came from a FILTER_CONTACTS_ACTION intent received
- * by this activity. The stored text will then be cleared after after this
- * method returns.
- *
- * @return The stored filter text
- */
- public String getAndClearFilterText() {
- String filterText = mFilterText;
- mFilterText = null;
- return filterText;
- }
-
- /**
- * Stores the filter text associated with a FILTER_CONTACTS_ACTION intent.
- * This is so child activities can check if they are supposed to display a filter.
- *
- * @param intent The intent received in {@link #onNewIntent(Intent)}
- */
- private void setupFilterText(Intent intent) {
- // If the intent was relaunched from history, don't apply the filter text.
- if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
- return;
- }
- String filter = intent.getStringExtra(UI.FILTER_TEXT_EXTRA_KEY);
- if (filter != null && filter.length() > 0) {
- mFilterText = filter;
- }
- }
-
- @Override
- public void onBackPressed() {
- if (mInSearchUi) {
- // We should let the user go back to usual screens with tabs.
- exitSearchUi();
- } else if (isTaskRoot()) {
- // Instead of stopping, simply push this to the back of the stack.
- // This is only done when running at the top of the stack;
- // otherwise, we have been launched by someone else so need to
- // allow the user to go back to the caller.
- moveTaskToBack(false);
- } else {
- super.onBackPressed();
- }
- }
-
- private final PhoneFavoriteFragment.Listener mPhoneFavoriteListener =
- new PhoneFavoriteFragment.Listener() {
- @Override
- public void onContactSelected(Uri contactUri) {
- PhoneNumberInteraction.startInteractionForPhoneCall(
- DialtactsActivity.this, contactUri, getCallOrigin());
- }
-
- @Override
- public void onCallNumberDirectly(String phoneNumber) {
- Intent intent = ContactsUtils.getCallIntent(phoneNumber, getCallOrigin());
- startActivity(intent);
- }
- };
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.dialtacts_options, menu);
-
- // set up intents and onClick listeners
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
- final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
-
- callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
- searchMenuItem.setOnMenuItemClickListener(mSearchMenuItemClickListener);
- filterOptionMenuItem.setOnMenuItemClickListener(mFilterOptionsMenuItemClickListener);
- addContactOptionMenuItem.setIntent(
- new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI));
-
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- if (mInSearchUi) {
- prepareOptionsMenuInSearchMode(menu);
- } else {
- // get reference to the currently selected tab
- final Tab tab = getActionBar().getSelectedTab();
- if (tab != null) {
- switch(tab.getPosition()) {
- case TAB_INDEX_DIALER:
- prepareOptionsMenuForDialerTab(menu);
- break;
- case TAB_INDEX_CALL_LOG:
- prepareOptionsMenuForCallLogTab(menu);
- break;
- case TAB_INDEX_FAVORITES:
- prepareOptionsMenuForFavoritesTab(menu);
- break;
- }
- }
- }
- return true;
- }
-
- private void prepareOptionsMenuInSearchMode(Menu menu) {
- // get references to menu items
- final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
- final MenuItem emptyRightMenuItem = menu.findItem(R.id.empty_right_menu_item);
-
- // prepare the menu items
- searchMenuItem.setVisible(false);
- filterOptionMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
- addContactOptionMenuItem.setVisible(false);
- callSettingsMenuItem.setVisible(false);
- emptyRightMenuItem.setVisible(false);
- }
-
- private void prepareOptionsMenuForDialerTab(Menu menu) {
- if (DEBUG) {
- Log.d(TAG, "onPrepareOptionsMenu(dialer). swipe: " + mDuringSwipe
- + ", user tab click: " + mUserTabClick);
- }
-
- // get references to menu items
- final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
- final MenuItem emptyRightMenuItem = menu.findItem(R.id.empty_right_menu_item);
-
- // prepare the menu items
- filterOptionMenuItem.setVisible(false);
- addContactOptionMenuItem.setVisible(false);
- if (mDuringSwipe || mUserTabClick) {
- // During horizontal movement, the real ActionBar menu items are shown
- searchMenuItem.setVisible(true);
- callSettingsMenuItem.setVisible(true);
- // When there is a permanent menu key, there is no overflow icon on the right of
- // the action bar which would force the search menu item (if it is visible) to the
- // left. This is the purpose of showing the emptyRightMenuItem.
- emptyRightMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
- } else {
- // This is when the user is looking at the dialer pad. In this case, the real
- // ActionBar is hidden and fake menu items are shown.
- // Except in landscape, in which case the real search menu item is shown.
- searchMenuItem.setVisible(ContactsUtils.isLandscape(this));
- // If a permanent menu key is available, then we need to show the call settings item
- // so that the call settings item can be invoked by the permanent menu key.
- callSettingsMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
- emptyRightMenuItem.setVisible(false);
- }
- }
-
- private void prepareOptionsMenuForCallLogTab(Menu menu) {
- // get references to menu items
- final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
- final MenuItem emptyRightMenuItem = menu.findItem(R.id.empty_right_menu_item);
-
- // prepare the menu items
- searchMenuItem.setVisible(true);
- filterOptionMenuItem.setVisible(false);
- addContactOptionMenuItem.setVisible(false);
- callSettingsMenuItem.setVisible(true);
- emptyRightMenuItem.setVisible(ViewConfiguration.get(this).hasPermanentMenuKey());
- }
-
- private void prepareOptionsMenuForFavoritesTab(Menu menu) {
- // get references to menu items
- final MenuItem searchMenuItem = menu.findItem(R.id.search_on_action_bar);
- final MenuItem filterOptionMenuItem = menu.findItem(R.id.filter_option);
- final MenuItem addContactOptionMenuItem = menu.findItem(R.id.add_contact);
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings);
- final MenuItem emptyRightMenuItem = menu.findItem(R.id.empty_right_menu_item);
-
- // prepare the menu items
- searchMenuItem.setVisible(true);
- filterOptionMenuItem.setVisible(true);
- addContactOptionMenuItem.setVisible(true);
- callSettingsMenuItem.setVisible(true);
- emptyRightMenuItem.setVisible(false);
- }
-
- @Override
- public void startSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData, boolean globalSearch) {
- if (mSearchFragment != null && mSearchFragment.isAdded() && !globalSearch) {
- if (mInSearchUi) {
- if (mSearchView.hasFocus()) {
- showInputMethod(mSearchView.findFocus());
- } else {
- mSearchView.requestFocus();
- }
- } else {
- enterSearchUi();
- }
- } else {
- super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
- }
- }
-
- /**
- * Hides every tab and shows search UI for phone lookup.
- */
- private void enterSearchUi() {
- if (mSearchFragment == null) {
- // We add the search fragment dynamically in the first onLayoutChange() and
- // mSearchFragment is set sometime later when the fragment transaction is actually
- // executed, which means there's a window when users are able to hit the (physical)
- // search key but mSearchFragment is still null.
- // It's quite hard to handle this case right, so let's just ignore the search key
- // in this case. Users can just hit it again and it will work this time.
- return;
- }
- if (mSearchView == null) {
- prepareSearchView();
- }
-
- final ActionBar actionBar = getActionBar();
-
- final Tab tab = actionBar.getSelectedTab();
-
- // User can search during the call, but we don't want to remember the status.
- if (tab != null && !DialpadFragment.phoneIsInUse()) {
- mLastManuallySelectedFragment = tab.getPosition();
- }
-
- mSearchView.setQuery(null, true);
-
- actionBar.setDisplayShowCustomEnabled(true);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setDisplayShowHomeEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
-
- updateFakeMenuButtonsVisibility(false);
-
- for (int i = 0; i < TAB_INDEX_COUNT; i++) {
- sendFragmentVisibilityChange(i, false /* not visible */ );
- }
-
- // Show the search fragment and hide everything else.
- mSearchFragment.setUserVisibleHint(true);
- final FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.show(mSearchFragment);
- transaction.commitAllowingStateLoss();
- mViewPager.setVisibility(View.GONE);
-
- // We need to call this and onActionViewCollapsed() manually, since we are using a custom
- // layout instead of asking the search menu item to take care of SearchView.
- mSearchView.onActionViewExpanded();
- mInSearchUi = true;
- }
-
- private void showInputMethod(View view) {
- InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null) {
- if (!imm.showSoftInput(view, 0)) {
- Log.w(TAG, "Failed to show soft input method.");
- }
- }
- }
-
- private void hideInputMethod(View view) {
- InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null && view != null) {
- imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
- }
- }
-
- /**
- * Goes back to usual Phone UI with tags. Previously selected Tag and associated Fragment
- * should be automatically focused again.
- */
- private void exitSearchUi() {
- final ActionBar actionBar = getActionBar();
-
- // Hide the search fragment, if exists.
- if (mSearchFragment != null) {
- mSearchFragment.setUserVisibleHint(false);
-
- final FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.hide(mSearchFragment);
- transaction.commitAllowingStateLoss();
- }
-
- // We want to hide SearchView and show Tabs. Also focus on previously selected one.
- actionBar.setDisplayShowCustomEnabled(false);
- actionBar.setDisplayShowHomeEnabled(false);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
-
- for (int i = 0; i < TAB_INDEX_COUNT; i++) {
- sendFragmentVisibilityChange(i, i == mViewPager.getCurrentItem());
- }
-
- // Before exiting the search screen, reset swipe state.
- mDuringSwipe = false;
- mUserTabClick = false;
-
- mViewPager.setVisibility(View.VISIBLE);
-
- hideInputMethod(getCurrentFocus());
-
- // Request to update option menu.
- invalidateOptionsMenu();
-
- // See comments in onActionViewExpanded()
- mSearchView.onActionViewCollapsed();
- mInSearchUi = false;
- }
-
- private Fragment getFragmentAt(int position) {
- switch (position) {
- case TAB_INDEX_DIALER:
- return mDialpadFragment;
- case TAB_INDEX_CALL_LOG:
- return mCallLogFragment;
- case TAB_INDEX_FAVORITES:
- return mPhoneFavoriteFragment;
- default:
- throw new IllegalStateException("Unknown fragment index: " + position);
- }
- }
-
- private void sendFragmentVisibilityChange(int position, boolean visibility) {
- if (DEBUG) {
- Log.d(TAG, "sendFragmentVisibiltyChange(). position: " + position
- + ", visibility: " + visibility);
- }
- // Position can be -1 initially. See PageChangeListener.
- if (position >= 0) {
- final Fragment fragment = getFragmentAt(position);
- if (fragment != null) {
- fragment.setMenuVisibility(visibility);
- fragment.setUserVisibleHint(visibility);
- }
- }
- }
-
- /**
- * Update visibility of the search button and menu button at the bottom.
- * They should be invisible when bottom ActionBar's real items are available, and be visible
- * otherwise.
- *
- * @param visible True when visible.
- */
- private void updateFakeMenuButtonsVisibility(boolean visible) {
- // Note: Landscape mode does not have the fake menu and search buttons.
- if (DEBUG) {
- Log.d(TAG, "updateFakeMenuButtonVisibility(" + visible + ")");
- }
-
- if (mSearchButton != null) {
- if (visible) {
- mSearchButton.setVisibility(View.VISIBLE);
- } else {
- mSearchButton.setVisibility(View.INVISIBLE);
- }
- }
- if (mMenuButton != null) {
- if (visible && !ViewConfiguration.get(this).hasPermanentMenuKey()) {
- mMenuButton.setVisibility(View.VISIBLE);
- } else {
- mMenuButton.setVisibility(View.INVISIBLE);
- }
- }
- }
-
- /** Returns an Intent to launch Call Settings screen */
- public static Intent getCallSettingsIntent() {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(PHONE_PACKAGE, CALL_SETTINGS_CLASS_NAME);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return intent;
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (resultCode != Activity.RESULT_OK) {
- return;
- }
- switch (requestCode) {
- case SUBACTIVITY_ACCOUNT_FILTER: {
- AccountFilterUtil.handleAccountFilterResult(
- mContactListFilterController, resultCode, data);
- }
- break;
- }
- }
-}
diff --git a/src/com/android/contacts/activities/NonPhoneActivity.java b/src/com/android/contacts/activities/NonPhoneActivity.java
deleted file mode 100644
index fc22146..0000000
--- a/src/com/android/contacts/activities/NonPhoneActivity.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2010 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.activities;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents.Insert;
-import android.text.TextUtils;
-
-import com.android.contacts.ContactsActivity;
-import com.android.contacts.R;
-import com.android.contacts.util.Constants;
-
-/**
- * Activity that intercepts DIAL and VIEW intents for phone numbers for devices that can not
- * be used as a phone. This allows the user to see the phone number
- */
-public class NonPhoneActivity extends ContactsActivity {
-
- private static final String PHONE_NUMBER_KEY = "PHONE_NUMBER";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final String phoneNumber = getPhoneNumber();
- if (TextUtils.isEmpty(phoneNumber)) {
- finish();
- return;
- }
-
- final NonPhoneDialogFragment fragment = new NonPhoneDialogFragment();
- Bundle bundle = new Bundle();
- bundle.putString(PHONE_NUMBER_KEY, phoneNumber);
- fragment.setArguments(bundle);
- getFragmentManager().beginTransaction().add(fragment, "Fragment").commitAllowingStateLoss();
- }
-
- private String getPhoneNumber() {
- if (getIntent() == null) return null;
- final Uri data = getIntent().getData();
- if (data == null) return null;
- final String scheme = data.getScheme();
- if (!Constants.SCHEME_TEL.equals(scheme)) return null;
- return getIntent().getData().getSchemeSpecificPart();
- }
-
- public static final class NonPhoneDialogFragment extends DialogFragment
- implements OnClickListener {
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final AlertDialog alertDialog;
- alertDialog = new AlertDialog.Builder(getActivity(), R.style.NonPhoneDialogTheme)
- .create();
- alertDialog.setTitle(R.string.non_phone_caption);
- alertDialog.setMessage(getArgumentPhoneNumber());
- alertDialog.setButton(DialogInterface.BUTTON_POSITIVE,
- getActivity().getString(R.string.non_phone_add_to_contacts), this);
- alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
- getActivity().getString(R.string.non_phone_close), this);
- return alertDialog;
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- intent.setType(Contacts.CONTENT_ITEM_TYPE);
- intent.putExtra(Insert.PHONE, getArgumentPhoneNumber());
- startActivity(intent);
- }
- dismiss();
- }
-
- private String getArgumentPhoneNumber() {
- return getArguments().getString(PHONE_NUMBER_KEY);
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- super.onDismiss(dialog);
- // During screen rotation, getActivity returns null. In this case we do not
- // want to close the Activity anyway
- final Activity activity = getActivity();
- if (activity != null) activity.finish();
- }
- }
-}
diff --git a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
deleted file mode 100644
index 9183208..0000000
--- a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.provider.CallLog.Calls;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.TextView;
-
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.R;
-
-/**
- * Adapter for a ListView containing history items from the details of a call.
- */
-public class CallDetailHistoryAdapter extends BaseAdapter {
- /** The top element is a blank header, which is hidden under the rest of the UI. */
- private static final int VIEW_TYPE_HEADER = 0;
- /** Each history item shows the detail of a call. */
- private static final int VIEW_TYPE_HISTORY_ITEM = 1;
-
- private final Context mContext;
- private final LayoutInflater mLayoutInflater;
- private final CallTypeHelper mCallTypeHelper;
- private final PhoneCallDetails[] mPhoneCallDetails;
- /** Whether the voicemail controls are shown. */
- private final boolean mShowVoicemail;
- /** Whether the call and SMS controls are shown. */
- private final boolean mShowCallAndSms;
- /** The controls that are shown on top of the history list. */
- private final View mControls;
- /** The listener to changes of focus of the header. */
- private View.OnFocusChangeListener mHeaderFocusChangeListener =
- new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- // When the header is focused, focus the controls above it instead.
- if (hasFocus) {
- mControls.requestFocus();
- }
- }
- };
-
- public CallDetailHistoryAdapter(Context context, LayoutInflater layoutInflater,
- CallTypeHelper callTypeHelper, PhoneCallDetails[] phoneCallDetails,
- boolean showVoicemail, boolean showCallAndSms, View controls) {
- mContext = context;
- mLayoutInflater = layoutInflater;
- mCallTypeHelper = callTypeHelper;
- mPhoneCallDetails = phoneCallDetails;
- mShowVoicemail = showVoicemail;
- mShowCallAndSms = showCallAndSms;
- mControls = controls;
- }
-
- @Override
- public boolean isEnabled(int position) {
- // None of history will be clickable.
- return false;
- }
-
- @Override
- public int getCount() {
- return mPhoneCallDetails.length + 1;
- }
-
- @Override
- public Object getItem(int position) {
- if (position == 0) {
- return null;
- }
- return mPhoneCallDetails[position - 1];
- }
-
- @Override
- public long getItemId(int position) {
- if (position == 0) {
- return -1;
- }
- return position - 1;
- }
-
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position == 0) {
- return VIEW_TYPE_HEADER;
- }
- return VIEW_TYPE_HISTORY_ITEM;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (position == 0) {
- final View header = convertView == null
- ? mLayoutInflater.inflate(R.layout.call_detail_history_header, parent, false)
- : convertView;
- // Voicemail controls are only shown in the main UI if there is a voicemail.
- View voicemailContainer = header.findViewById(R.id.header_voicemail_container);
- voicemailContainer.setVisibility(mShowVoicemail ? View.VISIBLE : View.GONE);
- // Call and SMS controls are only shown in the main UI if there is a known number.
- View callAndSmsContainer = header.findViewById(R.id.header_call_and_sms_container);
- callAndSmsContainer.setVisibility(mShowCallAndSms ? View.VISIBLE : View.GONE);
- header.setFocusable(true);
- header.setOnFocusChangeListener(mHeaderFocusChangeListener);
- return header;
- }
-
- // Make sure we have a valid convertView to start with
- final View result = convertView == null
- ? mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false)
- : convertView;
-
- PhoneCallDetails details = mPhoneCallDetails[position - 1];
- CallTypeIconsView callTypeIconView =
- (CallTypeIconsView) result.findViewById(R.id.call_type_icon);
- TextView callTypeTextView = (TextView) result.findViewById(R.id.call_type_text);
- TextView dateView = (TextView) result.findViewById(R.id.date);
- TextView durationView = (TextView) result.findViewById(R.id.duration);
-
- int callType = details.callTypes[0];
- callTypeIconView.clear();
- callTypeIconView.add(callType);
- callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType));
- // Set the date.
- CharSequence dateValue = DateUtils.formatDateRange(mContext, details.date, details.date,
- DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE |
- DateUtils.FORMAT_SHOW_WEEKDAY | DateUtils.FORMAT_SHOW_YEAR);
- dateView.setText(dateValue);
- // Set the duration
- if (callType == Calls.MISSED_TYPE || callType == Calls.VOICEMAIL_TYPE) {
- durationView.setVisibility(View.GONE);
- } else {
- durationView.setVisibility(View.VISIBLE);
- durationView.setText(formatDuration(details.duration));
- }
-
- return result;
- }
-
- private String formatDuration(long elapsedSeconds) {
- long minutes = 0;
- long seconds = 0;
-
- if (elapsedSeconds >= 60) {
- minutes = elapsedSeconds / 60;
- elapsedSeconds -= minutes * 60;
- }
- seconds = elapsedSeconds;
-
- return mContext.getString(R.string.callDetailsDurationFormat, minutes, seconds);
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
deleted file mode 100644
index 3688dca..0000000
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.PhoneLookup;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-
-import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.ContactPhotoManager;
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.PhoneCallDetailsHelper;
-import com.android.contacts.R;
-import com.android.contacts.util.ExpirableCache;
-import com.android.contacts.util.UriUtils;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-
-import java.util.LinkedList;
-
-/**
- * Adapter class to fill in data for the Call Log.
- */
-/*package*/ class CallLogAdapter extends GroupingListAdapter
- implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
- /** Interface used to initiate a refresh of the content. */
- public interface CallFetcher {
- public void fetchCalls();
- }
-
- /**
- * Stores a phone number of a call with the country code where it originally occurred.
- * <p>
- * Note the country does not necessarily specifies the country of the phone number itself, but
- * it is the country in which the user was in when the call was placed or received.
- */
- private static final class NumberWithCountryIso {
- public final String number;
- public final String countryIso;
-
- public NumberWithCountryIso(String number, String countryIso) {
- this.number = number;
- this.countryIso = countryIso;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) return false;
- if (!(o instanceof NumberWithCountryIso)) return false;
- NumberWithCountryIso other = (NumberWithCountryIso) o;
- return TextUtils.equals(number, other.number)
- && TextUtils.equals(countryIso, other.countryIso);
- }
-
- @Override
- public int hashCode() {
- return (number == null ? 0 : number.hashCode())
- ^ (countryIso == null ? 0 : countryIso.hashCode());
- }
- }
-
- /** The time in millis to delay starting the thread processing requests. */
- private static final int START_PROCESSING_REQUESTS_DELAY_MILLIS = 1000;
-
- /** The size of the cache of contact info. */
- private static final int CONTACT_INFO_CACHE_SIZE = 100;
-
- private final Context mContext;
- private final ContactInfoHelper mContactInfoHelper;
- private final CallFetcher mCallFetcher;
- private ViewTreeObserver mViewTreeObserver = null;
-
- /**
- * A cache of the contact details for the phone numbers in the call log.
- * <p>
- * The content of the cache is expired (but not purged) whenever the application comes to
- * the foreground.
- * <p>
- * The key is number with the country in which the call was placed or received.
- */
- private ExpirableCache<NumberWithCountryIso, ContactInfo> mContactInfoCache;
-
- /**
- * A request for contact details for the given number.
- */
- private static final class ContactInfoRequest {
- /** The number to look-up. */
- public final String number;
- /** The country in which a call to or from this number was placed or received. */
- public final String countryIso;
- /** The cached contact information stored in the call log. */
- public final ContactInfo callLogInfo;
-
- public ContactInfoRequest(String number, String countryIso, ContactInfo callLogInfo) {
- this.number = number;
- this.countryIso = countryIso;
- this.callLogInfo = callLogInfo;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof ContactInfoRequest)) return false;
-
- ContactInfoRequest other = (ContactInfoRequest) obj;
-
- if (!TextUtils.equals(number, other.number)) return false;
- if (!TextUtils.equals(countryIso, other.countryIso)) return false;
- if (!Objects.equal(callLogInfo, other.callLogInfo)) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((callLogInfo == null) ? 0 : callLogInfo.hashCode());
- result = prime * result + ((countryIso == null) ? 0 : countryIso.hashCode());
- result = prime * result + ((number == null) ? 0 : number.hashCode());
- return result;
- }
- }
-
- /**
- * List of requests to update contact details.
- * <p>
- * Each request is made of a phone number to look up, and the contact info currently stored in
- * the call log for this number.
- * <p>
- * The requests are added when displaying the contacts and are processed by a background
- * thread.
- */
- private final LinkedList<ContactInfoRequest> mRequests;
-
- private boolean mLoading = true;
- private static final int REDRAW = 1;
- private static final int START_THREAD = 2;
-
- private QueryThread mCallerIdThread;
-
- /** Instance of helper class for managing views. */
- private final CallLogListItemHelper mCallLogViewsHelper;
-
- /** Helper to set up contact photos. */
- private final ContactPhotoManager mContactPhotoManager;
- /** Helper to parse and process phone numbers. */
- private PhoneNumberHelper mPhoneNumberHelper;
- /** Helper to group call log entries. */
- private final CallLogGroupBuilder mCallLogGroupBuilder;
-
- /** Can be set to true by tests to disable processing of requests. */
- private volatile boolean mRequestProcessingDisabled = false;
-
- /** Listener for the primary action in the list, opens the call details. */
- private final View.OnClickListener mPrimaryActionListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- IntentProvider intentProvider = (IntentProvider) view.getTag();
- if (intentProvider != null) {
- mContext.startActivity(intentProvider.getIntent(mContext));
- }
- }
- };
- /** Listener for the secondary action in the list, either call or play. */
- private final View.OnClickListener mSecondaryActionListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- IntentProvider intentProvider = (IntentProvider) view.getTag();
- if (intentProvider != null) {
- mContext.startActivity(intentProvider.getIntent(mContext));
- }
- }
- };
-
- @Override
- public boolean onPreDraw() {
- // We only wanted to listen for the first draw (and this is it).
- unregisterPreDrawListener();
-
- // Only schedule a thread-creation message if the thread hasn't been
- // created yet. This is purely an optimization, to queue fewer messages.
- if (mCallerIdThread == null) {
- mHandler.sendEmptyMessageDelayed(START_THREAD, START_PROCESSING_REQUESTS_DELAY_MILLIS);
- }
-
- return true;
- }
-
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case REDRAW:
- notifyDataSetChanged();
- break;
- case START_THREAD:
- startRequestProcessing();
- break;
- }
- }
- };
-
- CallLogAdapter(Context context, CallFetcher callFetcher,
- ContactInfoHelper contactInfoHelper) {
- super(context);
-
- mContext = context;
- mCallFetcher = callFetcher;
- mContactInfoHelper = contactInfoHelper;
-
- mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
- mRequests = new LinkedList<ContactInfoRequest>();
-
- Resources resources = mContext.getResources();
- CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
-
- mContactPhotoManager = ContactPhotoManager.getInstance(mContext);
- mPhoneNumberHelper = new PhoneNumberHelper(resources);
- PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
- resources, callTypeHelper, mPhoneNumberHelper);
- mCallLogViewsHelper =
- new CallLogListItemHelper(
- phoneCallDetailsHelper, mPhoneNumberHelper, resources);
- mCallLogGroupBuilder = new CallLogGroupBuilder(this);
- }
-
- /**
- * Requery on background thread when {@link Cursor} changes.
- */
- @Override
- protected void onContentChanged() {
- mCallFetcher.fetchCalls();
- }
-
- void setLoading(boolean loading) {
- mLoading = loading;
- }
-
- @Override
- public boolean isEmpty() {
- if (mLoading) {
- // We don't want the empty state to show when loading.
- return false;
- } else {
- return super.isEmpty();
- }
- }
-
- /**
- * Starts a background thread to process contact-lookup requests, unless one
- * has already been started.
- */
- private synchronized void startRequestProcessing() {
- // For unit-testing.
- if (mRequestProcessingDisabled) return;
-
- // Idempotence... if a thread is already started, don't start another.
- if (mCallerIdThread != null) return;
-
- mCallerIdThread = new QueryThread();
- mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
- mCallerIdThread.start();
- }
-
- /**
- * Stops the background thread that processes updates and cancels any
- * pending requests to start it.
- */
- public synchronized void stopRequestProcessing() {
- // Remove any pending requests to start the processing thread.
- mHandler.removeMessages(START_THREAD);
- if (mCallerIdThread != null) {
- // Stop the thread; we are finished with it.
- mCallerIdThread.stopProcessing();
- mCallerIdThread.interrupt();
- mCallerIdThread = null;
- }
- }
-
- /**
- * Stop receiving onPreDraw() notifications.
- */
- private void unregisterPreDrawListener() {
- if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) {
- mViewTreeObserver.removeOnPreDrawListener(this);
- }
- mViewTreeObserver = null;
- }
-
- public void invalidateCache() {
- mContactInfoCache.expireAll();
-
- // Restart the request-processing thread after the next draw.
- stopRequestProcessing();
- unregisterPreDrawListener();
- }
-
- /**
- * Enqueues a request to look up the contact details for the given phone number.
- * <p>
- * It also provides the current contact info stored in the call log for this number.
- * <p>
- * If the {@code immediate} parameter is true, it will start immediately the thread that looks
- * up the contact information (if it has not been already started). Otherwise, it will be
- * started with a delay. See {@link #START_PROCESSING_REQUESTS_DELAY_MILLIS}.
- */
- @VisibleForTesting
- void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
- boolean immediate) {
- ContactInfoRequest request = new ContactInfoRequest(number, countryIso, callLogInfo);
- synchronized (mRequests) {
- if (!mRequests.contains(request)) {
- mRequests.add(request);
- mRequests.notifyAll();
- }
- }
- if (immediate) startRequestProcessing();
- }
-
- /**
- * Queries the appropriate content provider for the contact associated with the number.
- * <p>
- * Upon completion it also updates the cache in the call log, if it is different from
- * {@code callLogInfo}.
- * <p>
- * The number might be either a SIP address or a phone number.
- * <p>
- * It returns true if it updated the content of the cache and we should therefore tell the
- * view to update its content.
- */
- private boolean queryContactInfo(String number, String countryIso, ContactInfo callLogInfo) {
- final ContactInfo info = mContactInfoHelper.lookupNumber(number, countryIso);
-
- if (info == null) {
- // The lookup failed, just return without requesting to update the view.
- return false;
- }
-
- // Check the existing entry in the cache: only if it has changed we should update the
- // view.
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(numberCountryIso);
- boolean updated = (existingInfo != ContactInfo.EMPTY) && !info.equals(existingInfo);
-
- // Store the data in the cache so that the UI thread can use to display it. Store it
- // even if it has not changed so that it is marked as not expired.
- mContactInfoCache.put(numberCountryIso, info);
- // Update the call log even if the cache it is up-to-date: it is possible that the cache
- // contains the value from a different call log entry.
- updateCallLogContactInfoCache(number, countryIso, info, callLogInfo);
- return updated;
- }
-
- /*
- * Handles requests for contact name and number type.
- */
- private class QueryThread extends Thread {
- private volatile boolean mDone = false;
-
- public QueryThread() {
- super("CallLogAdapter.QueryThread");
- }
-
- public void stopProcessing() {
- mDone = true;
- }
-
- @Override
- public void run() {
- boolean needRedraw = false;
- while (true) {
- // Check if thread is finished, and if so return immediately.
- if (mDone) return;
-
- // Obtain next request, if any is available.
- // Keep synchronized section small.
- ContactInfoRequest req = null;
- synchronized (mRequests) {
- if (!mRequests.isEmpty()) {
- req = mRequests.removeFirst();
- }
- }
-
- if (req != null) {
- // Process the request. If the lookup succeeds, schedule a
- // redraw.
- needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
- } else {
- // Throttle redraw rate by only sending them when there are
- // more requests.
- if (needRedraw) {
- needRedraw = false;
- mHandler.sendEmptyMessage(REDRAW);
- }
-
- // Wait until another request is available, or until this
- // thread is no longer needed (as indicated by being
- // interrupted).
- try {
- synchronized (mRequests) {
- mRequests.wait(1000);
- }
- } catch (InterruptedException ie) {
- // Ignore, and attempt to continue processing requests.
- }
- }
- }
- }
- }
-
- @Override
- protected void addGroups(Cursor cursor) {
- mCallLogGroupBuilder.addGroups(cursor);
- }
-
- @Override
- protected View newStandAloneView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @Override
- protected void bindStandAloneView(View view, Context context, Cursor cursor) {
- bindView(view, cursor, 1);
- }
-
- @Override
- protected View newChildView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @Override
- protected void bindChildView(View view, Context context, Cursor cursor) {
- bindView(view, cursor, 1);
- }
-
- @Override
- protected View newGroupView(Context context, ViewGroup parent) {
- LayoutInflater inflater =
- (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
- findAndCacheViews(view);
- return view;
- }
-
- @Override
- protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
- boolean expanded) {
- bindView(view, cursor, groupSize);
- }
-
- private void findAndCacheViews(View view) {
- // Get the views to bind to.
- CallLogListItemViews views = CallLogListItemViews.fromView(view);
- views.primaryActionView.setOnClickListener(mPrimaryActionListener);
- views.secondaryActionView.setOnClickListener(mSecondaryActionListener);
- view.setTag(views);
- }
-
- /**
- * Binds the views in the entry to the data in the call log.
- *
- * @param view the view corresponding to this entry
- * @param c the cursor pointing to the entry in the call log
- * @param count the number of entries in the current item, greater than 1 if it is a group
- */
- private void bindView(View view, Cursor c, int count) {
- final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- final int section = c.getInt(CallLogQuery.SECTION);
-
- // This might be a header: check the value of the section column in the cursor.
- if (section == CallLogQuery.SECTION_NEW_HEADER
- || section == CallLogQuery.SECTION_OLD_HEADER) {
- views.primaryActionView.setVisibility(View.GONE);
- views.bottomDivider.setVisibility(View.GONE);
- views.listHeaderTextView.setVisibility(View.VISIBLE);
- views.listHeaderTextView.setText(
- section == CallLogQuery.SECTION_NEW_HEADER
- ? R.string.call_log_new_header
- : R.string.call_log_old_header);
- // Nothing else to set up for a header.
- return;
- }
- // Default case: an item in the call log.
- views.primaryActionView.setVisibility(View.VISIBLE);
- views.bottomDivider.setVisibility(isLastOfSection(c) ? View.GONE : View.VISIBLE);
- views.listHeaderTextView.setVisibility(View.GONE);
-
- final String number = c.getString(CallLogQuery.NUMBER);
- final long date = c.getLong(CallLogQuery.DATE);
- final long duration = c.getLong(CallLogQuery.DURATION);
- final int callType = c.getInt(CallLogQuery.CALL_TYPE);
- final String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
-
- final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
-
- views.primaryActionView.setTag(
- IntentProvider.getCallDetailIntentProvider(
- this, c.getPosition(), c.getLong(CallLogQuery.ID), count));
- // Store away the voicemail information so we can play it directly.
- if (callType == Calls.VOICEMAIL_TYPE) {
- String voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
- final long rowId = c.getLong(CallLogQuery.ID);
- views.secondaryActionView.setTag(
- IntentProvider.getPlayVoicemailIntentProvider(rowId, voicemailUri));
- } else if (!TextUtils.isEmpty(number)) {
- // Store away the number so we can call it directly if you click on the call icon.
- views.secondaryActionView.setTag(
- IntentProvider.getReturnCallIntentProvider(number));
- } else {
- // No action enabled.
- views.secondaryActionView.setTag(null);
- }
-
- // Lookup contacts with this number
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ExpirableCache.CachedValue<ContactInfo> cachedInfo =
- mContactInfoCache.getCachedValue(numberCountryIso);
- ContactInfo info = cachedInfo == null ? null : cachedInfo.getValue();
- if (!mPhoneNumberHelper.canPlaceCallsTo(number)
- || mPhoneNumberHelper.isVoicemailNumber(number)) {
- // If this is a number that cannot be dialed, there is no point in looking up a contact
- // for it.
- info = ContactInfo.EMPTY;
- } else if (cachedInfo == null) {
- mContactInfoCache.put(numberCountryIso, ContactInfo.EMPTY);
- // Use the cached contact info from the call log.
- info = cachedContactInfo;
- // The db request should happen on a non-UI thread.
- // Request the contact details immediately since they are currently missing.
- enqueueRequest(number, countryIso, cachedContactInfo, true);
- // We will format the phone number when we make the background request.
- } else {
- if (cachedInfo.isExpired()) {
- // The contact info is no longer up to date, we should request it. However, we
- // do not need to request them immediately.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
- } else if (!callLogInfoMatches(cachedContactInfo, info)) {
- // The call log information does not match the one we have, look it up again.
- // We could simply update the call log directly, but that needs to be done in a
- // background thread, so it is easier to simply request a new lookup, which will, as
- // a side-effect, update the call log.
- enqueueRequest(number, countryIso, cachedContactInfo, false);
- }
-
- if (info == ContactInfo.EMPTY) {
- // Use the cached contact info from the call log.
- info = cachedContactInfo;
- }
- }
-
- final Uri lookupUri = info.lookupUri;
- final String name = info.name;
- final int ntype = info.type;
- final String label = info.label;
- final long photoId = info.photoId;
- CharSequence formattedNumber = info.formattedNumber;
- final int[] callTypes = getCallTypes(c, count);
- final String geocode = c.getString(CallLogQuery.GEOCODED_LOCATION);
- final PhoneCallDetails details;
- if (TextUtils.isEmpty(name)) {
- details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
- callTypes, date, duration);
- } else {
- // We do not pass a photo id since we do not need the high-res picture.
- details = new PhoneCallDetails(number, formattedNumber, countryIso, geocode,
- callTypes, date, duration, name, ntype, label, lookupUri, null);
- }
-
- final boolean isNew = c.getInt(CallLogQuery.IS_READ) == 0;
- // New items also use the highlighted version of the text.
- final boolean isHighlighted = isNew;
- mCallLogViewsHelper.setPhoneCallDetails(views, details, isHighlighted);
- setPhoto(views, photoId, lookupUri);
-
- // Listen for the first draw
- if (mViewTreeObserver == null) {
- mViewTreeObserver = view.getViewTreeObserver();
- mViewTreeObserver.addOnPreDrawListener(this);
- }
- }
-
- /** Returns true if this is the last item of a section. */
- private boolean isLastOfSection(Cursor c) {
- if (c.isLast()) return true;
- final int section = c.getInt(CallLogQuery.SECTION);
- if (!c.moveToNext()) return true;
- final int nextSection = c.getInt(CallLogQuery.SECTION);
- c.moveToPrevious();
- return section != nextSection;
- }
-
- /** Checks whether the contact info from the call log matches the one from the contacts db. */
- private boolean callLogInfoMatches(ContactInfo callLogInfo, ContactInfo info) {
- // The call log only contains a subset of the fields in the contacts db.
- // Only check those.
- return TextUtils.equals(callLogInfo.name, info.name)
- && callLogInfo.type == info.type
- && TextUtils.equals(callLogInfo.label, info.label);
- }
-
- /** Stores the updated contact info in the call log if it is different from the current one. */
- private void updateCallLogContactInfoCache(String number, String countryIso,
- ContactInfo updatedInfo, ContactInfo callLogInfo) {
- final ContentValues values = new ContentValues();
- boolean needsUpdate = false;
-
- if (callLogInfo != null) {
- if (!TextUtils.equals(updatedInfo.name, callLogInfo.name)) {
- values.put(Calls.CACHED_NAME, updatedInfo.name);
- needsUpdate = true;
- }
-
- if (updatedInfo.type != callLogInfo.type) {
- values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
- needsUpdate = true;
- }
-
- if (!TextUtils.equals(updatedInfo.label, callLogInfo.label)) {
- values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
- needsUpdate = true;
- }
- if (!UriUtils.areEqual(updatedInfo.lookupUri, callLogInfo.lookupUri)) {
- values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
- needsUpdate = true;
- }
- if (!TextUtils.equals(updatedInfo.normalizedNumber, callLogInfo.normalizedNumber)) {
- values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
- needsUpdate = true;
- }
- if (!TextUtils.equals(updatedInfo.number, callLogInfo.number)) {
- values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
- needsUpdate = true;
- }
- if (updatedInfo.photoId != callLogInfo.photoId) {
- values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
- needsUpdate = true;
- }
- if (!TextUtils.equals(updatedInfo.formattedNumber, callLogInfo.formattedNumber)) {
- values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
- needsUpdate = true;
- }
- } else {
- // No previous values, store all of them.
- values.put(Calls.CACHED_NAME, updatedInfo.name);
- values.put(Calls.CACHED_NUMBER_TYPE, updatedInfo.type);
- values.put(Calls.CACHED_NUMBER_LABEL, updatedInfo.label);
- values.put(Calls.CACHED_LOOKUP_URI, UriUtils.uriToString(updatedInfo.lookupUri));
- values.put(Calls.CACHED_MATCHED_NUMBER, updatedInfo.number);
- values.put(Calls.CACHED_NORMALIZED_NUMBER, updatedInfo.normalizedNumber);
- values.put(Calls.CACHED_PHOTO_ID, updatedInfo.photoId);
- values.put(Calls.CACHED_FORMATTED_NUMBER, updatedInfo.formattedNumber);
- needsUpdate = true;
- }
-
- if (!needsUpdate) return;
-
- if (countryIso == null) {
- mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
- Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL",
- new String[]{ number });
- } else {
- mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,
- Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?",
- new String[]{ number, countryIso });
- }
- }
-
- /** Returns the contact information as stored in the call log. */
- private ContactInfo getContactInfoFromCallLog(Cursor c) {
- ContactInfo info = new ContactInfo();
- info.lookupUri = UriUtils.parseUriOrNull(c.getString(CallLogQuery.CACHED_LOOKUP_URI));
- info.name = c.getString(CallLogQuery.CACHED_NAME);
- info.type = c.getInt(CallLogQuery.CACHED_NUMBER_TYPE);
- info.label = c.getString(CallLogQuery.CACHED_NUMBER_LABEL);
- String matchedNumber = c.getString(CallLogQuery.CACHED_MATCHED_NUMBER);
- info.number = matchedNumber == null ? c.getString(CallLogQuery.NUMBER) : matchedNumber;
- info.normalizedNumber = c.getString(CallLogQuery.CACHED_NORMALIZED_NUMBER);
- info.photoId = c.getLong(CallLogQuery.CACHED_PHOTO_ID);
- info.photoUri = null; // We do not cache the photo URI.
- info.formattedNumber = c.getString(CallLogQuery.CACHED_FORMATTED_NUMBER);
- return info;
- }
-
- /**
- * Returns the call types for the given number of items in the cursor.
- * <p>
- * It uses the next {@code count} rows in the cursor to extract the types.
- * <p>
- * It position in the cursor is unchanged by this function.
- */
- private int[] getCallTypes(Cursor cursor, int count) {
- int position = cursor.getPosition();
- int[] callTypes = new int[count];
- for (int index = 0; index < count; ++index) {
- callTypes[index] = cursor.getInt(CallLogQuery.CALL_TYPE);
- cursor.moveToNext();
- }
- cursor.moveToPosition(position);
- return callTypes;
- }
-
- private void setPhoto(CallLogListItemViews views, long photoId, Uri contactUri) {
- views.quickContactView.assignContactUri(contactUri);
- mContactPhotoManager.loadThumbnail(views.quickContactView, photoId, true);
- }
-
- /**
- * Sets whether processing of requests for contact details should be enabled.
- * <p>
- * This method should be called in tests to disable such processing of requests when not
- * needed.
- */
- @VisibleForTesting
- void disableRequestProcessingForTest() {
- mRequestProcessingDisabled = true;
- }
-
- @VisibleForTesting
- void injectContactInfoForTest(String number, String countryIso, ContactInfo contactInfo) {
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- mContactInfoCache.put(numberCountryIso, contactInfo);
- }
-
- @Override
- public void addGroup(int cursorPosition, int size, boolean expanded) {
- super.addGroup(cursorPosition, size, expanded);
- }
-
- /*
- * Get the number from the Contacts, if available, since sometimes
- * the number provided by caller id may not be formatted properly
- * depending on the carrier (roaming) in use at the time of the
- * incoming call.
- * Logic : If the caller-id number starts with a "+", use it
- * Else if the number in the contacts starts with a "+", use that one
- * Else if the number in the contacts is longer, use that one
- */
- public String getBetterNumberFromContacts(String number, String countryIso) {
- String matchingNumber = null;
- // Look in the cache first. If it's not found then query the Phones db
- NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
- ContactInfo ci = mContactInfoCache.getPossiblyExpired(numberCountryIso);
- if (ci != null && ci != ContactInfo.EMPTY) {
- matchingNumber = ci.number;
- } else {
- try {
- Cursor phonesCursor = mContext.getContentResolver().query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
- PhoneQuery._PROJECTION, null, null, null);
- if (phonesCursor != null) {
- if (phonesCursor.moveToFirst()) {
- matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
- }
- phonesCursor.close();
- }
- } catch (Exception e) {
- // Use the number from the call log
- }
- }
- if (!TextUtils.isEmpty(matchingNumber) &&
- (matchingNumber.startsWith("+")
- || matchingNumber.length() > number.length())) {
- number = matchingNumber;
- }
- return number;
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
deleted file mode 100644
index f9d21c0..0000000
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ /dev/null
@@ -1,549 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.app.ListFragment;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.preference.PreferenceManager;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.R;
-import com.android.contacts.util.Constants;
-import com.android.contacts.util.EmptyLoader;
-import com.android.contacts.voicemail.VoicemailStatusHelper;
-import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
-import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.ITelephony;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.List;
-
-/**
- * Displays a list of call log entries.
- */
-public class CallLogFragment extends ListFragment
- implements CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
- private static final String TAG = "CallLogFragment";
-
- /**
- * ID of the empty loader to defer other fragments.
- */
- private static final int EMPTY_LOADER_ID = 0;
-
- private static final String PREF_CALL_LOG_FILTER_LAST_CALL_TYPE = "CallLogFragment_last_filter";
-
- private CallLogAdapter mAdapter;
- private CallLogQueryHandler mCallLogQueryHandler;
- private boolean mScrollToTop;
-
- /** Whether there is at least one voicemail source installed. */
- private boolean mVoicemailSourcesAvailable = false;
- /** Whether we are currently filtering over voicemail. */
- private boolean mShowingVoicemailOnly = false;
-
- private VoicemailStatusHelper mVoicemailStatusHelper;
- private View mStatusMessageView;
- private TextView mStatusMessageText;
- private TextView mStatusMessageAction;
- private TextView mFilterStatusView;
- private KeyguardManager mKeyguardManager;
-
- private boolean mEmptyLoaderRunning;
- private boolean mCallLogFetched;
- private boolean mVoicemailStatusFetched;
-
- private final Handler mHandler = new Handler();
-
- private class CustomContentObserver extends ContentObserver {
- public CustomContentObserver() {
- super(mHandler);
- }
- @Override
- public void onChange(boolean selfChange) {
- mRefreshDataRequired = true;
- }
- }
-
- // See issue 6363009
- private final ContentObserver mCallLogObserver = new CustomContentObserver();
- private final ContentObserver mContactsObserver = new CustomContentObserver();
- private boolean mRefreshDataRequired = true;
-
- // Exactly same variable is in Fragment as a package private.
- private boolean mMenuVisible = true;
-
- // Default to all calls.
- private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
-
- @Override
- public void onCreate(Bundle state) {
- super.onCreate(state);
-
- mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this);
- mKeyguardManager =
- (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
- getActivity().getContentResolver().registerContentObserver(
- CallLog.CONTENT_URI, true, mCallLogObserver);
- getActivity().getContentResolver().registerContentObserver(
- ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
- setHasOptionsMenu(true);
-
- // Load the last filter used.
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
- mCallTypeFilter = prefs.getInt(PREF_CALL_LOG_FILTER_LAST_CALL_TYPE,
- CallLogQueryHandler.CALL_TYPE_ALL);
- }
-
- /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
- @Override
- public void onCallsFetched(Cursor cursor) {
- if (getActivity() == null || getActivity().isFinishing()) {
- return;
- }
- mAdapter.setLoading(false);
- mAdapter.changeCursor(cursor);
- // This will update the state of the "Clear call log" menu item.
- getActivity().invalidateOptionsMenu();
- if (mScrollToTop) {
- final ListView listView = getListView();
- // The smooth-scroll animation happens over a fixed time period.
- // As a result, if it scrolls through a large portion of the list,
- // each frame will jump so far from the previous one that the user
- // will not experience the illusion of downward motion. Instead,
- // if we're not already near the top of the list, we instantly jump
- // near the top, and animate from there.
- if (listView.getFirstVisiblePosition() > 5) {
- listView.setSelection(5);
- }
- // Workaround for framework issue: the smooth-scroll doesn't
- // occur if setSelection() is called immediately before.
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (getActivity() == null || getActivity().isFinishing()) {
- return;
- }
- listView.smoothScrollToPosition(0);
- }
- });
-
- mScrollToTop = false;
- }
- mCallLogFetched = true;
- destroyEmptyLoaderIfAllDataFetched();
- }
-
- /**
- * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
- */
- @Override
- public void onVoicemailStatusFetched(Cursor statusCursor) {
- if (getActivity() == null || getActivity().isFinishing()) {
- return;
- }
- updateVoicemailStatusMessage(statusCursor);
-
- int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
- setVoicemailSourcesAvailable(activeSources != 0);
- MoreCloseables.closeQuietly(statusCursor);
- mVoicemailStatusFetched = true;
- destroyEmptyLoaderIfAllDataFetched();
- }
-
- private void destroyEmptyLoaderIfAllDataFetched() {
- if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
- mEmptyLoaderRunning = false;
- getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
- }
- }
-
- /** Sets whether there are any voicemail sources available in the platform. */
- private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
- if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
- mVoicemailSourcesAvailable = voicemailSourcesAvailable;
-
- Activity activity = getActivity();
- if (activity != null) {
- // This is so that the options menu content is updated.
- activity.invalidateOptionsMenu();
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
- View view = inflater.inflate(R.layout.call_log_fragment, container, false);
- mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
- mStatusMessageView = view.findViewById(R.id.voicemail_status);
- mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message);
- mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action);
- mFilterStatusView = (TextView) view.findViewById(R.id.filter_status);
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
- mAdapter = new CallLogAdapter(getActivity(), this,
- new ContactInfoHelper(getActivity(), currentCountryIso));
- setListAdapter(mAdapter);
- getListView().setItemsCanFocus(true);
-
- updateFilterHeader();
- }
-
- /**
- * Based on the new intent, decide whether the list should be configured
- * to scroll up to display the first item.
- */
- public void configureScreenFromIntent(Intent newIntent) {
- // Typically, when switching to the call-log we want to show the user
- // the same section of the list that they were most recently looking
- // at. However, under some circumstances, we want to automatically
- // scroll to the top of the list to present the newest call items.
- // For example, immediately after a call is finished, we want to
- // display information about that call.
- mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
- }
-
- @Override
- public void onStart() {
- // Start the empty loader now to defer other fragments. We destroy it when both calllog
- // and the voicemail status are fetched.
- getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
- new EmptyLoader.Callback(getActivity()));
- mEmptyLoaderRunning = true;
- super.onStart();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- refreshData();
- }
-
- private void updateVoicemailStatusMessage(Cursor statusCursor) {
- List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
- if (messages.size() == 0) {
- mStatusMessageView.setVisibility(View.GONE);
- } else {
- mStatusMessageView.setVisibility(View.VISIBLE);
- // TODO: Change the code to show all messages. For now just pick the first message.
- final StatusMessage message = messages.get(0);
- if (message.showInCallLog()) {
- mStatusMessageText.setText(message.callLogMessageId);
- }
- if (message.actionMessageId != -1) {
- mStatusMessageAction.setText(message.actionMessageId);
- }
- if (message.actionUri != null) {
- mStatusMessageAction.setVisibility(View.VISIBLE);
- mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getActivity().startActivity(
- new Intent(Intent.ACTION_VIEW, message.actionUri));
- }
- });
- } else {
- mStatusMessageAction.setVisibility(View.GONE);
- }
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // Kill the requests thread
- mAdapter.stopRequestProcessing();
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
- prefs.edit()
- .putInt(PREF_CALL_LOG_FILTER_LAST_CALL_TYPE, mCallTypeFilter)
- .apply();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- updateOnExit();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mAdapter.stopRequestProcessing();
- mAdapter.changeCursor(null);
- getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
- getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
- }
-
- @Override
- public void fetchCalls() {
- mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
- }
-
- public void startCallsQuery() {
- mAdapter.setLoading(true);
- mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
- if (mShowingVoicemailOnly) {
- mShowingVoicemailOnly = false;
- getActivity().invalidateOptionsMenu();
- }
- }
-
- private void startVoicemailStatusQuery() {
- mCallLogQueryHandler.fetchVoicemailStatus();
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.call_log_options, menu);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all);
- // Check if all the menu items are inflated correctly. As a shortcut, we assume all
- // menu items are ready if the first item is non-null.
- if (itemDeleteAll != null) {
- itemDeleteAll.setEnabled(mAdapter != null && !mAdapter.isEmpty());
- menu.findItem(R.id.show_voicemails_only).setVisible(mVoicemailSourcesAvailable);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.delete_all:
- ClearCallLogDialog.show(getFragmentManager());
- return true;
-
- case R.id.show_outgoing_only:
- mCallLogQueryHandler.fetchCalls(Calls.OUTGOING_TYPE);
- mCallTypeFilter = Calls.OUTGOING_TYPE;
- updateFilterHeader();
- return true;
-
- case R.id.show_incoming_only:
- mCallLogQueryHandler.fetchCalls(Calls.INCOMING_TYPE);
- mCallTypeFilter = Calls.INCOMING_TYPE;
- updateFilterHeader();
- return true;
-
- case R.id.show_missed_only:
- mCallLogQueryHandler.fetchCalls(Calls.MISSED_TYPE);
- mCallTypeFilter = Calls.MISSED_TYPE;
- updateFilterHeader();
- return true;
-
- case R.id.show_voicemails_only:
- mCallLogQueryHandler.fetchCalls(Calls.VOICEMAIL_TYPE);
- mCallTypeFilter = Calls.VOICEMAIL_TYPE;
- updateFilterHeader();
- mShowingVoicemailOnly = true;
- return true;
-
- case R.id.show_all_calls:
- mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
- mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
- updateFilterHeader();
- mShowingVoicemailOnly = false;
- return true;
-
- default:
- return false;
- }
- }
-
- private void updateFilterHeader() {
- switch (mCallTypeFilter) {
- case CallLogQueryHandler.CALL_TYPE_ALL:
- mFilterStatusView.setVisibility(View.GONE);
- break;
- case Calls.INCOMING_TYPE:
- showFilterStatus(R.string.call_log_incoming_header);
- break;
- case Calls.OUTGOING_TYPE:
- showFilterStatus(R.string.call_log_outgoing_header);
- break;
- case Calls.MISSED_TYPE:
- showFilterStatus(R.string.call_log_missed_header);
- break;
- case Calls.VOICEMAIL_TYPE:
- showFilterStatus(R.string.call_log_voicemail_header);
- break;
- }
- }
-
- private void showFilterStatus(int resId) {
- mFilterStatusView.setText(resId);
- mFilterStatusView.setVisibility(View.VISIBLE);
- }
-
- public void callSelectedEntry() {
- int position = getListView().getSelectedItemPosition();
- if (position < 0) {
- // In touch mode you may often not have something selected, so
- // just call the first entry to make sure that [send] [send] calls the
- // most recent entry.
- position = 0;
- }
- final Cursor cursor = (Cursor)mAdapter.getItem(position);
- if (cursor != null) {
- String number = cursor.getString(CallLogQuery.NUMBER);
- if (TextUtils.isEmpty(number)
- || number.equals(CallerInfo.UNKNOWN_NUMBER)
- || number.equals(CallerInfo.PRIVATE_NUMBER)
- || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
- // This number can't be called, do nothing
- return;
- }
- Intent intent;
- // If "number" is really a SIP address, construct a sip: URI.
- if (PhoneNumberUtils.isUriNumber(number)) {
- intent = ContactsUtils.getCallIntent(
- Uri.fromParts(Constants.SCHEME_SIP, number, null));
- } else {
- // We're calling a regular PSTN phone number.
- // Construct a tel: URI, but do some other possible cleanup first.
- int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
- if (!number.startsWith("+") &&
- (callType == Calls.INCOMING_TYPE
- || callType == Calls.MISSED_TYPE)) {
- // If the caller-id matches a contact with a better qualified number, use it
- String countryIso = cursor.getString(CallLogQuery.COUNTRY_ISO);
- number = mAdapter.getBetterNumberFromContacts(number, countryIso);
- }
- intent = ContactsUtils.getCallIntent(
- Uri.fromParts(Constants.SCHEME_TEL, number, null));
- }
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- startActivity(intent);
- }
- }
-
- @VisibleForTesting
- CallLogAdapter getAdapter() {
- return mAdapter;
- }
-
- @Override
- public void setMenuVisibility(boolean menuVisible) {
- super.setMenuVisibility(menuVisible);
- if (mMenuVisible != menuVisible) {
- mMenuVisible = menuVisible;
- if (!menuVisible) {
- updateOnExit();
- } else if (isResumed()) {
- refreshData();
- }
- }
- }
-
- /** Requests updates to the data to be shown. */
- private void refreshData() {
- // Prevent unnecessary refresh.
- if (mRefreshDataRequired) {
- // Mark all entries in the contact info cache as out of date, so they will be looked up
- // again once being shown.
- mAdapter.invalidateCache();
- startCallsQuery();
- startVoicemailStatusQuery();
- updateOnEntry();
- mRefreshDataRequired = false;
- }
- }
-
- /** Removes the missed call notifications. */
- private void removeMissedCallNotifications() {
- try {
- ITelephony telephony =
- ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
- if (telephony != null) {
- telephony.cancelMissedCallsNotification();
- } else {
- Log.w(TAG, "Telephony service is null, can't call " +
- "cancelMissedCallsNotification");
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
- }
- }
-
- /** Updates call data and notification state while leaving the call log tab. */
- private void updateOnExit() {
- updateOnTransition(false);
- }
-
- /** Updates call data and notification state while entering the call log tab. */
- private void updateOnEntry() {
- updateOnTransition(true);
- }
-
- private void updateOnTransition(boolean onEntry) {
- // We don't want to update any call data when keyguard is on because the user has likely not
- // seen the new calls yet.
- // This might be called before onCreate() and thus we need to check null explicitly.
- if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
- // On either of the transitions we reset the new flag and update the notifications.
- // While exiting we additionally consume all missed calls (by marking them as read).
- // This will ensure that they no more appear in the "new" section when we return back.
- mCallLogQueryHandler.markNewCallsAsOld();
- if (!onEntry) {
- mCallLogQueryHandler.markMissedCallsAsRead();
- }
- removeMissedCallNotifications();
- updateVoicemailNotifications();
- }
- }
-
- private void updateVoicemailNotifications() {
- Intent serviceIntent = new Intent(getActivity(), CallLogNotificationsService.class);
- serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
- getActivity().startService(serviceIntent);
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogGroupBuilder.java b/src/com/android/contacts/calllog/CallLogGroupBuilder.java
deleted file mode 100644
index 5c7d3ee..0000000
--- a/src/com/android/contacts/calllog/CallLogGroupBuilder.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.database.Cursor;
-import android.provider.CallLog.Calls;
-import android.telephony.PhoneNumberUtils;
-
-import com.android.common.widget.GroupingListAdapter;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Groups together calls in the call log.
- * <p>
- * This class is meant to be used in conjunction with {@link GroupingListAdapter}.
- */
-public class CallLogGroupBuilder {
- public interface GroupCreator {
- public void addGroup(int cursorPosition, int size, boolean expanded);
- }
-
- /** The object on which the groups are created. */
- private final GroupCreator mGroupCreator;
-
- public CallLogGroupBuilder(GroupCreator groupCreator) {
- mGroupCreator = groupCreator;
- }
-
- /**
- * Finds all groups of adjacent entries in the call log which should be grouped together and
- * calls {@link GroupCreator#addGroup(int, int, boolean)} on {@link #mGroupCreator} for each of
- * them.
- * <p>
- * For entries that are not grouped with others, we do not need to create a group of size one.
- * <p>
- * It assumes that the cursor will not change during its execution.
- *
- * @see GroupingListAdapter#addGroups(Cursor)
- */
- public void addGroups(Cursor cursor) {
- final int count = cursor.getCount();
- if (count == 0) {
- return;
- }
-
- int currentGroupSize = 1;
- cursor.moveToFirst();
- // The number of the first entry in the group.
- String firstNumber = cursor.getString(CallLogQuery.NUMBER);
- // This is the type of the first call in the group.
- int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
- while (cursor.moveToNext()) {
- // The number of the current row in the cursor.
- final String currentNumber = cursor.getString(CallLogQuery.NUMBER);
- final int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
- final boolean sameNumber = equalNumbers(firstNumber, currentNumber);
- final boolean shouldGroup;
-
- if (CallLogQuery.isSectionHeader(cursor)) {
- // Cannot group headers.
- shouldGroup = false;
- } else if (!sameNumber) {
- // Should only group with calls from the same number.
- shouldGroup = false;
- } else if (firstCallType == Calls.VOICEMAIL_TYPE) {
- // never group voicemail.
- shouldGroup = false;
- } else {
- // Incoming, outgoing, and missed calls group together.
- shouldGroup = (callType == Calls.INCOMING_TYPE || callType == Calls.OUTGOING_TYPE ||
- callType == Calls.MISSED_TYPE);
- }
-
- if (shouldGroup) {
- // Increment the size of the group to include the current call, but do not create
- // the group until we find a call that does not match.
- currentGroupSize++;
- } else {
- // Create a group for the previous set of calls, excluding the current one, but do
- // not create a group for a single call.
- if (currentGroupSize > 1) {
- addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);
- }
- // Start a new group; it will include at least the current call.
- currentGroupSize = 1;
- // The current entry is now the first in the group.
- firstNumber = currentNumber;
- firstCallType = callType;
- }
- }
- // If the last set of calls at the end of the call log was itself a group, create it now.
- if (currentGroupSize > 1) {
- addGroup(count - currentGroupSize, currentGroupSize);
- }
- }
-
- /**
- * Creates a group of items in the cursor.
- * <p>
- * The group is always unexpanded.
- *
- * @see CallLogAdapter#addGroup(int, int, boolean)
- */
- private void addGroup(int cursorPosition, int size) {
- mGroupCreator.addGroup(cursorPosition, size, false);
- }
-
- @VisibleForTesting
- boolean equalNumbers(String number1, String number2) {
- if (PhoneNumberUtils.isUriNumber(number1) || PhoneNumberUtils.isUriNumber(number2)) {
- return compareSipAddresses(number1, number2);
- } else {
- return PhoneNumberUtils.compare(number1, number2);
- }
- }
-
- @VisibleForTesting
- boolean compareSipAddresses(String number1, String number2) {
- if (number1 == null || number2 == null) return number1 == number2;
-
- int index1 = number1.indexOf('@');
- final String userinfo1;
- final String rest1;
- if (index1 != -1) {
- userinfo1 = number1.substring(0, index1);
- rest1 = number1.substring(index1);
- } else {
- userinfo1 = number1;
- rest1 = "";
- }
-
- int index2 = number2.indexOf('@');
- final String userinfo2;
- final String rest2;
- if (index2 != -1) {
- userinfo2 = number2.substring(0, index2);
- rest2 = number2.substring(index2);
- } else {
- userinfo2 = number2;
- rest2 = "";
- }
-
- return userinfo1.equals(userinfo2) && rest1.equalsIgnoreCase(rest2);
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
deleted file mode 100644
index eb30c81..0000000
--- a/src/com/android/contacts/calllog/CallLogListItemHelper.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.res.Resources;
-import android.provider.CallLog.Calls;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.PhoneCallDetailsHelper;
-import com.android.contacts.R;
-
-/**
- * Helper class to fill in the views of a call log entry.
- */
-/*package*/ class CallLogListItemHelper {
- /** Helper for populating the details of a phone call. */
- private final PhoneCallDetailsHelper mPhoneCallDetailsHelper;
- /** Helper for handling phone numbers. */
- private final PhoneNumberHelper mPhoneNumberHelper;
- /** Resources to look up strings. */
- private final Resources mResources;
-
- /**
- * Creates a new helper instance.
- *
- * @param phoneCallDetailsHelper used to set the details of a phone call
- * @param phoneNumberHelper used to process phone number
- */
- public CallLogListItemHelper(PhoneCallDetailsHelper phoneCallDetailsHelper,
- PhoneNumberHelper phoneNumberHelper, Resources resources) {
- mPhoneCallDetailsHelper = phoneCallDetailsHelper;
- mPhoneNumberHelper = phoneNumberHelper;
- mResources = resources;
- }
-
- /**
- * Sets the name, label, and number for a contact.
- *
- * @param views the views to populate
- * @param details the details of a phone call needed to fill in the data
- * @param isHighlighted whether to use the highlight text for the call
- */
- public void setPhoneCallDetails(CallLogListItemViews views, PhoneCallDetails details,
- boolean isHighlighted) {
- mPhoneCallDetailsHelper.setPhoneCallDetails(views.phoneCallDetailsViews, details,
- isHighlighted);
- boolean canCall = mPhoneNumberHelper.canPlaceCallsTo(details.number);
- boolean canPlay = details.callTypes[0] == Calls.VOICEMAIL_TYPE;
-
- if (canPlay) {
- // Playback action takes preference.
- configurePlaySecondaryAction(views, isHighlighted);
- views.dividerView.setVisibility(View.VISIBLE);
- } else if (canCall) {
- // Call is the secondary action.
- configureCallSecondaryAction(views, details);
- views.dividerView.setVisibility(View.VISIBLE);
- } else {
- // No action available.
- views.secondaryActionView.setVisibility(View.GONE);
- views.dividerView.setVisibility(View.GONE);
- }
- }
-
- /** Sets the secondary action to correspond to the call button. */
- private void configureCallSecondaryAction(CallLogListItemViews views,
- PhoneCallDetails details) {
- views.secondaryActionView.setVisibility(View.VISIBLE);
- views.secondaryActionView.setImageResource(R.drawable.ic_ab_dialer_holo_dark);
- views.secondaryActionView.setContentDescription(getCallActionDescription(details));
- }
-
- /** Returns the description used by the call action for this phone call. */
- private CharSequence getCallActionDescription(PhoneCallDetails details) {
- final CharSequence recipient;
- if (!TextUtils.isEmpty(details.name)) {
- recipient = details.name;
- } else {
- recipient = mPhoneNumberHelper.getDisplayNumber(
- details.number, details.formattedNumber);
- }
- return mResources.getString(R.string.description_call, recipient);
- }
-
- /** Sets the secondary action to correspond to the play button. */
- private void configurePlaySecondaryAction(CallLogListItemViews views, boolean isHighlighted) {
- views.secondaryActionView.setVisibility(View.VISIBLE);
- views.secondaryActionView.setImageResource(
- isHighlighted ? R.drawable.ic_play_active_holo_dark : R.drawable.ic_play_holo_dark);
- views.secondaryActionView.setContentDescription(
- mResources.getString(R.string.description_call_log_play_button));
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogListItemView.java b/src/com/android/contacts/calllog/CallLogListItemView.java
deleted file mode 100644
index 584a7f2..0000000
--- a/src/com/android/contacts/calllog/CallLogListItemView.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-/**
- * An entry in the call log.
- */
-public class CallLogListItemView extends LinearLayout {
- public CallLogListItemView(Context context) {
- super(context);
- }
-
- public CallLogListItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CallLogListItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void requestLayout() {
- // We will assume that once measured this will not need to resize
- // itself, so there is no need to pass the layout request to the parent
- // view (ListView).
- forceLayout();
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
deleted file mode 100644
index 348cd2f..0000000
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.QuickContactBadge;
-import android.widget.TextView;
-
-import com.android.contacts.PhoneCallDetailsViews;
-import com.android.contacts.R;
-import com.android.contacts.test.NeededForTesting;
-
-/**
- * Simple value object containing the various views within a call log entry.
- */
-public final class CallLogListItemViews {
- /** The quick contact badge for the contact. */
- public final QuickContactBadge quickContactView;
- /** The primary action view of the entry. */
- public final View primaryActionView;
- /** The secondary action button on the entry. */
- public final ImageView secondaryActionView;
- /** The divider between the primary and secondary actions. */
- public final View dividerView;
- /** The details of the phone call. */
- public final PhoneCallDetailsViews phoneCallDetailsViews;
- /** The text of the header of a section. */
- public final TextView listHeaderTextView;
- /** The divider to be shown below items. */
- public final View bottomDivider;
-
- private CallLogListItemViews(QuickContactBadge quickContactView, View primaryActionView,
- ImageView secondaryActionView, View dividerView,
- PhoneCallDetailsViews phoneCallDetailsViews,
- TextView listHeaderTextView, View bottomDivider) {
- this.quickContactView = quickContactView;
- this.primaryActionView = primaryActionView;
- this.secondaryActionView = secondaryActionView;
- this.dividerView = dividerView;
- this.phoneCallDetailsViews = phoneCallDetailsViews;
- this.listHeaderTextView = listHeaderTextView;
- this.bottomDivider = bottomDivider;
- }
-
- public static CallLogListItemViews fromView(View view) {
- return new CallLogListItemViews(
- (QuickContactBadge) view.findViewById(R.id.quick_contact_photo),
- view.findViewById(R.id.primary_action_view),
- (ImageView) view.findViewById(R.id.secondary_action_icon),
- view.findViewById(R.id.divider),
- PhoneCallDetailsViews.fromView(view),
- (TextView) view.findViewById(R.id.call_log_header),
- view.findViewById(R.id.call_log_divider));
- }
-
- @NeededForTesting
- public static CallLogListItemViews createForTest(Context context) {
- return new CallLogListItemViews(
- new QuickContactBadge(context),
- new View(context),
- new ImageView(context),
- new View(context),
- PhoneCallDetailsViews.createForTest(context),
- new TextView(context),
- new View(context));
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogNotificationsService.java b/src/com/android/contacts/calllog/CallLogNotificationsService.java
deleted file mode 100644
index be540ad..0000000
--- a/src/com/android/contacts/calllog/CallLogNotificationsService.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.net.Uri;
-import android.util.Log;
-
-/**
- * Provides operations for managing notifications.
- * <p>
- * It handles the following actions:
- * <ul>
- * <li>{@link #ACTION_MARK_NEW_VOICEMAILS_AS_OLD}: marks all the new voicemails in the call log as
- * old; this is called when a notification is dismissed.</li>
- * <li>{@link #ACTION_UPDATE_NOTIFICATIONS}: updates the content of the new items notification; it
- * may include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}, containing the URI of the new
- * voicemail that has triggered this update (if any).</li>
- * </ul>
- */
-public class CallLogNotificationsService extends IntentService {
- private static final String TAG = "CallLogNotificationsService";
-
- /** Action to mark all the new voicemails as old. */
- public static final String ACTION_MARK_NEW_VOICEMAILS_AS_OLD =
- "com.android.contacts.calllog.ACTION_MARK_NEW_VOICEMAILS_AS_OLD";
-
- /**
- * Action to update the notifications.
- * <p>
- * May include an optional extra {@link #EXTRA_NEW_VOICEMAIL_URI}.
- */
- public static final String ACTION_UPDATE_NOTIFICATIONS =
- "com.android.contacts.calllog.UPDATE_NOTIFICATIONS";
-
- /**
- * Extra to included with {@link #ACTION_UPDATE_NOTIFICATIONS} to identify the new voicemail
- * that triggered an update.
- * <p>
- * It must be a {@link Uri}.
- */
- public static final String EXTRA_NEW_VOICEMAIL_URI = "NEW_VOICEMAIL_URI";
-
- private CallLogQueryHandler mCallLogQueryHandler;
-
- public CallLogNotificationsService() {
- super("CallLogNotificationsService");
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- mCallLogQueryHandler = new CallLogQueryHandler(getContentResolver(), null /*listener*/);
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- if (ACTION_MARK_NEW_VOICEMAILS_AS_OLD.equals(intent.getAction())) {
- mCallLogQueryHandler.markNewVoicemailsAsOld();
- } else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
- Uri voicemailUri = (Uri) intent.getParcelableExtra(EXTRA_NEW_VOICEMAIL_URI);
- DefaultVoicemailNotifier.getInstance(this).updateNotification(voicemailUri);
- } else {
- Log.d(TAG, "onHandleIntent: could not handle: " + intent);
- }
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogQuery.java b/src/com/android/contacts/calllog/CallLogQuery.java
deleted file mode 100644
index 90017b7..0000000
--- a/src/com/android/contacts/calllog/CallLogQuery.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.database.Cursor;
-import android.provider.CallLog.Calls;
-
-/**
- * The query for the call log table.
- */
-public final class CallLogQuery {
- // If you alter this, you must also alter the method that inserts a fake row to the headers
- // in the CallLogQueryHandler class called createHeaderCursorFor().
- public static final String[] _PROJECTION = new String[] {
- Calls._ID, // 0
- Calls.NUMBER, // 1
- Calls.DATE, // 2
- Calls.DURATION, // 3
- Calls.TYPE, // 4
- Calls.COUNTRY_ISO, // 5
- Calls.VOICEMAIL_URI, // 6
- Calls.GEOCODED_LOCATION, // 7
- Calls.CACHED_NAME, // 8
- Calls.CACHED_NUMBER_TYPE, // 9
- Calls.CACHED_NUMBER_LABEL, // 10
- Calls.CACHED_LOOKUP_URI, // 11
- Calls.CACHED_MATCHED_NUMBER, // 12
- Calls.CACHED_NORMALIZED_NUMBER, // 13
- Calls.CACHED_PHOTO_ID, // 14
- Calls.CACHED_FORMATTED_NUMBER, // 15
- Calls.IS_READ, // 16
- };
-
- public static final int ID = 0;
- public static final int NUMBER = 1;
- public static final int DATE = 2;
- public static final int DURATION = 3;
- public static final int CALL_TYPE = 4;
- public static final int COUNTRY_ISO = 5;
- public static final int VOICEMAIL_URI = 6;
- public static final int GEOCODED_LOCATION = 7;
- public static final int CACHED_NAME = 8;
- public static final int CACHED_NUMBER_TYPE = 9;
- public static final int CACHED_NUMBER_LABEL = 10;
- public static final int CACHED_LOOKUP_URI = 11;
- public static final int CACHED_MATCHED_NUMBER = 12;
- public static final int CACHED_NORMALIZED_NUMBER = 13;
- public static final int CACHED_PHOTO_ID = 14;
- public static final int CACHED_FORMATTED_NUMBER = 15;
- public static final int IS_READ = 16;
- /** The index of the synthetic "section" column in the extended projection. */
- public static final int SECTION = 17;
-
- /**
- * The name of the synthetic "section" column.
- * <p>
- * This column identifies whether a row is a header or an actual item, and whether it is
- * part of the new or old calls.
- */
- public static final String SECTION_NAME = "section";
- /** The value of the "section" column for the header of the new section. */
- public static final int SECTION_NEW_HEADER = 0;
- /** The value of the "section" column for the items of the new section. */
- public static final int SECTION_NEW_ITEM = 1;
- /** The value of the "section" column for the header of the old section. */
- public static final int SECTION_OLD_HEADER = 2;
- /** The value of the "section" column for the items of the old section. */
- public static final int SECTION_OLD_ITEM = 3;
-
- /** The call log projection including the section name. */
- public static final String[] EXTENDED_PROJECTION;
- static {
- EXTENDED_PROJECTION = new String[_PROJECTION.length + 1];
- System.arraycopy(_PROJECTION, 0, EXTENDED_PROJECTION, 0, _PROJECTION.length);
- EXTENDED_PROJECTION[_PROJECTION.length] = SECTION_NAME;
- }
-
- public static boolean isSectionHeader(Cursor cursor) {
- int section = cursor.getInt(CallLogQuery.SECTION);
- return section == CallLogQuery.SECTION_NEW_HEADER
- || section == CallLogQuery.SECTION_OLD_HEADER;
- }
-
- public static boolean isNewSection(Cursor cursor) {
- int section = cursor.getInt(CallLogQuery.SECTION);
- return section == CallLogQuery.SECTION_NEW_ITEM
- || section == CallLogQuery.SECTION_NEW_HEADER;
- }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
deleted file mode 100644
index a6382b6..0000000
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.database.sqlite.SQLiteDatabaseCorruptException;
-import android.database.sqlite.SQLiteDiskIOException;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteFullException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.VoicemailContract.Status;
-import android.util.Log;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
-import com.google.common.collect.Lists;
-
-import java.lang.ref.WeakReference;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/** Handles asynchronous queries to the call log. */
-/*package*/ class CallLogQueryHandler extends AsyncQueryHandler {
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
-
- private static final String TAG = "CallLogQueryHandler";
- private static final int NUM_LOGS_TO_DISPLAY = 1000;
-
- /** The token for the query to fetch the new entries from the call log. */
- private static final int QUERY_NEW_CALLS_TOKEN = 53;
- /** The token for the query to fetch the old entries from the call log. */
- private static final int QUERY_OLD_CALLS_TOKEN = 54;
- /** The token for the query to mark all missed calls as old after seeing the call log. */
- private static final int UPDATE_MARK_AS_OLD_TOKEN = 55;
- /** The token for the query to mark all new voicemails as old. */
- private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 56;
- /** The token for the query to mark all missed calls as read after seeing the call log. */
- private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 57;
- /** The token for the query to fetch voicemail status messages. */
- private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 58;
-
- /**
- * Call type similar to Calls.INCOMING_TYPE used to specify all types instead of one particular
- * type.
- */
- public static final int CALL_TYPE_ALL = -1;
-
- /**
- * The time window from the current time within which an unread entry will be added to the new
- * section.
- */
- private static final long NEW_SECTION_TIME_WINDOW = TimeUnit.DAYS.toMillis(7);
-
- private final WeakReference<Listener> mListener;
-
- /** The cursor containing the new calls, or null if they have not yet been fetched. */
- @GuardedBy("this") private Cursor mNewCallsCursor;
- /** The cursor containing the old calls, or null if they have not yet been fetched. */
- @GuardedBy("this") private Cursor mOldCallsCursor;
- /**
- * The identifier of the latest calls request.
- * <p>
- * A request for the list of calls requires two queries and hence the two cursor
- * {@link #mNewCallsCursor} and {@link #mOldCallsCursor} above, corresponding to
- * {@link #QUERY_NEW_CALLS_TOKEN} and {@link #QUERY_OLD_CALLS_TOKEN}.
- * <p>
- * When a new request is about to be started, existing cursors are closed. However, it is
- * possible that one of the queries completes after the new request has started. This means that
- * we might merge two cursors that do not correspond to the same request. Moreover, this may
- * lead to a resource leak if the same query completes and we override the cursor without
- * closing it first.
- * <p>
- * To make sure we only join two cursors from the same request, we use this variable to store
- * the request id of the latest request and make sure we only process cursors corresponding to
- * the this request.
- */
- @GuardedBy("this") private int mCallsRequestId;
-
- /**
- * Simple handler that wraps background calls to catch
- * {@link SQLiteException}, such as when the disk is full.
- */
- protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler {
- public CatchingWorkerHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- try {
- // Perform same query while catching any exceptions
- super.handleMessage(msg);
- } catch (SQLiteDiskIOException e) {
- Log.w(TAG, "Exception on background worker thread", e);
- } catch (SQLiteFullException e) {
- Log.w(TAG, "Exception on background worker thread", e);
- } catch (SQLiteDatabaseCorruptException e) {
- Log.w(TAG, "Exception on background worker thread", e);
- }
- }
- }
-
- @Override
- protected Handler createHandler(Looper looper) {
- // Provide our special handler that catches exceptions
- return new CatchingWorkerHandler(looper);
- }
-
- public CallLogQueryHandler(ContentResolver contentResolver, Listener listener) {
- super(contentResolver);
- mListener = new WeakReference<Listener>(listener);
- }
-
- /** Creates a cursor that contains a single row and maps the section to the given value. */
- private Cursor createHeaderCursorFor(int section) {
- MatrixCursor matrixCursor =
- new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
- // The values in this row correspond to default values for _PROJECTION from CallLogQuery
- // plus the section value.
- matrixCursor.addRow(new Object[]{
- 0L, "", 0L, 0L, 0, "", "", "", null, 0, null, null, null, null, 0L, null, 0,
- section
- });
- return matrixCursor;
- }
-
- /** Returns a cursor for the old calls header. */
- private Cursor createOldCallsHeaderCursor() {
- return createHeaderCursorFor(CallLogQuery.SECTION_OLD_HEADER);
- }
-
- /** Returns a cursor for the new calls header. */
- private Cursor createNewCallsHeaderCursor() {
- return createHeaderCursorFor(CallLogQuery.SECTION_NEW_HEADER);
- }
-
- /**
- * Fetches the list of calls from the call log for a given type.
- * <p>
- * It will asynchronously update the content of the list view when the fetch completes.
- */
- public void fetchCalls(int callType) {
- cancelFetch();
- int requestId = newCallsRequest();
- fetchCalls(QUERY_NEW_CALLS_TOKEN, requestId, true /*isNew*/, callType);
- fetchCalls(QUERY_OLD_CALLS_TOKEN, requestId, false /*isNew*/, callType);
- }
-
- public void fetchVoicemailStatus() {
- startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI,
- VoicemailStatusHelperImpl.PROJECTION, null, null, null);
- }
-
- /** Fetches the list of calls in the call log, either the new one or the old ones. */
- private void fetchCalls(int token, int requestId, boolean isNew, int callType) {
- // We need to check for NULL explicitly otherwise entries with where READ is NULL
- // may not match either the query or its negation.
- // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".
- String selection = String.format("%s IS NOT NULL AND %s = 0 AND %s > ?",
- Calls.IS_READ, Calls.IS_READ, Calls.DATE);
- List<String> selectionArgs = Lists.newArrayList(
- Long.toString(System.currentTimeMillis() - NEW_SECTION_TIME_WINDOW));
- if (!isNew) {
- // Negate the query.
- selection = String.format("NOT (%s)", selection);
- }
- if (callType > CALL_TYPE_ALL) {
- // Add a clause to fetch only items of type voicemail.
- selection = String.format("(%s) AND (%s = ?)", selection, Calls.TYPE);
- selectionArgs.add(Integer.toString(callType));
- }
- Uri uri = Calls.CONTENT_URI_WITH_VOICEMAIL.buildUpon()
- .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(NUM_LOGS_TO_DISPLAY))
- .build();
- startQuery(token, requestId, uri,
- CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),
- Calls.DEFAULT_SORT_ORDER);
- }
-
- /** Cancel any pending fetch request. */
- private void cancelFetch() {
- cancelOperation(QUERY_NEW_CALLS_TOKEN);
- cancelOperation(QUERY_OLD_CALLS_TOKEN);
- }
-
- /** Updates all new calls to mark them as old. */
- public void markNewCallsAsOld() {
- // Mark all "new" calls as not new anymore.
- StringBuilder where = new StringBuilder();
- where.append(Calls.NEW);
- where.append(" = 1");
-
- ContentValues values = new ContentValues(1);
- values.put(Calls.NEW, "0");
-
- startUpdate(UPDATE_MARK_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
- values, where.toString(), null);
- }
-
- /** Updates all new voicemails to mark them as old. */
- public void markNewVoicemailsAsOld() {
- // Mark all "new" voicemails as not new anymore.
- StringBuilder where = new StringBuilder();
- where.append(Calls.NEW);
- where.append(" = 1 AND ");
- where.append(Calls.TYPE);
- where.append(" = ?");
-
- ContentValues values = new ContentValues(1);
- values.put(Calls.NEW, "0");
-
- startUpdate(UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
- values, where.toString(), new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) });
- }
-
- /** Updates all missed calls to mark them as read. */
- public void markMissedCallsAsRead() {
- // Mark all "new" calls as not new anymore.
- StringBuilder where = new StringBuilder();
- where.append(Calls.IS_READ).append(" = 0");
- where.append(" AND ");
- where.append(Calls.TYPE).append(" = ").append(Calls.MISSED_TYPE);
-
- ContentValues values = new ContentValues(1);
- values.put(Calls.IS_READ, "1");
-
- startUpdate(UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN, null, Calls.CONTENT_URI, values,
- where.toString(), null);
- }
-
- /**
- * Start a new request and return its id. The request id will be used as the cookie for the
- * background request.
- * <p>
- * Closes any open cursor that has not yet been sent to the requester.
- */
- private synchronized int newCallsRequest() {
- MoreCloseables.closeQuietly(mNewCallsCursor);
- MoreCloseables.closeQuietly(mOldCallsCursor);
- mNewCallsCursor = null;
- mOldCallsCursor = null;
- return ++mCallsRequestId;
- }
-
- @Override
- protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
- if (token == QUERY_NEW_CALLS_TOKEN) {
- int requestId = ((Integer) cookie).intValue();
- if (requestId != mCallsRequestId) {
- // Ignore this query since it does not correspond to the latest request.
- return;
- }
-
- // Store the returned cursor.
- MoreCloseables.closeQuietly(mNewCallsCursor);
- mNewCallsCursor = new ExtendedCursor(
- cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_NEW_ITEM);
- } else if (token == QUERY_OLD_CALLS_TOKEN) {
- int requestId = ((Integer) cookie).intValue();
- if (requestId != mCallsRequestId) {
- // Ignore this query since it does not correspond to the latest request.
- return;
- }
-
- // Store the returned cursor.
- MoreCloseables.closeQuietly(mOldCallsCursor);
- mOldCallsCursor = new ExtendedCursor(
- cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_OLD_ITEM);
- } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {
- updateVoicemailStatus(cursor);
- return;
- } else {
- Log.w(TAG, "Unknown query completed: ignoring: " + token);
- return;
- }
-
- if (mNewCallsCursor != null && mOldCallsCursor != null) {
- updateAdapterData(createMergedCursor());
- }
- }
-
- /** Creates the merged cursor representing the data to show in the call log. */
- @GuardedBy("this")
- private Cursor createMergedCursor() {
- try {
- final boolean hasNewCalls = mNewCallsCursor.getCount() != 0;
- final boolean hasOldCalls = mOldCallsCursor.getCount() != 0;
-
- if (!hasNewCalls) {
- // Return only the old calls, without the header.
- MoreCloseables.closeQuietly(mNewCallsCursor);
- return mOldCallsCursor;
- }
-
- if (!hasOldCalls) {
- // Return only the new calls.
- MoreCloseables.closeQuietly(mOldCallsCursor);
- return new MergeCursor(
- new Cursor[]{ createNewCallsHeaderCursor(), mNewCallsCursor });
- }
-
- return new MergeCursor(new Cursor[]{
- createNewCallsHeaderCursor(), mNewCallsCursor,
- createOldCallsHeaderCursor(), mOldCallsCursor});
- } finally {
- // Any cursor still open is now owned, directly or indirectly, by the caller.
- mNewCallsCursor = null;
- mOldCallsCursor = null;
- }
- }
-
- /**
- * Updates the adapter in the call log fragment to show the new cursor data.
- */
- private void updateAdapterData(Cursor combinedCursor) {
- final Listener listener = mListener.get();
- if (listener != null) {
- listener.onCallsFetched(combinedCursor);
- }
- }
-
- private void updateVoicemailStatus(Cursor statusCursor) {
- final Listener listener = mListener.get();
- if (listener != null) {
- listener.onVoicemailStatusFetched(statusCursor);
- }
- }
-
- /** Listener to completion of various queries. */
- public interface Listener {
- /** Called when {@link CallLogQueryHandler#fetchVoicemailStatus()} completes. */
- void onVoicemailStatusFetched(Cursor statusCursor);
-
- /**
- * Called when {@link CallLogQueryHandler#fetchCalls(int)}complete.
- */
- void onCallsFetched(Cursor combinedCursor);
- }
-}
diff --git a/src/com/android/contacts/calllog/CallLogReceiver.java b/src/com/android/contacts/calllog/CallLogReceiver.java
deleted file mode 100644
index 14bfa64..0000000
--- a/src/com/android/contacts/calllog/CallLogReceiver.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.provider.VoicemailContract;
-import android.util.Log;
-
-/**
- * Receiver for call log events.
- * <p>
- * It is currently used to handle {@link VoicemailContract#ACTION_NEW_VOICEMAIL} and
- * {@link Intent#ACTION_BOOT_COMPLETED}.
- */
-public class CallLogReceiver extends BroadcastReceiver {
- private static final String TAG = "CallLogReceiver";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) {
- Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
- serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
- serviceIntent.putExtra(
- CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, intent.getData());
- context.startService(serviceIntent);
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
- serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
- context.startService(serviceIntent);
- } else {
- Log.w(TAG, "onReceive: could not handle: " + intent);
- }
- }
-}
diff --git a/src/com/android/contacts/calllog/CallTypeHelper.java b/src/com/android/contacts/calllog/CallTypeHelper.java
deleted file mode 100644
index 2ca0db0..0000000
--- a/src/com/android/contacts/calllog/CallTypeHelper.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.res.Resources;
-import android.provider.CallLog.Calls;
-
-import com.android.contacts.R;
-
-/**
- * Helper class to perform operations related to call types.
- */
-public class CallTypeHelper {
- /** Name used to identify incoming calls. */
- private final CharSequence mIncomingName;
- /** Name used to identify outgoing calls. */
- private final CharSequence mOutgoingName;
- /** Name used to identify missed calls. */
- private final CharSequence mMissedName;
- /** Name used to identify voicemail calls. */
- private final CharSequence mVoicemailName;
- /** Color used to identify new missed calls. */
- private final int mNewMissedColor;
- /** Color used to identify new voicemail calls. */
- private final int mNewVoicemailColor;
-
- public CallTypeHelper(Resources resources) {
- // Cache these values so that we do not need to look them up each time.
- mIncomingName = resources.getString(R.string.type_incoming);
- mOutgoingName = resources.getString(R.string.type_outgoing);
- mMissedName = resources.getString(R.string.type_missed);
- mVoicemailName = resources.getString(R.string.type_voicemail);
- mNewMissedColor = resources.getColor(R.color.call_log_missed_call_highlight_color);
- mNewVoicemailColor = resources.getColor(R.color.call_log_voicemail_highlight_color);
- }
-
- /** Returns the text used to represent the given call type. */
- public CharSequence getCallTypeText(int callType) {
- switch (callType) {
- case Calls.INCOMING_TYPE:
- return mIncomingName;
-
- case Calls.OUTGOING_TYPE:
- return mOutgoingName;
-
- case Calls.MISSED_TYPE:
- return mMissedName;
-
- case Calls.VOICEMAIL_TYPE:
- return mVoicemailName;
-
- default:
- throw new IllegalArgumentException("invalid call type: " + callType);
- }
- }
-
- /** Returns the color used to highlight the given call type, null if not highlight is needed. */
- public Integer getHighlightedColor(int callType) {
- switch (callType) {
- case Calls.INCOMING_TYPE:
- // New incoming calls are not highlighted.
- return null;
-
- case Calls.OUTGOING_TYPE:
- // New outgoing calls are not highlighted.
- return null;
-
- case Calls.MISSED_TYPE:
- return mNewMissedColor;
-
- case Calls.VOICEMAIL_TYPE:
- return mNewVoicemailColor;
-
- default:
- throw new IllegalArgumentException("invalid call type: " + callType);
- }
- }
-}
diff --git a/src/com/android/contacts/calllog/CallTypeIconsView.java b/src/com/android/contacts/calllog/CallTypeIconsView.java
deleted file mode 100644
index 384b597..0000000
--- a/src/com/android/contacts/calllog/CallTypeIconsView.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.provider.CallLog.Calls;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.contacts.R;
-import com.android.contacts.test.NeededForTesting;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * View that draws one or more symbols for different types of calls (missed calls, outgoing etc).
- * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited
- * for ListView-recycling that a regular LinearLayout using ImageViews.
- */
-public class CallTypeIconsView extends View {
- private List<Integer> mCallTypes = Lists.newArrayListWithCapacity(3);
- private Resources mResources;
- private int mWidth;
- private int mHeight;
-
- public CallTypeIconsView(Context context) {
- this(context, null);
- }
-
- public CallTypeIconsView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mResources = new Resources(context);
- }
-
- public void clear() {
- mCallTypes.clear();
- mWidth = 0;
- mHeight = 0;
- invalidate();
- }
-
- public void add(int callType) {
- mCallTypes.add(callType);
-
- final Drawable drawable = getCallTypeDrawable(callType);
- mWidth += drawable.getIntrinsicWidth() + mResources.iconMargin;
- mHeight = Math.max(mHeight, drawable.getIntrinsicHeight());
- invalidate();
- }
-
- @NeededForTesting
- public int getCount() {
- return mCallTypes.size();
- }
-
- @NeededForTesting
- public int getCallType(int index) {
- return mCallTypes.get(index);
- }
-
- private Drawable getCallTypeDrawable(int callType) {
- switch (callType) {
- case Calls.INCOMING_TYPE:
- return mResources.incoming;
- case Calls.OUTGOING_TYPE:
- return mResources.outgoing;
- case Calls.MISSED_TYPE:
- return mResources.missed;
- case Calls.VOICEMAIL_TYPE:
- return mResources.voicemail;
- default:
- throw new IllegalArgumentException("invalid call type: " + callType);
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(mWidth, mHeight);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- int left = 0;
- for (Integer callType : mCallTypes) {
- final Drawable drawable = getCallTypeDrawable(callType);
- final int right = left + drawable.getIntrinsicWidth();
- drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight());
- drawable.draw(canvas);
- left = right + mResources.iconMargin;
- }
- }
-
- private static class Resources {
- public final Drawable incoming;
- public final Drawable outgoing;
- public final Drawable missed;
- public final Drawable voicemail;
- public final int iconMargin;
-
- public Resources(Context context) {
- final android.content.res.Resources r = context.getResources();
- incoming = r.getDrawable(R.drawable.ic_call_incoming_holo_dark);
- outgoing = r.getDrawable(R.drawable.ic_call_outgoing_holo_dark);
- missed = r.getDrawable(R.drawable.ic_call_missed_holo_dark);
- voicemail = r.getDrawable(R.drawable.ic_call_voicemail_holo_dark);
- iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin);
- }
- }
-}
diff --git a/src/com/android/contacts/calllog/ClearCallLogDialog.java b/src/com/android/contacts/calllog/ClearCallLogDialog.java
deleted file mode 100644
index c4dbb7e..0000000
--- a/src/com/android/contacts/calllog/ClearCallLogDialog.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.FragmentManager;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.CallLog.Calls;
-
-import com.android.contacts.R;
-
-/**
- * Dialog that clears the call log after confirming with the user
- */
-public class ClearCallLogDialog extends DialogFragment {
- /** Preferred way to show this dialog */
- public static void show(FragmentManager fragmentManager) {
- ClearCallLogDialog dialog = new ClearCallLogDialog();
- dialog.show(fragmentManager, "deleteCallLog");
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final ContentResolver resolver = getActivity().getContentResolver();
- final OnClickListener okListener = new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final ProgressDialog progressDialog = ProgressDialog.show(getActivity(),
- getString(R.string.clearCallLogProgress_title),
- "", true, false);
- final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- resolver.delete(Calls.CONTENT_URI, null, null);
- return null;
- }
- @Override
- protected void onPostExecute(Void result) {
- progressDialog.dismiss();
- }
- };
- // TODO: Once we have the API, we should configure this ProgressDialog
- // to only show up after a certain time (e.g. 150ms)
- progressDialog.show();
- task.execute();
- }
- };
- return new AlertDialog.Builder(getActivity())
- .setTitle(R.string.clearCallLogConfirmation_title)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setMessage(R.string.clearCallLogConfirmation)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, okListener)
- .setCancelable(true)
- .create();
- }
-}
diff --git a/src/com/android/contacts/calllog/ContactInfo.java b/src/com/android/contacts/calllog/ContactInfo.java
deleted file mode 100644
index 30e7e71..0000000
--- a/src/com/android/contacts/calllog/ContactInfo.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.net.Uri;
-import android.text.TextUtils;
-
-import com.android.contacts.util.UriUtils;
-
-/**
- * Information for a contact as needed by the Call Log.
- */
-public final class ContactInfo {
- public Uri lookupUri;
- public String name;
- public int type;
- public String label;
- public String number;
- public String formattedNumber;
- public String normalizedNumber;
- /** The photo for the contact, if available. */
- public long photoId;
- /** The high-res photo for the contact, if available. */
- public Uri photoUri;
-
- public static ContactInfo EMPTY = new ContactInfo();
-
- @Override
- public int hashCode() {
- // Uses only name and contactUri to determine hashcode.
- // This should be sufficient to have a reasonable distribution of hash codes.
- // Moreover, there should be no two people with the same lookupUri.
- final int prime = 31;
- int result = 1;
- result = prime * result + ((lookupUri == null) ? 0 : lookupUri.hashCode());
- result = prime * result + ((name == null) ? 0 : name.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
- ContactInfo other = (ContactInfo) obj;
- if (!UriUtils.areEqual(lookupUri, other.lookupUri)) return false;
- if (!TextUtils.equals(name, other.name)) return false;
- if (type != other.type) return false;
- if (!TextUtils.equals(label, other.label)) return false;
- if (!TextUtils.equals(number, other.number)) return false;
- if (!TextUtils.equals(formattedNumber, other.formattedNumber)) return false;
- if (!TextUtils.equals(normalizedNumber, other.normalizedNumber)) return false;
- if (photoId != other.photoId) return false;
- if (!UriUtils.areEqual(photoUri, other.photoUri)) return false;
- return true;
- }
-}
diff --git a/src/com/android/contacts/calllog/ContactInfoHelper.java b/src/com/android/contacts/calllog/ContactInfoHelper.java
deleted file mode 100644
index edca00c..0000000
--- a/src/com/android/contacts/calllog/ContactInfoHelper.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-import com.android.contacts.util.UriUtils;
-
-/**
- * Utility class to look up the contact information for a given number.
- */
-public class ContactInfoHelper {
- private final Context mContext;
- private final String mCurrentCountryIso;
-
- public ContactInfoHelper(Context context, String currentCountryIso) {
- mContext = context;
- mCurrentCountryIso = currentCountryIso;
- }
-
- /**
- * Returns the contact information for the given number.
- * <p>
- * If the number does not match any contact, returns a contact info containing only the number
- * and the formatted number.
- * <p>
- * If an error occurs during the lookup, it returns null.
- *
- * @param number the number to look up
- * @param countryIso the country associated with this number
- */
- public ContactInfo lookupNumber(String number, String countryIso) {
- final ContactInfo info;
-
- // Determine the contact info.
- if (PhoneNumberUtils.isUriNumber(number)) {
- // This "number" is really a SIP address.
- ContactInfo sipInfo = queryContactInfoForSipAddress(number);
- if (sipInfo == null || sipInfo == ContactInfo.EMPTY) {
- // Check whether the "username" part of the SIP address is
- // actually the phone number of a contact.
- String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
- if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
- sipInfo = queryContactInfoForPhoneNumber(username, countryIso);
- }
- }
- info = sipInfo;
- } else {
- // Look for a contact that has the given phone number.
- ContactInfo phoneInfo = queryContactInfoForPhoneNumber(number, countryIso);
-
- if (phoneInfo == null || phoneInfo == ContactInfo.EMPTY) {
- // Check whether the phone number has been saved as an "Internet call" number.
- phoneInfo = queryContactInfoForSipAddress(number);
- }
- info = phoneInfo;
- }
-
- final ContactInfo updatedInfo;
- if (info == null) {
- // The lookup failed.
- updatedInfo = null;
- } else {
- // If we did not find a matching contact, generate an empty contact info for the number.
- if (info == ContactInfo.EMPTY) {
- // Did not find a matching contact.
- updatedInfo = new ContactInfo();
- updatedInfo.number = number;
- updatedInfo.formattedNumber = formatPhoneNumber(number, null, countryIso);
- } else {
- updatedInfo = info;
- }
- }
- return updatedInfo;
- }
-
- /**
- * Looks up a contact using the given URI.
- * <p>
- * It returns null if an error occurs, {@link ContactInfo#EMPTY} if no matching contact is
- * found, or the {@link ContactInfo} for the given contact.
- * <p>
- * The {@link ContactInfo#formattedNumber} field is always set to {@code null} in the returned
- * value.
- */
- private ContactInfo lookupContactFromUri(Uri uri) {
- final ContactInfo info;
- Cursor phonesCursor =
- mContext.getContentResolver().query(
- uri, PhoneQuery._PROJECTION, null, null, null);
-
- if (phonesCursor != null) {
- try {
- if (phonesCursor.moveToFirst()) {
- info = new ContactInfo();
- long contactId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
- String lookupKey = phonesCursor.getString(PhoneQuery.LOOKUP_KEY);
- info.lookupUri = Contacts.getLookupUri(contactId, lookupKey);
- info.name = phonesCursor.getString(PhoneQuery.NAME);
- info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
- info.label = phonesCursor.getString(PhoneQuery.LABEL);
- info.number = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
- info.normalizedNumber = phonesCursor.getString(PhoneQuery.NORMALIZED_NUMBER);
- info.photoId = phonesCursor.getLong(PhoneQuery.PHOTO_ID);
- info.photoUri =
- UriUtils.parseUriOrNull(phonesCursor.getString(PhoneQuery.PHOTO_URI));
- info.formattedNumber = null;
- } else {
- info = ContactInfo.EMPTY;
- }
- } finally {
- phonesCursor.close();
- }
- } else {
- // Failed to fetch the data, ignore this request.
- info = null;
- }
- return info;
- }
-
- /**
- * Determines the contact information for the given SIP address.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given SIP address, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForSipAddress(String sipAddress) {
- final ContactInfo info;
-
- // "contactNumber" is a SIP address, so use the PhoneLookup table with the SIP parameter.
- Uri.Builder uriBuilder = PhoneLookup.CONTENT_FILTER_URI.buildUpon();
- uriBuilder.appendPath(Uri.encode(sipAddress));
- uriBuilder.appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, "1");
- return lookupContactFromUri(uriBuilder.build());
- }
-
- /**
- * Determines the contact information for the given phone number.
- * <p>
- * It returns the contact info if found.
- * <p>
- * If no contact corresponds to the given phone number, returns {@link ContactInfo#EMPTY}.
- * <p>
- * If the lookup fails for some other reason, it returns null.
- */
- private ContactInfo queryContactInfoForPhoneNumber(String number, String countryIso) {
- String contactNumber = number;
- if (!TextUtils.isEmpty(countryIso)) {
- // Normalize the number: this is needed because the PhoneLookup query below does not
- // accept a country code as an input.
- String numberE164 = PhoneNumberUtils.formatNumberToE164(number, countryIso);
- if (!TextUtils.isEmpty(numberE164)) {
- // Only use it if the number could be formatted to E164.
- contactNumber = numberE164;
- }
- }
-
- // The "contactNumber" is a regular phone number, so use the PhoneLookup table.
- Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(contactNumber));
- ContactInfo info = lookupContactFromUri(uri);
- if (info != null && info != ContactInfo.EMPTY) {
- info.formattedNumber = formatPhoneNumber(number, null, countryIso);
- }
- return info;
- }
-
- /**
- * Format the given phone number
- *
- * @param number the number to be formatted.
- * @param normalizedNumber the normalized number of the given number.
- * @param countryIso the ISO 3166-1 two letters country code, the country's
- * convention will be used to format the number if the normalized
- * phone is null.
- *
- * @return the formatted number, or the given number if it was formatted.
- */
- private String formatPhoneNumber(String number, String normalizedNumber,
- String countryIso) {
- if (TextUtils.isEmpty(number)) {
- return "";
- }
- // If "number" is really a SIP address, don't try to do any formatting at all.
- if (PhoneNumberUtils.isUriNumber(number)) {
- return number;
- }
- if (TextUtils.isEmpty(countryIso)) {
- countryIso = mCurrentCountryIso;
- }
- return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
- }
-}
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
deleted file mode 100644
index cfac1de..0000000
--- a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.PhoneLookup;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.CallDetailActivity;
-import com.android.contacts.R;
-import com.google.common.collect.Maps;
-
-import java.util.Map;
-
-/**
- * Implementation of {@link VoicemailNotifier} that shows a notification in the
- * status bar.
- */
-public class DefaultVoicemailNotifier implements VoicemailNotifier {
- public static final String TAG = "DefaultVoicemailNotifier";
-
- /** The tag used to identify notifications from this class. */
- private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
- /** The identifier of the notification of new voicemails. */
- private static final int NOTIFICATION_ID = 1;
-
- /** The singleton instance of {@link DefaultVoicemailNotifier}. */
- private static DefaultVoicemailNotifier sInstance;
-
- private final Context mContext;
- private final NotificationManager mNotificationManager;
- private final NewCallsQuery mNewCallsQuery;
- private final NameLookupQuery mNameLookupQuery;
- private final PhoneNumberHelper mPhoneNumberHelper;
-
- /** Returns the singleton instance of the {@link DefaultVoicemailNotifier}. */
- public static synchronized DefaultVoicemailNotifier getInstance(Context context) {
- if (sInstance == null) {
- NotificationManager notificationManager =
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- ContentResolver contentResolver = context.getContentResolver();
- sInstance = new DefaultVoicemailNotifier(context, notificationManager,
- createNewCallsQuery(contentResolver),
- createNameLookupQuery(contentResolver),
- createPhoneNumberHelper(context));
- }
- return sInstance;
- }
-
- private DefaultVoicemailNotifier(Context context,
- NotificationManager notificationManager, NewCallsQuery newCallsQuery,
- NameLookupQuery nameLookupQuery, PhoneNumberHelper phoneNumberHelper) {
- mContext = context;
- mNotificationManager = notificationManager;
- mNewCallsQuery = newCallsQuery;
- mNameLookupQuery = nameLookupQuery;
- mPhoneNumberHelper = phoneNumberHelper;
- }
-
- /** Updates the notification and notifies of the call with the given URI. */
- @Override
- public void updateNotification(Uri newCallUri) {
- // Lookup the list of new voicemails to include in the notification.
- // TODO: Move this into a service, to avoid holding the receiver up.
- final NewCall[] newCalls = mNewCallsQuery.query();
-
- if (newCalls == null) {
- // Query failed, just return.
- return;
- }
-
- if (newCalls.length == 0) {
- // No voicemails to notify about: clear the notification.
- clearNotification();
- return;
- }
-
- Resources resources = mContext.getResources();
-
- // This represents a list of names to include in the notification.
- String callers = null;
-
- // Maps each number into a name: if a number is in the map, it has already left a more
- // recent voicemail.
- final Map<String, String> names = Maps.newHashMap();
-
- // Determine the call corresponding to the new voicemail we have to notify about.
- NewCall callToNotify = null;
-
- // Iterate over the new voicemails to determine all the information above.
- for (NewCall newCall : newCalls) {
- // Check if we already know the name associated with this number.
- String name = names.get(newCall.number);
- if (name == null) {
- // Look it up in the database.
- name = mNameLookupQuery.query(newCall.number);
- // If we cannot lookup the contact, use the number instead.
- if (name == null) {
- name = mPhoneNumberHelper.getDisplayNumber(newCall.number, "").toString();
- if (TextUtils.isEmpty(name)) {
- name = newCall.number;
- }
- }
- names.put(newCall.number, name);
- // This is a new caller. Add it to the back of the list of callers.
- if (TextUtils.isEmpty(callers)) {
- callers = name;
- } else {
- callers = resources.getString(
- R.string.notification_voicemail_callers_list, callers, name);
- }
- }
- // Check if this is the new call we need to notify about.
- if (newCallUri != null && newCallUri.equals(newCall.voicemailUri)) {
- callToNotify = newCall;
- }
- }
-
- if (newCallUri != null && callToNotify == null) {
- Log.e(TAG, "The new call could not be found in the call log: " + newCallUri);
- }
-
- // Determine the title of the notification and the icon for it.
- final String title = resources.getQuantityString(
- R.plurals.notification_voicemail_title, newCalls.length, newCalls.length);
- // TODO: Use the photo of contact if all calls are from the same person.
- final int icon = android.R.drawable.stat_notify_voicemail;
-
- Notification.Builder notificationBuilder = new Notification.Builder(mContext)
- .setSmallIcon(icon)
- .setContentTitle(title)
- .setContentText(callers)
- .setDefaults(callToNotify != null ? Notification.DEFAULT_ALL : 0)
- .setDeleteIntent(createMarkNewVoicemailsAsOldIntent())
- .setAutoCancel(true);
-
- // Determine the intent to fire when the notification is clicked on.
- final Intent contentIntent;
- if (newCalls.length == 1) {
- // Open the voicemail directly.
- contentIntent = new Intent(mContext, CallDetailActivity.class);
- contentIntent.setData(newCalls[0].callsUri);
- contentIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
- newCalls[0].voicemailUri);
- Intent playIntent = new Intent(mContext, CallDetailActivity.class);
- playIntent.setData(newCalls[0].callsUri);
- playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
- newCalls[0].voicemailUri);
- playIntent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, true);
- playIntent.putExtra(CallDetailActivity.EXTRA_FROM_NOTIFICATION, true);
- notificationBuilder.addAction(R.drawable.ic_play_holo_dark,
- resources.getString(R.string.notification_action_voicemail_play),
- PendingIntent.getActivity(mContext, 0, playIntent, 0));
- } else {
- // Open the call log.
- contentIntent = new Intent(Intent.ACTION_VIEW, Calls.CONTENT_URI);
- }
- notificationBuilder.setContentIntent(
- PendingIntent.getActivity(mContext, 0, contentIntent, 0));
-
- // The text to show in the ticker, describing the new event.
- if (callToNotify != null) {
- notificationBuilder.setTicker(resources.getString(
- R.string.notification_new_voicemail_ticker, names.get(callToNotify.number)));
- }
-
- mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notificationBuilder.build());
- }
-
- /** Creates a pending intent that marks all new voicemails as old. */
- private PendingIntent createMarkNewVoicemailsAsOldIntent() {
- Intent intent = new Intent(mContext, CallLogNotificationsService.class);
- intent.setAction(CallLogNotificationsService.ACTION_MARK_NEW_VOICEMAILS_AS_OLD);
- return PendingIntent.getService(mContext, 0, intent, 0);
- }
-
- @Override
- public void clearNotification() {
- mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
- }
-
- /** Information about a new voicemail. */
- private static final class NewCall {
- public final Uri callsUri;
- public final Uri voicemailUri;
- public final String number;
-
- public NewCall(Uri callsUri, Uri voicemailUri, String number) {
- this.callsUri = callsUri;
- this.voicemailUri = voicemailUri;
- this.number = number;
- }
- }
-
- /** Allows determining the new calls for which a notification should be generated. */
- public interface NewCallsQuery {
- /**
- * Returns the new calls for which a notification should be generated.
- */
- public NewCall[] query();
- }
-
- /** Create a new instance of {@link NewCallsQuery}. */
- public static NewCallsQuery createNewCallsQuery(ContentResolver contentResolver) {
- return new DefaultNewCallsQuery(contentResolver);
- }
-
- /**
- * Default implementation of {@link NewCallsQuery} that looks up the list of new calls to
- * notify about in the call log.
- */
- private static final class DefaultNewCallsQuery implements NewCallsQuery {
- private static final String[] PROJECTION = {
- Calls._ID, Calls.NUMBER, Calls.VOICEMAIL_URI
- };
- private static final int ID_COLUMN_INDEX = 0;
- private static final int NUMBER_COLUMN_INDEX = 1;
- private static final int VOICEMAIL_URI_COLUMN_INDEX = 2;
-
- private final ContentResolver mContentResolver;
-
- private DefaultNewCallsQuery(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
- }
-
- @Override
- public NewCall[] query() {
- final String selection = String.format("%s = 1 AND %s = ?", Calls.NEW, Calls.TYPE);
- final String[] selectionArgs = new String[]{ Integer.toString(Calls.VOICEMAIL_TYPE) };
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(Calls.CONTENT_URI_WITH_VOICEMAIL, PROJECTION,
- selection, selectionArgs, Calls.DEFAULT_SORT_ORDER);
- if (cursor == null) {
- return null;
- }
- NewCall[] newCalls = new NewCall[cursor.getCount()];
- while (cursor.moveToNext()) {
- newCalls[cursor.getPosition()] = createNewCallsFromCursor(cursor);
- }
- return newCalls;
- } finally {
- MoreCloseables.closeQuietly(cursor);
- }
- }
-
- /** Returns an instance of {@link NewCall} created by using the values of the cursor. */
- private NewCall createNewCallsFromCursor(Cursor cursor) {
- String voicemailUriString = cursor.getString(VOICEMAIL_URI_COLUMN_INDEX);
- Uri callsUri = ContentUris.withAppendedId(
- Calls.CONTENT_URI_WITH_VOICEMAIL, cursor.getLong(ID_COLUMN_INDEX));
- Uri voicemailUri = voicemailUriString == null ? null : Uri.parse(voicemailUriString);
- return new NewCall(callsUri, voicemailUri, cursor.getString(NUMBER_COLUMN_INDEX));
- }
- }
-
- /** Allows determining the name associated with a given phone number. */
- public interface NameLookupQuery {
- /**
- * Returns the name associated with the given number in the contacts database, or null if
- * the number does not correspond to any of the contacts.
- * <p>
- * If there are multiple contacts with the same phone number, it will return the name of one
- * of the matching contacts.
- */
- public String query(String number);
- }
-
- /** Create a new instance of {@link NameLookupQuery}. */
- public static NameLookupQuery createNameLookupQuery(ContentResolver contentResolver) {
- return new DefaultNameLookupQuery(contentResolver);
- }
-
- /**
- * Default implementation of {@link NameLookupQuery} that looks up the name of a contact in the
- * contacts database.
- */
- private static final class DefaultNameLookupQuery implements NameLookupQuery {
- private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME };
- private static final int DISPLAY_NAME_COLUMN_INDEX = 0;
-
- private final ContentResolver mContentResolver;
-
- private DefaultNameLookupQuery(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
- }
-
- @Override
- public String query(String number) {
- Cursor cursor = null;
- try {
- cursor = mContentResolver.query(
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
- PROJECTION, null, null, null);
- if (cursor == null || !cursor.moveToFirst()) return null;
- return cursor.getString(DISPLAY_NAME_COLUMN_INDEX);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- }
-
- /**
- * Create a new PhoneNumberHelper.
- * <p>
- * This will cause some Disk I/O, at least the first time it is created, so it should not be
- * called from the main thread.
- */
- public static PhoneNumberHelper createPhoneNumberHelper(Context context) {
- return new PhoneNumberHelper(context.getResources());
- }
-}
diff --git a/src/com/android/contacts/calllog/ExtendedCursor.java b/src/com/android/contacts/calllog/ExtendedCursor.java
deleted file mode 100644
index 3894192..0000000
--- a/src/com/android/contacts/calllog/ExtendedCursor.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.database.AbstractCursor;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-
-import com.android.common.io.MoreCloseables;
-
-/**
- * Wraps a cursor to add an additional column with the same value for all rows.
- * <p>
- * The number of rows in the cursor and the set of columns is determined by the cursor being
- * wrapped.
- */
-public class ExtendedCursor extends AbstractCursor {
- /** The cursor to wrap. */
- private final Cursor mCursor;
- /** The name of the additional column. */
- private final String mColumnName;
- /** The value to be assigned to the additional column. */
- private final Object mValue;
-
- /**
- * Creates a new cursor which extends the given cursor by adding a column with a constant value.
- *
- * @param cursor the cursor to extend
- * @param columnName the name of the additional column
- * @param value the value to be assigned to the additional column
- */
- public ExtendedCursor(Cursor cursor, String columnName, Object value) {
- mCursor = cursor;
- mColumnName = columnName;
- mValue = value;
- }
-
- @Override
- public int getCount() {
- return mCursor.getCount();
- }
-
- @Override
- public String[] getColumnNames() {
- String[] columnNames = mCursor.getColumnNames();
- int length = columnNames.length;
- String[] extendedColumnNames = new String[length + 1];
- System.arraycopy(columnNames, 0, extendedColumnNames, 0, length);
- extendedColumnNames[length] = mColumnName;
- return extendedColumnNames;
- }
-
- @Override
- public String getString(int column) {
- if (column == mCursor.getColumnCount()) {
- return (String) mValue;
- }
- return mCursor.getString(column);
- }
-
- @Override
- public short getShort(int column) {
- if (column == mCursor.getColumnCount()) {
- return (Short) mValue;
- }
- return mCursor.getShort(column);
- }
-
- @Override
- public int getInt(int column) {
- if (column == mCursor.getColumnCount()) {
- return (Integer) mValue;
- }
- return mCursor.getInt(column);
- }
-
- @Override
- public long getLong(int column) {
- if (column == mCursor.getColumnCount()) {
- return (Long) mValue;
- }
- return mCursor.getLong(column);
- }
-
- @Override
- public float getFloat(int column) {
- if (column == mCursor.getColumnCount()) {
- return (Float) mValue;
- }
- return mCursor.getFloat(column);
- }
-
- @Override
- public double getDouble(int column) {
- if (column == mCursor.getColumnCount()) {
- return (Double) mValue;
- }
- return mCursor.getDouble(column);
- }
-
- @Override
- public boolean isNull(int column) {
- if (column == mCursor.getColumnCount()) {
- return mValue == null;
- }
- return mCursor.isNull(column);
- }
-
- @Override
- public boolean onMove(int oldPosition, int newPosition) {
- return mCursor.moveToPosition(newPosition);
- }
-
- @Override
- public void close() {
- MoreCloseables.closeQuietly(mCursor);
- super.close();
- }
-
- @Override
- public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
- }
-
- @Override
- public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
- }
-
- @Override
- public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
- }
-
- @Override
- public void unregisterDataSetObserver(DataSetObserver observer) {
- mCursor.unregisterDataSetObserver(observer);
- }
-}
diff --git a/src/com/android/contacts/calllog/IntentProvider.java b/src/com/android/contacts/calllog/IntentProvider.java
deleted file mode 100644
index 487799c..0000000
--- a/src/com/android/contacts/calllog/IntentProvider.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.CallLog.Calls;
-
-import com.android.contacts.CallDetailActivity;
-import com.android.contacts.ContactsUtils;
-
-/**
- * Used to create an intent to attach to an action in the call log.
- * <p>
- * The intent is constructed lazily with the given information.
- */
-public abstract class IntentProvider {
- public abstract Intent getIntent(Context context);
-
- public static IntentProvider getReturnCallIntentProvider(final String number) {
- return new IntentProvider() {
- @Override
- public Intent getIntent(Context context) {
- return ContactsUtils.getCallIntent(number);
- }
- };
- }
-
- public static IntentProvider getPlayVoicemailIntentProvider(final long rowId,
- final String voicemailUri) {
- return new IntentProvider() {
- @Override
- public Intent getIntent(Context context) {
- Intent intent = new Intent(context, CallDetailActivity.class);
- intent.setData(ContentUris.withAppendedId(
- Calls.CONTENT_URI_WITH_VOICEMAIL, rowId));
- if (voicemailUri != null) {
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
- Uri.parse(voicemailUri));
- }
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, true);
- return intent;
- }
- };
- }
-
- public static IntentProvider getCallDetailIntentProvider(
- final CallLogAdapter adapter, final int position, final long id, final int groupSize) {
- return new IntentProvider() {
- @Override
- public Intent getIntent(Context context) {
- Cursor cursor = adapter.getCursor();
- cursor.moveToPosition(position);
- if (CallLogQuery.isSectionHeader(cursor)) {
- // Do nothing when a header is clicked.
- return null;
- }
- Intent intent = new Intent(context, CallDetailActivity.class);
- // Check if the first item is a voicemail.
- String voicemailUri = cursor.getString(CallLogQuery.VOICEMAIL_URI);
- if (voicemailUri != null) {
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
- Uri.parse(voicemailUri));
- }
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false);
-
- if (groupSize > 1) {
- // We want to restore the position in the cursor at the end.
- long[] ids = new long[groupSize];
- // Copy the ids of the rows in the group.
- for (int index = 0; index < groupSize; ++index) {
- ids[index] = cursor.getLong(CallLogQuery.ID);
- cursor.moveToNext();
- }
- intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, ids);
- } else {
- // If there is a single item, use the direct URI for it.
- intent.setData(ContentUris.withAppendedId(
- Calls.CONTENT_URI_WITH_VOICEMAIL, id));
- }
- return intent;
- }
- };
- }
-}
diff --git a/src/com/android/contacts/calllog/PhoneNumberHelper.java b/src/com/android/contacts/calllog/PhoneNumberHelper.java
deleted file mode 100644
index e24d0ae..0000000
--- a/src/com/android/contacts/calllog/PhoneNumberHelper.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.res.Resources;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-
-import com.android.contacts.R;
-import com.android.internal.telephony.CallerInfo;
-
-/**
- * Helper for formatting and managing phone numbers.
- */
-public class PhoneNumberHelper {
- private final Resources mResources;
-
- public PhoneNumberHelper(Resources resources) {
- mResources = resources;
- }
-
- /** Returns true if it is possible to place a call to the given number. */
- public boolean canPlaceCallsTo(CharSequence number) {
- return !(TextUtils.isEmpty(number)
- || number.equals(CallerInfo.UNKNOWN_NUMBER)
- || number.equals(CallerInfo.PRIVATE_NUMBER)
- || number.equals(CallerInfo.PAYPHONE_NUMBER));
- }
-
- /** Returns true if it is possible to send an SMS to the given number. */
- public boolean canSendSmsTo(CharSequence number) {
- return canPlaceCallsTo(number) && !isVoicemailNumber(number) && !isSipNumber(number);
- }
-
- /**
- * Returns the string to display for the given phone number.
- *
- * @param number the number to display
- * @param formattedNumber the formatted number if available, may be null
- */
- public CharSequence getDisplayNumber(CharSequence number, CharSequence formattedNumber) {
- if (TextUtils.isEmpty(number)) {
- return "";
- }
- if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
- return mResources.getString(R.string.unknown);
- }
- if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
- return mResources.getString(R.string.private_num);
- }
- if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
- return mResources.getString(R.string.payphone);
- }
- if (isVoicemailNumber(number)) {
- return mResources.getString(R.string.voicemail);
- }
- if (TextUtils.isEmpty(formattedNumber)) {
- return number;
- } else {
- return formattedNumber;
- }
- }
-
- /**
- * Returns true if the given number is the number of the configured voicemail.
- * To be able to mock-out this, it is not a static method.
- */
- public boolean isVoicemailNumber(CharSequence number) {
- return PhoneNumberUtils.isVoiceMailNumber(number.toString());
- }
-
- /**
- * Returns true if the given number is a SIP address.
- * To be able to mock-out this, it is not a static method.
- */
- public boolean isSipNumber(CharSequence number) {
- return PhoneNumberUtils.isUriNumber(number.toString());
- }
-}
diff --git a/src/com/android/contacts/calllog/PhoneQuery.java b/src/com/android/contacts/calllog/PhoneQuery.java
deleted file mode 100644
index af44add..0000000
--- a/src/com/android/contacts/calllog/PhoneQuery.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.provider.ContactsContract.PhoneLookup;
-
-/**
- * The query to look up the {@link ContactInfo} for a given number in the Call Log.
- */
-final class PhoneQuery {
- public static final String[] _PROJECTION = new String[] {
- PhoneLookup._ID,
- PhoneLookup.DISPLAY_NAME,
- PhoneLookup.TYPE,
- PhoneLookup.LABEL,
- PhoneLookup.NUMBER,
- PhoneLookup.NORMALIZED_NUMBER,
- PhoneLookup.PHOTO_ID,
- PhoneLookup.LOOKUP_KEY,
- PhoneLookup.PHOTO_URI};
-
- public static final int PERSON_ID = 0;
- public static final int NAME = 1;
- public static final int PHONE_TYPE = 2;
- public static final int LABEL = 3;
- public static final int MATCHED_NUMBER = 4;
- public static final int NORMALIZED_NUMBER = 5;
- public static final int PHOTO_ID = 6;
- public static final int LOOKUP_KEY = 7;
- public static final int PHOTO_URI = 8;
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/calllog/VoicemailNotifier.java b/src/com/android/contacts/calllog/VoicemailNotifier.java
deleted file mode 100644
index 8d45486..0000000
--- a/src/com/android/contacts/calllog/VoicemailNotifier.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.net.Uri;
-
-/**
- * Handles notifications for voicemails.
- */
-public interface VoicemailNotifier {
- /**
- * Updates the notification and clears it if there are no new voicemails.
- * <p>
- * If the given URI corresponds to a new voicemail, also notifies about it.
- * <p>
- * It is not safe to call this method from the main thread.
- *
- * @param newCallUri URI of the new call, may be null
- */
- public void updateNotification(Uri newCallUri);
-
- /** Clears the new voicemail notification. */
- public void clearNotification();
-}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
deleted file mode 100644
index 6ba4178..0000000
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ /dev/null
@@ -1,1629 +0,0 @@
-/*
- * Copyright (C) 2011 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.dialpad;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.AudioManager;
-import android.media.ToneGenerator;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemProperties;
-import android.provider.Contacts.Intents.Insert;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.PhonesColumns;
-import android.provider.Settings;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.text.Editable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.DialerKeyListener;
-import android.text.style.RelativeSizeSpan;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.PopupMenu;
-import android.widget.TextView;
-
-import com.android.contacts.ContactsUtils;
-import com.android.contacts.R;
-import com.android.contacts.SpecialCharSequenceMgr;
-import com.android.contacts.activities.DialtactsActivity;
-import com.android.contacts.util.Constants;
-import com.android.contacts.util.PhoneNumberFormatter;
-import com.android.contacts.util.StopWatch;
-import com.android.internal.telephony.ITelephony;
-import com.android.phone.CallLogAsync;
-import com.android.phone.HapticFeedback;
-
-/**
- * Fragment that displays a twelve-key phone dialpad.
- */
-public class DialpadFragment extends Fragment
- implements View.OnClickListener,
- View.OnLongClickListener, View.OnKeyListener,
- AdapterView.OnItemClickListener, TextWatcher,
- PopupMenu.OnMenuItemClickListener,
- DialpadImageButton.OnPressedListener {
- private static final String TAG = DialpadFragment.class.getSimpleName();
-
- private static final boolean DEBUG = DialtactsActivity.DEBUG;
-
- private static final String EMPTY_NUMBER = "";
-
- /** The length of DTMF tones in milliseconds */
- private static final int TONE_LENGTH_MS = 150;
- private static final int TONE_LENGTH_INFINITE = -1;
-
- /** The DTMF tone volume relative to other sounds in the stream */
- private static final int TONE_RELATIVE_VOLUME = 80;
-
- /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
- private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
-
- /**
- * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits
- * isn't enclosed by the container.
- */
- private View mDigitsContainer;
- private EditText mDigits;
-
- /** Remembers if we need to clear digits field when the screen is completely gone. */
- private boolean mClearDigitsOnStop;
-
- private View mDelete;
- private ToneGenerator mToneGenerator;
- private final Object mToneGeneratorLock = new Object();
- private View mDialpad;
- /**
- * Remembers the number of dialpad buttons which are pressed at this moment.
- * If it becomes 0, meaning no buttons are pressed, we'll call
- * {@link ToneGenerator#stopTone()}; the method shouldn't be called unless the last key is
- * released.
- */
- private int mDialpadPressCount;
-
- private View mDialButtonContainer;
- private View mDialButton;
- private ListView mDialpadChooser;
- private DialpadChooserAdapter mDialpadChooserAdapter;
-
- /**
- * Regular expression prohibiting manual phone call. Can be empty, which means "no rule".
- */
- private String mProhibitedPhoneNumberRegexp;
-
-
- // Last number dialed, retrieved asynchronously from the call DB
- // in onCreate. This number is displayed when the user hits the
- // send key and cleared in onPause.
- private final CallLogAsync mCallLog = new CallLogAsync();
- private String mLastNumberDialed = EMPTY_NUMBER;
-
- // determines if we want to playback local DTMF tones.
- private boolean mDTMFToneEnabled;
-
- // Vibration (haptic feedback) for dialer key presses.
- private final HapticFeedback mHaptic = new HapticFeedback();
-
- /** Identifier for the "Add Call" intent extra. */
- private static final String ADD_CALL_MODE_KEY = "add_call_mode";
-
- /**
- * Identifier for intent extra for sending an empty Flash message for
- * CDMA networks. This message is used by the network to simulate a
- * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
- *
- * TODO: Using an intent extra to tell the phone to send this flash is a
- * temporary measure. To be replaced with an ITelephony call in the future.
- * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
- * in Phone app until this is replaced with the ITelephony API.
- */
- private static final String EXTRA_SEND_EMPTY_FLASH
- = "com.android.phone.extra.SEND_EMPTY_FLASH";
-
- private String mCurrentCountryIso;
-
- private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- /**
- * Listen for phone state changes so that we can take down the
- * "dialpad chooser" if the phone becomes idle while the
- * chooser UI is visible.
- */
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
- // + state + ", '" + incomingNumber + "'");
- if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
- // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
- // Note there's a race condition in the UI here: the
- // dialpad chooser could conceivably disappear (on its
- // own) at the exact moment the user was trying to select
- // one of the choices, which would be confusing. (But at
- // least that's better than leaving the dialpad chooser
- // onscreen, but useless...)
- showDialpadChooser(false);
- }
- }
- };
-
- private boolean mWasEmptyBeforeTextChange;
-
- /**
- * This field is set to true while processing an incoming DIAL intent, in order to make sure
- * that SpecialCharSequenceMgr actions can be triggered by user input but *not* by a
- * tel: URI passed by some other app. It will be set to false when all digits are cleared.
- */
- private boolean mDigitsFilledByIntent;
-
- private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent";
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
- }
-
- @Override
- public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
- if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) {
- final Activity activity = getActivity();
- if (activity != null) {
- activity.invalidateOptionsMenu();
- }
- }
-
- // DTMF Tones do not need to be played here any longer -
- // the DTMF dialer handles that functionality now.
- }
-
- @Override
- public void afterTextChanged(Editable input) {
- // When DTMF dialpad buttons are being pressed, we delay SpecialCharSequencMgr sequence,
- // since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down"
- // behavior.
- if (!mDigitsFilledByIntent &&
- SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
- // A special sequence was entered, clear the digits
- mDigits.getText().clear();
- }
-
- if (isDigitsEmpty()) {
- mDigitsFilledByIntent = false;
- mDigits.setCursorVisible(false);
- }
-
- updateDialAndDeleteButtonEnabledState();
- }
-
- @Override
- public void onCreate(Bundle state) {
- super.onCreate(state);
-
- mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
-
- try {
- mHaptic.init(getActivity(),
- getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
- } catch (Resources.NotFoundException nfe) {
- Log.e(TAG, "Vibrate control bool missing.", nfe);
- }
-
- setHasOptionsMenu(true);
-
- mProhibitedPhoneNumberRegexp = getResources().getString(
- R.string.config_prohibited_phone_number_regexp);
-
- if (state != null) {
- mDigitsFilledByIntent = state.getBoolean(PREF_DIGITS_FILLED_BY_INTENT);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
- View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false);
-
- // Load up the resources for the text field.
- Resources r = getResources();
-
- mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
- mDigits = (EditText) fragmentView.findViewById(R.id.digits);
- mDigits.setKeyListener(DialerKeyListener.getInstance());
- mDigits.setOnClickListener(this);
- mDigits.setOnKeyListener(this);
- mDigits.setOnLongClickListener(this);
- mDigits.addTextChangedListener(this);
-
- PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
-
- // Check for the presence of the keypad
- View oneButton = fragmentView.findViewById(R.id.one);
- if (oneButton != null) {
- setupKeypad(fragmentView);
- }
-
- DisplayMetrics dm = getResources().getDisplayMetrics();
- int minCellSize = (int) (56 * dm.density); // 56dip == minimum size of menu buttons
- int cellCount = dm.widthPixels / minCellSize;
- int fakeMenuItemWidth = dm.widthPixels / cellCount;
- mDialButtonContainer = fragmentView.findViewById(R.id.dialButtonContainer);
- // If in portrait, add padding to the dial button since we need space for the
- // search and menu/overflow buttons.
- if (mDialButtonContainer != null && !ContactsUtils.isLandscape(this.getActivity())) {
- mDialButtonContainer.setPadding(
- fakeMenuItemWidth, mDialButtonContainer.getPaddingTop(),
- fakeMenuItemWidth, mDialButtonContainer.getPaddingBottom());
- }
- mDialButton = fragmentView.findViewById(R.id.dialButton);
- if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
- mDialButton.setOnClickListener(this);
- mDialButton.setOnLongClickListener(this);
- } else {
- mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
- mDialButton = null;
- }
-
- mDelete = fragmentView.findViewById(R.id.deleteButton);
- if (mDelete != null) {
- mDelete.setOnClickListener(this);
- mDelete.setOnLongClickListener(this);
- }
-
- mDialpad = fragmentView.findViewById(R.id.dialpad); // This is null in landscape mode.
-
- // In landscape we put the keyboard in phone mode.
- if (null == mDialpad) {
- mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
- } else {
- mDigits.setCursorVisible(false);
- }
-
- // Set up the "dialpad chooser" UI; see showDialpadChooser().
- mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
- mDialpadChooser.setOnItemClickListener(this);
-
- configureScreenFromIntent(getActivity().getIntent());
-
- return fragmentView;
- }
-
- private boolean isLayoutReady() {
- return mDigits != null;
- }
-
- public EditText getDigitsWidget() {
- return mDigits;
- }
-
- /**
- * @return true when {@link #mDigits} is actually filled by the Intent.
- */
- private boolean fillDigitsIfNecessary(Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
- Uri uri = intent.getData();
- if (uri != null) {
- if (Constants.SCHEME_TEL.equals(uri.getScheme())) {
- // Put the requested number into the input area
- String data = uri.getSchemeSpecificPart();
- // Remember it is filled via Intent.
- mDigitsFilledByIntent = true;
- setFormattedDigits(data, null);
- return true;
- } else {
- String type = intent.getType();
- if (People.CONTENT_ITEM_TYPE.equals(type)
- || Phones.CONTENT_ITEM_TYPE.equals(type)) {
- // Query the phone number
- Cursor c = getActivity().getContentResolver().query(intent.getData(),
- new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
- null, null, null);
- if (c != null) {
- try {
- if (c.moveToFirst()) {
- // Remember it is filled via Intent.
- mDigitsFilledByIntent = true;
- // Put the number into the input area
- setFormattedDigits(c.getString(0), c.getString(1));
- return true;
- }
- } finally {
- c.close();
- }
- }
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * @see #showDialpadChooser(boolean)
- */
- private static boolean needToShowDialpadChooser(Intent intent, boolean isAddCallMode) {
- final String action = intent.getAction();
-
- boolean needToShowDialpadChooser = false;
-
- if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
- Uri uri = intent.getData();
- if (uri == null) {
- // ACTION_DIAL or ACTION_VIEW with no data.
- // This behaves basically like ACTION_MAIN: If there's
- // already an active call, bring up an intermediate UI to
- // make the user confirm what they really want to do.
- // Be sure *not* to show the dialpad chooser if this is an
- // explicit "Add call" action, though.
- if (!isAddCallMode && phoneIsInUse()) {
- needToShowDialpadChooser = true;
- }
- }
- } else if (Intent.ACTION_MAIN.equals(action)) {
- // The MAIN action means we're bringing up a blank dialer
- // (e.g. by selecting the Home shortcut, or tabbing over from
- // Contacts or Call log.)
- //
- // At this point, IF there's already an active call, there's a
- // good chance that the user got here accidentally (but really
- // wanted the in-call dialpad instead). So we bring up an
- // intermediate UI to make the user confirm what they really
- // want to do.
- if (phoneIsInUse()) {
- // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
- needToShowDialpadChooser = true;
- }
- }
-
- return needToShowDialpadChooser;
- }
-
- private static boolean isAddCallMode(Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
- // see if we are "adding a call" from the InCallScreen; false by default.
- return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
- } else {
- return false;
- }
- }
-
- /**
- * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires
- * the screen to enter "Add Call" mode, this method will show correct UI for the mode.
- */
- public void configureScreenFromIntent(Intent intent) {
- if (!isLayoutReady()) {
- // This happens typically when parent's Activity#onNewIntent() is called while
- // Fragment#onCreateView() isn't called yet, and thus we cannot configure Views at
- // this point. onViewCreate() should call this method after preparing layouts, so
- // just ignore this call now.
- Log.i(TAG,
- "Screen configuration is requested before onCreateView() is called. Ignored");
- return;
- }
-
- boolean needToShowDialpadChooser = false;
-
- final boolean isAddCallMode = isAddCallMode(intent);
- if (!isAddCallMode) {
- final boolean digitsFilled = fillDigitsIfNecessary(intent);
- if (!digitsFilled) {
- needToShowDialpadChooser = needToShowDialpadChooser(intent, isAddCallMode);
- }
- }
- showDialpadChooser(needToShowDialpadChooser);
- }
-
- /**
- * Sets formatted digits to digits field.
- */
- private void setFormattedDigits(String data, String normalizedNumber) {
- // strip the non-dialable numbers out of the data string.
- String dialString = PhoneNumberUtils.extractNetworkPortion(data);
- dialString =
- PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
- if (!TextUtils.isEmpty(dialString)) {
- Editable digits = mDigits.getText();
- digits.replace(0, digits.length(), dialString);
- // for some reason this isn't getting called in the digits.replace call above..
- // but in any case, this will make sure the background drawable looks right
- afterTextChanged(digits);
- }
- }
-
- private void setupKeypad(View fragmentView) {
- int[] buttonIds = new int[] { R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
- R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.zero, R.id.star, R.id.pound};
- for (int id : buttonIds) {
- ((DialpadImageButton) fragmentView.findViewById(id)).setOnPressedListener(this);
- }
-
- // Long-pressing one button will initiate Voicemail.
- fragmentView.findViewById(R.id.one).setOnLongClickListener(this);
-
- // Long-pressing zero button will enter '+' instead.
- fragmentView.findViewById(R.id.zero).setOnLongClickListener(this);
-
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- final StopWatch stopWatch = StopWatch.start("Dialpad.onResume");
-
- // Query the last dialed number. Do it first because hitting
- // the DB is 'slow'. This call is asynchronous.
- queryLastOutgoingCall();
-
- stopWatch.lap("qloc");
-
- // retrieve the DTMF tone play back setting.
- mDTMFToneEnabled = Settings.System.getInt(getActivity().getContentResolver(),
- Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
-
- stopWatch.lap("dtwd");
-
- // Retrieve the haptic feedback setting.
- mHaptic.checkSystemSetting();
-
- stopWatch.lap("hptc");
-
- // if the mToneGenerator creation fails, just continue without it. It is
- // a local audio signal, and is not as important as the dtmf tone itself.
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- try {
- mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
- } catch (RuntimeException e) {
- Log.w(TAG, "Exception caught while creating local tone generator: " + e);
- mToneGenerator = null;
- }
- }
- }
- stopWatch.lap("tg");
- // Prevent unnecessary confusion. Reset the press count anyway.
- mDialpadPressCount = 0;
-
- Activity parent = getActivity();
- if (parent instanceof DialtactsActivity) {
- // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
- // digits in the dialer field.
- fillDigitsIfNecessary(parent.getIntent());
- }
-
- stopWatch.lap("fdin");
-
- // While we're in the foreground, listen for phone state changes,
- // purely so that we can take down the "dialpad chooser" if the
- // phone becomes idle while the chooser UI is visible.
- TelephonyManager telephonyManager =
- (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
- telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
- stopWatch.lap("tm");
-
- // Potentially show hint text in the mDigits field when the user
- // hasn't typed any digits yet. (If there's already an active call,
- // this hint text will remind the user that he's about to add a new
- // call.)
- //
- // TODO: consider adding better UI for the case where *both* lines
- // are currently in use. (Right now we let the user try to add
- // another call, but that call is guaranteed to fail. Perhaps the
- // entire dialer UI should be disabled instead.)
- if (phoneIsInUse()) {
- final SpannableString hint = new SpannableString(
- getActivity().getString(R.string.dialerDialpadHintText));
- hint.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), 0);
- mDigits.setHint(hint);
- } else {
- // Common case; no hint necessary.
- mDigits.setHint(null);
-
- // Also, a sanity-check: the "dialpad chooser" UI should NEVER
- // be visible if the phone is idle!
- showDialpadChooser(false);
- }
-
- stopWatch.lap("hnt");
-
- updateDialAndDeleteButtonEnabledState();
-
- stopWatch.lap("bes");
-
- stopWatch.stopAndLog(TAG, 50);
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- // Stop listening for phone state changes.
- TelephonyManager telephonyManager =
- (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
- telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-
- // Make sure we don't leave this activity with a tone still playing.
- stopTone();
- // Just in case reset the counter too.
- mDialpadPressCount = 0;
-
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator != null) {
- mToneGenerator.release();
- mToneGenerator = null;
- }
- }
- // TODO: I wonder if we should not check if the AsyncTask that
- // lookup the last dialed number has completed.
- mLastNumberDialed = EMPTY_NUMBER; // Since we are going to query again, free stale number.
-
- SpecialCharSequenceMgr.cleanup();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (mClearDigitsOnStop) {
- mClearDigitsOnStop = false;
- mDigits.getText().clear();
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(PREF_DIGITS_FILLED_BY_INTENT, mDigitsFilledByIntent);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // Landscape dialer uses the real actionbar menu, whereas portrait uses a fake one
- // that is created using constructPopupMenu()
- if (ContactsUtils.isLandscape(this.getActivity()) ||
- ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
- isLayoutReady() && mDialpadChooser != null) {
- inflater.inflate(R.menu.dialpad_options, menu);
- }
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- // Hardware menu key should be available and Views should already be ready.
- if (ContactsUtils.isLandscape(this.getActivity()) ||
- ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
- isLayoutReady() && mDialpadChooser != null) {
- setupMenuItems(menu);
- }
- }
-
- private void setupMenuItems(Menu menu) {
- final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_dialpad);
- final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
- final MenuItem twoSecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
- final MenuItem waitMenuItem = menu.findItem(R.id.menu_add_wait);
-
- // Check if all the menu items are inflated correctly. As a shortcut, we assume all menu
- // items are ready if the first item is non-null.
- if (callSettingsMenuItem == null) {
- return;
- }
-
- final Activity activity = getActivity();
- if (activity != null && ViewConfiguration.get(activity).hasPermanentMenuKey()) {
- // Call settings should be available via its parent Activity.
- callSettingsMenuItem.setVisible(false);
- } else {
- callSettingsMenuItem.setVisible(true);
- callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
- }
-
- // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
- // seeing usual dialpads and has typed at least one digit.
- // We never show a menu if the "choose dialpad" UI is up.
- if (dialpadChooserVisible() || isDigitsEmpty()) {
- addToContactMenuItem.setVisible(false);
- twoSecPauseMenuItem.setVisible(false);
- waitMenuItem.setVisible(false);
- } else {
- final CharSequence digits = mDigits.getText();
-
- // Put the current digits string into an intent
- addToContactMenuItem.setIntent(getAddToContactIntent(digits));
- addToContactMenuItem.setVisible(true);
-
- // Check out whether to show Pause & Wait option menu items
- int selectionStart;
- int selectionEnd;
- String strDigits = digits.toString();
-
- selectionStart = mDigits.getSelectionStart();
- selectionEnd = mDigits.getSelectionEnd();
-
- if (selectionStart != -1) {
- if (selectionStart > selectionEnd) {
- // swap it as we want start to be less then end
- int tmp = selectionStart;
- selectionStart = selectionEnd;
- selectionEnd = tmp;
- }
-
- if (selectionStart != 0) {
- // Pause can be visible if cursor is not in the begining
- twoSecPauseMenuItem.setVisible(true);
-
- // For Wait to be visible set of condition to meet
- waitMenuItem.setVisible(showWait(selectionStart, selectionEnd, strDigits));
- } else {
- // cursor in the beginning both pause and wait to be invisible
- twoSecPauseMenuItem.setVisible(false);
- waitMenuItem.setVisible(false);
- }
- } else {
- twoSecPauseMenuItem.setVisible(true);
-
- // cursor is not selected so assume new digit is added to the end
- int strLength = strDigits.length();
- waitMenuItem.setVisible(showWait(strLength, strLength, strDigits));
- }
- }
- }
-
- private static Intent getAddToContactIntent(CharSequence digits) {
- final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- intent.putExtra(Insert.PHONE, digits);
- intent.setType(People.CONTENT_ITEM_TYPE);
- return intent;
- }
-
- private void keyPressed(int keyCode) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_1:
- playTone(ToneGenerator.TONE_DTMF_1, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_2:
- playTone(ToneGenerator.TONE_DTMF_2, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_3:
- playTone(ToneGenerator.TONE_DTMF_3, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_4:
- playTone(ToneGenerator.TONE_DTMF_4, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_5:
- playTone(ToneGenerator.TONE_DTMF_5, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_6:
- playTone(ToneGenerator.TONE_DTMF_6, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_7:
- playTone(ToneGenerator.TONE_DTMF_7, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_8:
- playTone(ToneGenerator.TONE_DTMF_8, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_9:
- playTone(ToneGenerator.TONE_DTMF_9, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_0:
- playTone(ToneGenerator.TONE_DTMF_0, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_POUND:
- playTone(ToneGenerator.TONE_DTMF_P, TONE_LENGTH_INFINITE);
- break;
- case KeyEvent.KEYCODE_STAR:
- playTone(ToneGenerator.TONE_DTMF_S, TONE_LENGTH_INFINITE);
- break;
- default:
- break;
- }
-
- mHaptic.vibrate();
- KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
- mDigits.onKeyDown(keyCode, event);
-
- // If the cursor is at the end of the text we hide it.
- final int length = mDigits.length();
- if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
- mDigits.setCursorVisible(false);
- }
- }
-
- @Override
- public boolean onKey(View view, int keyCode, KeyEvent event) {
- switch (view.getId()) {
- case R.id.digits:
- if (keyCode == KeyEvent.KEYCODE_ENTER) {
- dialButtonPressed();
- return true;
- }
- break;
- }
- return false;
- }
-
- /**
- * When a key is pressed, we start playing DTMF tone, do vibration, and enter the digit
- * immediately. When a key is released, we stop the tone. Note that the "key press" event will
- * be delivered by the system with certain amount of delay, it won't be synced with user's
- * actual "touch-down" behavior.
- */
- @Override
- public void onPressed(View view, boolean pressed) {
- if (DEBUG) Log.d(TAG, "onPressed(). view: " + view + ", pressed: " + pressed);
- if (pressed) {
- switch (view.getId()) {
- case R.id.one: {
- keyPressed(KeyEvent.KEYCODE_1);
- break;
- }
- case R.id.two: {
- keyPressed(KeyEvent.KEYCODE_2);
- break;
- }
- case R.id.three: {
- keyPressed(KeyEvent.KEYCODE_3);
- break;
- }
- case R.id.four: {
- keyPressed(KeyEvent.KEYCODE_4);
- break;
- }
- case R.id.five: {
- keyPressed(KeyEvent.KEYCODE_5);
- break;
- }
- case R.id.six: {
- keyPressed(KeyEvent.KEYCODE_6);
- break;
- }
- case R.id.seven: {
- keyPressed(KeyEvent.KEYCODE_7);
- break;
- }
- case R.id.eight: {
- keyPressed(KeyEvent.KEYCODE_8);
- break;
- }
- case R.id.nine: {
- keyPressed(KeyEvent.KEYCODE_9);
- break;
- }
- case R.id.zero: {
- keyPressed(KeyEvent.KEYCODE_0);
- break;
- }
- case R.id.pound: {
- keyPressed(KeyEvent.KEYCODE_POUND);
- break;
- }
- case R.id.star: {
- keyPressed(KeyEvent.KEYCODE_STAR);
- break;
- }
- default: {
- Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view);
- break;
- }
- }
- mDialpadPressCount++;
- } else {
- view.jumpDrawablesToCurrentState();
- mDialpadPressCount--;
- if (mDialpadPressCount < 0) {
- // e.g.
- // - when the user action is detected as horizontal swipe, at which only
- // "up" event is thrown.
- // - when the user long-press '0' button, at which dialpad will decrease this count
- // while it still gets press-up event here.
- if (DEBUG) Log.d(TAG, "mKeyPressCount become negative.");
- stopTone();
- mDialpadPressCount = 0;
- } else if (mDialpadPressCount == 0) {
- stopTone();
- }
- }
- }
-
- @Override
- public void onClick(View view) {
- switch (view.getId()) {
- case R.id.deleteButton: {
- keyPressed(KeyEvent.KEYCODE_DEL);
- return;
- }
- case R.id.dialButton: {
- mHaptic.vibrate(); // Vibrate here too, just like we do for the regular keys
- dialButtonPressed();
- return;
- }
- case R.id.digits: {
- if (!isDigitsEmpty()) {
- mDigits.setCursorVisible(true);
- }
- return;
- }
- default: {
- Log.wtf(TAG, "Unexpected onClick() event from: " + view);
- return;
- }
- }
- }
-
- public PopupMenu constructPopupMenu(View anchorView) {
- final Context context = getActivity();
- if (context == null) {
- return null;
- }
- final PopupMenu popupMenu = new PopupMenu(context, anchorView);
- final Menu menu = popupMenu.getMenu();
- popupMenu.inflate(R.menu.dialpad_options);
- popupMenu.setOnMenuItemClickListener(this);
- setupMenuItems(menu);
- return popupMenu;
- }
-
- @Override
- public boolean onLongClick(View view) {
- final Editable digits = mDigits.getText();
- final int id = view.getId();
- switch (id) {
- case R.id.deleteButton: {
- digits.clear();
- // TODO: The framework forgets to clear the pressed
- // status of disabled button. Until this is fixed,
- // clear manually the pressed status. b/2133127
- mDelete.setPressed(false);
- return true;
- }
- case R.id.one: {
- // '1' may be already entered since we rely on onTouch() event for numeric buttons.
- // Just for safety we also check if the digits field is empty or not.
- if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) {
- // We'll try to initiate voicemail and thus we want to remove irrelevant string.
- removePreviousDigitIfPossible();
-
- if (isVoicemailAvailable()) {
- callVoicemail();
- } else if (getActivity() != null) {
- // Voicemail is unavailable maybe because Airplane mode is turned on.
- // Check the current status and show the most appropriate error message.
- final boolean isAirplaneModeOn =
- Settings.System.getInt(getActivity().getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0) != 0;
- if (isAirplaneModeOn) {
- DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_voicemail_airplane_mode_message);
- dialogFragment.show(getFragmentManager(),
- "voicemail_request_during_airplane_mode");
- } else {
- DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_voicemail_not_ready_message);
- dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
- }
- }
- return true;
- }
- return false;
- }
- case R.id.zero: {
- // Remove tentative input ('0') done by onTouch().
- removePreviousDigitIfPossible();
- keyPressed(KeyEvent.KEYCODE_PLUS);
-
- // Stop tone immediately and decrease the press count, so that possible subsequent
- // dial button presses won't honor the 0 click any more.
- // Note: this *will* make mDialpadPressCount negative when the 0 key is released,
- // which should be handled appropriately.
- stopTone();
- if (mDialpadPressCount > 0) mDialpadPressCount--;
-
- return true;
- }
- case R.id.digits: {
- // Right now EditText does not show the "paste" option when cursor is not visible.
- // To show that, make the cursor visible, and return false, letting the EditText
- // show the option by itself.
- mDigits.setCursorVisible(true);
- return false;
- }
- case R.id.dialButton: {
- if (isDigitsEmpty()) {
- handleDialButtonClickWithEmptyDigits();
- // This event should be consumed so that onClick() won't do the exactly same
- // thing.
- return true;
- } else {
- return false;
- }
- }
- }
- return false;
- }
-
- /**
- * Remove the digit just before the current position. This can be used if we want to replace
- * the previous digit or cancel previously entered character.
- */
- private void removePreviousDigitIfPossible() {
- final Editable editable = mDigits.getText();
- final int currentPosition = mDigits.getSelectionStart();
- if (currentPosition > 0) {
- mDigits.setSelection(currentPosition);
- mDigits.getText().delete(currentPosition - 1, currentPosition);
- }
- }
-
- public void callVoicemail() {
- startActivity(ContactsUtils.getVoicemailIntent());
- mClearDigitsOnStop = true;
- getActivity().finish();
- }
-
- public static class ErrorDialogFragment extends DialogFragment {
- private int mTitleResId;
- private int mMessageResId;
-
- private static final String ARG_TITLE_RES_ID = "argTitleResId";
- private static final String ARG_MESSAGE_RES_ID = "argMessageResId";
-
- public static ErrorDialogFragment newInstance(int messageResId) {
- return newInstance(0, messageResId);
- }
-
- public static ErrorDialogFragment newInstance(int titleResId, int messageResId) {
- final ErrorDialogFragment fragment = new ErrorDialogFragment();
- final Bundle args = new Bundle();
- args.putInt(ARG_TITLE_RES_ID, titleResId);
- args.putInt(ARG_MESSAGE_RES_ID, messageResId);
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mTitleResId = getArguments().getInt(ARG_TITLE_RES_ID);
- mMessageResId = getArguments().getInt(ARG_MESSAGE_RES_ID);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- if (mTitleResId != 0) {
- builder.setTitle(mTitleResId);
- }
- if (mMessageResId != 0) {
- builder.setMessage(mMessageResId);
- }
- builder.setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dismiss();
- }
- });
- return builder.create();
- }
- }
-
- /**
- * In most cases, when the dial button is pressed, there is a
- * number in digits area. Pack it in the intent, start the
- * outgoing call broadcast as a separate task and finish this
- * activity.
- *
- * When there is no digit and the phone is CDMA and off hook,
- * we're sending a blank flash for CDMA. CDMA networks use Flash
- * messages when special processing needs to be done, mainly for
- * 3-way or call waiting scenarios. Presumably, here we're in a
- * special 3-way scenario where the network needs a blank flash
- * before being able to add the new participant. (This is not the
- * case with all 3-way calls, just certain CDMA infrastructures.)
- *
- * Otherwise, there is no digit, display the last dialed
- * number. Don't finish since the user may want to edit it. The
- * user needs to press the dial button again, to dial it (general
- * case described above).
- */
- public void dialButtonPressed() {
- if (isDigitsEmpty()) { // No number entered.
- handleDialButtonClickWithEmptyDigits();
- } else {
- final String number = mDigits.getText().toString();
-
- // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
- // test equipment.
- // TODO: clean it up.
- if (number != null
- && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
- && number.matches(mProhibitedPhoneNumberRegexp)
- && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
- Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
- if (getActivity() != null) {
- DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
- R.string.dialog_phone_call_prohibited_message);
- dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
- }
-
- // Clear the digits just in case.
- mDigits.getText().clear();
- } else {
- final Intent intent = ContactsUtils.getCallIntent(number,
- (getActivity() instanceof DialtactsActivity ?
- ((DialtactsActivity)getActivity()).getCallOrigin() : null));
- startActivity(intent);
- mClearDigitsOnStop = true;
- getActivity().finish();
- }
- }
- }
-
- private void handleDialButtonClickWithEmptyDigits() {
- if (phoneIsCdma() && phoneIsOffhook()) {
- // This is really CDMA specific. On GSM is it possible
- // to be off hook and wanted to add a 3rd party using
- // the redial feature.
- startActivity(newFlashIntent());
- } else {
- if (!TextUtils.isEmpty(mLastNumberDialed)) {
- // Recall the last number dialed.
- mDigits.setText(mLastNumberDialed);
-
- // ...and move the cursor to the end of the digits string,
- // so you'll be able to delete digits using the Delete
- // button (just as if you had typed the number manually.)
- //
- // Note we use mDigits.getText().length() here, not
- // mLastNumberDialed.length(), since the EditText widget now
- // contains a *formatted* version of mLastNumberDialed (due to
- // mTextWatcher) and its length may have changed.
- mDigits.setSelection(mDigits.getText().length());
- } else {
- // There's no "last number dialed" or the
- // background query is still running. There's
- // nothing useful for the Dial button to do in
- // this case. Note: with a soft dial button, this
- // can never happens since the dial button is
- // disabled under these conditons.
- playTone(ToneGenerator.TONE_PROP_NACK);
- }
- }
- }
-
- /**
- * Plays the specified tone for TONE_LENGTH_MS milliseconds.
- */
- private void playTone(int tone) {
- playTone(tone, TONE_LENGTH_MS);
- }
-
- /**
- * Play the specified tone for the specified milliseconds
- *
- * The tone is played locally, using the audio stream for phone calls.
- * Tones are played only if the "Audible touch tones" user preference
- * is checked, and are NOT played if the device is in silent mode.
- *
- * The tone length can be -1, meaning "keep playing the tone." If the caller does so, it should
- * call stopTone() afterward.
- *
- * @param tone a tone code from {@link ToneGenerator}
- * @param durationMs tone length.
- */
- private void playTone(int tone, int durationMs) {
- // if local tone playback is disabled, just return.
- if (!mDTMFToneEnabled) {
- return;
- }
-
- // Also do nothing if the phone is in silent mode.
- // We need to re-check the ringer mode for *every* playTone()
- // call, rather than keeping a local flag that's updated in
- // onResume(), since it's possible to toggle silent mode without
- // leaving the current activity (via the ENDCALL-longpress menu.)
- AudioManager audioManager =
- (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
- int ringerMode = audioManager.getRingerMode();
- if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
- || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
- return;
- }
-
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
- return;
- }
-
- // Start the new tone (will stop any playing tone)
- mToneGenerator.startTone(tone, durationMs);
- }
- }
-
- /**
- * Stop the tone if it is played.
- */
- private void stopTone() {
- // if local tone playback is disabled, just return.
- if (!mDTMFToneEnabled) {
- return;
- }
- synchronized (mToneGeneratorLock) {
- if (mToneGenerator == null) {
- Log.w(TAG, "stopTone: mToneGenerator == null");
- return;
- }
- mToneGenerator.stopTone();
- }
- }
-
- /**
- * Brings up the "dialpad chooser" UI in place of the usual Dialer
- * elements (the textfield/button and the dialpad underneath).
- *
- * We show this UI if the user brings up the Dialer while a call is
- * already in progress, since there's a good chance we got here
- * accidentally (and the user really wanted the in-call dialpad instead).
- * So in this situation we display an intermediate UI that lets the user
- * explicitly choose between the in-call dialpad ("Use touch tone
- * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
- * to call in progress" just goes back to the in-call UI with no dialpad
- * at all.)
- *
- * @param enabled If true, show the "dialpad chooser" instead
- * of the regular Dialer UI
- */
- private void showDialpadChooser(boolean enabled) {
- // Check if onCreateView() is already called by checking one of View objects.
- if (!isLayoutReady()) {
- return;
- }
-
- if (enabled) {
- // Log.i(TAG, "Showing dialpad chooser!");
- if (mDigitsContainer != null) {
- mDigitsContainer.setVisibility(View.GONE);
- } else {
- // mDigits is not enclosed by the container. Make the digits field itself gone.
- mDigits.setVisibility(View.GONE);
- }
- if (mDialpad != null) mDialpad.setVisibility(View.GONE);
- if (mDialButtonContainer != null) mDialButtonContainer.setVisibility(View.GONE);
-
- mDialpadChooser.setVisibility(View.VISIBLE);
-
- // Instantiate the DialpadChooserAdapter and hook it up to the
- // ListView. We do this only once.
- if (mDialpadChooserAdapter == null) {
- mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
- }
- mDialpadChooser.setAdapter(mDialpadChooserAdapter);
- } else {
- // Log.i(TAG, "Displaying normal Dialer UI.");
- if (mDigitsContainer != null) {
- mDigitsContainer.setVisibility(View.VISIBLE);
- } else {
- mDigits.setVisibility(View.VISIBLE);
- }
- if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
- if (mDialButtonContainer != null) mDialButtonContainer.setVisibility(View.VISIBLE);
- mDialpadChooser.setVisibility(View.GONE);
- }
- }
-
- /**
- * @return true if we're currently showing the "dialpad chooser" UI.
- */
- private boolean dialpadChooserVisible() {
- return mDialpadChooser.getVisibility() == View.VISIBLE;
- }
-
- /**
- * Simple list adapter, binding to an icon + text label
- * for each item in the "dialpad chooser" list.
- */
- private static class DialpadChooserAdapter extends BaseAdapter {
- private LayoutInflater mInflater;
-
- // Simple struct for a single "choice" item.
- static class ChoiceItem {
- String text;
- Bitmap icon;
- int id;
-
- public ChoiceItem(String s, Bitmap b, int i) {
- text = s;
- icon = b;
- id = i;
- }
- }
-
- // IDs for the possible "choices":
- static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
- static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
- static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
-
- private static final int NUM_ITEMS = 3;
- private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
-
- public DialpadChooserAdapter(Context context) {
- // Cache the LayoutInflate to avoid asking for a new one each time.
- mInflater = LayoutInflater.from(context);
-
- // Initialize the possible choices.
- // TODO: could this be specified entirely in XML?
-
- // - "Use touch tone keypad"
- mChoiceItems[0] = new ChoiceItem(
- context.getString(R.string.dialer_useDtmfDialpad),
- BitmapFactory.decodeResource(context.getResources(),
- R.drawable.ic_dialer_fork_tt_keypad),
- DIALPAD_CHOICE_USE_DTMF_DIALPAD);
-
- // - "Return to call in progress"
- mChoiceItems[1] = new ChoiceItem(
- context.getString(R.string.dialer_returnToInCallScreen),
- BitmapFactory.decodeResource(context.getResources(),
- R.drawable.ic_dialer_fork_current_call),
- DIALPAD_CHOICE_RETURN_TO_CALL);
-
- // - "Add call"
- mChoiceItems[2] = new ChoiceItem(
- context.getString(R.string.dialer_addAnotherCall),
- BitmapFactory.decodeResource(context.getResources(),
- R.drawable.ic_dialer_fork_add_call),
- DIALPAD_CHOICE_ADD_NEW_CALL);
- }
-
- @Override
- public int getCount() {
- return NUM_ITEMS;
- }
-
- /**
- * Return the ChoiceItem for a given position.
- */
- @Override
- public Object getItem(int position) {
- return mChoiceItems[position];
- }
-
- /**
- * Return a unique ID for each possible choice.
- */
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- /**
- * Make a view for each row.
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // When convertView is non-null, we can reuse it (there's no need
- // to reinflate it.)
- if (convertView == null) {
- convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
- }
-
- TextView text = (TextView) convertView.findViewById(R.id.text);
- text.setText(mChoiceItems[position].text);
-
- ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
- icon.setImageBitmap(mChoiceItems[position].icon);
-
- return convertView;
- }
- }
-
- /**
- * Handle clicks from the dialpad chooser.
- */
- @Override
- public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
- DialpadChooserAdapter.ChoiceItem item =
- (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
- int itemId = item.id;
- switch (itemId) {
- case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
- // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
- // Fire off an intent to go back to the in-call UI
- // with the dialpad visible.
- returnToInCallScreen(true);
- break;
-
- case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
- // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
- // Fire off an intent to go back to the in-call UI
- // (with the dialpad hidden).
- returnToInCallScreen(false);
- break;
-
- case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
- // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
- // Ok, guess the user really did want to be here (in the
- // regular Dialer) after all. Bring back the normal Dialer UI.
- showDialpadChooser(false);
- break;
-
- default:
- Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
- break;
- }
- }
-
- /**
- * Returns to the in-call UI (where there's presumably a call in
- * progress) in response to the user selecting "use touch tone keypad"
- * or "return to call" from the dialpad chooser.
- */
- private void returnToInCallScreen(boolean showDialpad) {
- try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
- } catch (RemoteException e) {
- Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
- }
-
- // Finally, finish() ourselves so that we don't stay on the
- // activity stack.
- // Note that we do this whether or not the showCallScreenWithDialpad()
- // call above had any effect or not! (That call is a no-op if the
- // phone is idle, which can happen if the current call ends while
- // the dialpad chooser is up. In this case we can't show the
- // InCallScreen, and there's no point staying here in the Dialer,
- // so we just take the user back where he came from...)
- getActivity().finish();
- }
-
- /**
- * @return true if the phone is "in use", meaning that at least one line
- * is active (ie. off hook or ringing or dialing).
- */
- public static boolean phoneIsInUse() {
- boolean phoneInUse = false;
- try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (phone != null) phoneInUse = !phone.isIdle();
- } catch (RemoteException e) {
- Log.w(TAG, "phone.isIdle() failed", e);
- }
- return phoneInUse;
- }
-
- /**
- * @return true if the phone is a CDMA phone type
- */
- private boolean phoneIsCdma() {
- boolean isCdma = false;
- try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (phone != null) {
- isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "phone.getActivePhoneType() failed", e);
- }
- return isCdma;
- }
-
- /**
- * @return true if the phone state is OFFHOOK
- */
- private boolean phoneIsOffhook() {
- boolean phoneOffhook = false;
- try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- if (phone != null) phoneOffhook = phone.isOffhook();
- } catch (RemoteException e) {
- Log.w(TAG, "phone.isOffhook() failed", e);
- }
- return phoneOffhook;
- }
-
- /**
- * Returns true whenever any one of the options from the menu is selected.
- * Code changes to support dialpad options
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_2s_pause:
- updateDialString(",");
- return true;
- case R.id.menu_add_wait:
- updateDialString(";");
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return onOptionsItemSelected(item);
- }
-
- /**
- * Updates the dial string (mDigits) after inserting a Pause character (,)
- * or Wait character (;).
- */
- private void updateDialString(String newDigits) {
- int selectionStart;
- int selectionEnd;
-
- // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
- int anchor = mDigits.getSelectionStart();
- int point = mDigits.getSelectionEnd();
-
- selectionStart = Math.min(anchor, point);
- selectionEnd = Math.max(anchor, point);
-
- Editable digits = mDigits.getText();
- if (selectionStart != -1) {
- if (selectionStart == selectionEnd) {
- // then there is no selection. So insert the pause at this
- // position and update the mDigits.
- digits.replace(selectionStart, selectionStart, newDigits);
- } else {
- digits.replace(selectionStart, selectionEnd, newDigits);
- // Unselect: back to a regular cursor, just pass the character inserted.
- mDigits.setSelection(selectionStart + 1);
- }
- } else {
- int len = mDigits.length();
- digits.replace(len, len, newDigits);
- }
- }
-
- /**
- * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
- */
- private void updateDialAndDeleteButtonEnabledState() {
- final boolean digitsNotEmpty = !isDigitsEmpty();
-
- if (mDialButton != null) {
- // On CDMA phones, if we're already on a call, we *always*
- // enable the Dial button (since you can press it without
- // entering any digits to send an empty flash.)
- if (phoneIsCdma() && phoneIsOffhook()) {
- mDialButton.setEnabled(true);
- } else {
- // Common case: GSM, or CDMA but not on a call.
- // Enable the Dial button if some digits have
- // been entered, or if there is a last dialed number
- // that could be redialed.
- mDialButton.setEnabled(digitsNotEmpty ||
- !TextUtils.isEmpty(mLastNumberDialed));
- }
- }
- mDelete.setEnabled(digitsNotEmpty);
- }
-
- /**
- * Check if voicemail is enabled/accessible.
- *
- * @return true if voicemail is enabled and accessibly. Note that this can be false
- * "temporarily" after the app boot.
- * @see TelephonyManager#getVoiceMailNumber()
- */
- private boolean isVoicemailAvailable() {
- try {
- return (TelephonyManager.getDefault().getVoiceMailNumber() != null);
- } catch (SecurityException se) {
- // Possibly no READ_PHONE_STATE privilege.
- Log.w(TAG, "SecurityException is thrown. Maybe privilege isn't sufficient.");
- }
- return false;
- }
-
- /**
- * This function return true if Wait menu item can be shown
- * otherwise returns false. Assumes the passed string is non-empty
- * and the 0th index check is not required.
- */
- private static boolean showWait(int start, int end, String digits) {
- if (start == end) {
- // visible false in this case
- if (start > digits.length()) return false;
-
- // preceding char is ';', so visible should be false
- if (digits.charAt(start - 1) == ';') return false;
-
- // next char is ';', so visible should be false
- if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
- } else {
- // visible false in this case
- if (start > digits.length() || end > digits.length()) return false;
-
- // In this case we need to just check for ';' preceding to start
- // or next to end
- if (digits.charAt(start - 1) == ';') return false;
- }
- return true;
- }
-
- /**
- * @return true if the widget with the phone number digits is empty.
- */
- private boolean isDigitsEmpty() {
- return mDigits.length() == 0;
- }
-
- /**
- * Starts the asyn query to get the last dialed/outgoing
- * number. When the background query finishes, mLastNumberDialed
- * is set to the last dialed number or an empty string if none
- * exists yet.
- */
- private void queryLastOutgoingCall() {
- mLastNumberDialed = EMPTY_NUMBER;
- CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
- new CallLogAsync.GetLastOutgoingCallArgs(
- getActivity(),
- new CallLogAsync.OnLastOutgoingCallComplete() {
- @Override
- public void lastOutgoingCall(String number) {
- // TODO: Filter out emergency numbers if
- // the carrier does not want redial for
- // these.
- mLastNumberDialed = number;
- updateDialAndDeleteButtonEnabledState();
- }
- });
- mCallLog.getLastOutgoingCall(lastCallArgs);
- }
-
- private Intent newFlashIntent() {
- final Intent intent = ContactsUtils.getCallIntent(EMPTY_NUMBER);
- intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
- return intent;
- }
-}
diff --git a/src/com/android/contacts/dialpad/DialpadImageButton.java b/src/com/android/contacts/dialpad/DialpadImageButton.java
deleted file mode 100644
index a18cbb8..0000000
--- a/src/com/android/contacts/dialpad/DialpadImageButton.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2012 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.dialpad;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ImageButton;
-
-/**
- * Custom {@link ImageButton} for dialpad buttons.
- *
- * During horizontal swipe, we want to exit "fading out" animation offered by its background
- * just after starting the swipe.This class overrides {@link #onTouchEvent(MotionEvent)} to achieve
- * the behavior.
- */
-public class DialpadImageButton extends ImageButton {
- public interface OnPressedListener {
- public void onPressed(View view, boolean pressed);
- }
-
- private OnPressedListener mOnPressedListener;
-
- public void setOnPressedListener(OnPressedListener onPressedListener) {
- mOnPressedListener = onPressedListener;
- }
-
- public DialpadImageButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DialpadImageButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
- if (mOnPressedListener != null) {
- mOnPressedListener.onPressed(this, pressed);
- }
- }
-}
diff --git a/src/com/android/contacts/dialpad/DigitsEditText.java b/src/com/android/contacts/dialpad/DigitsEditText.java
deleted file mode 100644
index 7b3e8b2..0000000
--- a/src/com/android/contacts/dialpad/DigitsEditText.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2011 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.dialpad;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.text.InputType;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-/**
- * EditText which suppresses IME show up.
- */
-public class DigitsEditText extends EditText {
- public DigitsEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- setShowSoftInputOnFocus(false);
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- final InputMethodManager imm = ((InputMethodManager) getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE));
- if (imm != null && imm.isActive(this)) {
- imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final boolean ret = super.onTouchEvent(event);
- // Must be done after super.onTouchEvent()
- final InputMethodManager imm = ((InputMethodManager) getContext()
- .getSystemService(Context.INPUT_METHOD_SERVICE));
- if (imm != null && imm.isActive(this)) {
- imm.hideSoftInputFromWindow(getApplicationWindowToken(), 0);
- }
- return ret;
- }
-
- @Override
- public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) {
- // Since we're replacing the text every time we add or remove a
- // character, only read the difference. (issue 5337550)
- final int added = event.getAddedCount();
- final int removed = event.getRemovedCount();
- final int length = event.getBeforeText().length();
- if (added > removed) {
- event.setRemovedCount(0);
- event.setAddedCount(1);
- event.setFromIndex(length);
- } else if (removed > added) {
- event.setRemovedCount(1);
- event.setAddedCount(0);
- event.setFromIndex(length - 1);
- } else {
- return;
- }
- } else if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
- // The parent EditText class lets tts read "edit box" when this View has a focus, which
- // confuses users on app launch (issue 5275935).
- return;
- }
- super.sendAccessibilityEventUnchecked(event);
- }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index 207ceea..551eab4 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -51,7 +51,6 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsUtils;
import com.android.contacts.R;
-import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.activities.TransactionSafeActivity;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.AccountType.StringInflater;
@@ -456,7 +455,7 @@
* @param activity that is calling this interaction. This must be of type
* {@link TransactionSafeActivity} because we need to check on the activity state after the
* phone numbers have been queried for.
- * @param callOrigin If non null, {@link DialtactsActivity#EXTRA_CALL_ORIGIN} will be
+ * @param callOrigin If non null, {@link PhoneConstants#EXTRA_CALL_ORIGIN} will be
* appended to the Intent initiating phone call. See comments in Phone package (PhoneApp)
* for more detail.
*/
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index ddcb1ae..5377df2 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -30,20 +30,19 @@
*/
public abstract class ContactListFilterController {
- public static final String CONTACT_LIST_FILTER_SERVICE = "contactListFilter";
+ // singleton to cache the filter controller
+ private static ContactListFilterControllerImpl sFilterController = null;
public interface ContactListFilterListener {
void onContactListFilterChanged();
}
public static ContactListFilterController getInstance(Context context) {
- return (ContactListFilterController)
- context.getApplicationContext().getSystemService(CONTACT_LIST_FILTER_SERVICE);
- }
-
- public static ContactListFilterController
- createContactListFilterController(Context context) {
- return new ContactListFilterControllerImpl(context);
+ // We may need to synchronize this in the future if background task will call this.
+ if (sFilterController == null) {
+ sFilterController = new ContactListFilterControllerImpl(context);
+ }
+ return sFilterController;
}
public abstract void addListener(ContactListFilterListener listener);
diff --git a/src/com/android/contacts/test/FragmentTestActivity.java b/src/com/android/contacts/test/FragmentTestActivity.java
deleted file mode 100644
index 80c6731..0000000
--- a/src/com/android/contacts/test/FragmentTestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2010 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.test;
-
-import android.os.Bundle;
-import android.view.Window;
-import android.view.WindowManager;
-
-import com.android.contacts.ContactsActivity;
-import com.android.contacts.R;
-
-/**
- * An activity that is used for testing fragments. A unit test starts this
- * activity, adds a fragment and then tests the fragment.
- */
-public class FragmentTestActivity extends ContactsActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Normally fragment/activity onStart() methods will not be called when screen is locked.
- // Use the following flags to ensure that activities can be show for testing.
- Window window = getWindow();
- window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-
- setContentView(R.layout.fragment_test);
- }
-}
diff --git a/src/com/android/contacts/util/ExpirableCache.java b/src/com/android/contacts/util/ExpirableCache.java
deleted file mode 100644
index 3f19a26..0000000
--- a/src/com/android/contacts/util/ExpirableCache.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import android.util.LruCache;
-
-import com.android.contacts.test.NeededForTesting;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.concurrent.Immutable;
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * An LRU cache in which all items can be marked as expired at a given time and it is possible to
- * query whether a particular cached value is expired or not.
- * <p>
- * A typical use case for this is caching of values which are expensive to compute but which are
- * still useful when out of date.
- * <p>
- * Consider a cache for contact information:
- * <pre>{@code
- * private ExpirableCache<String, Contact> mContactCache;}</pre>
- * which stores the contact information for a given phone number.
- * <p>
- * When we need to store contact information for a given phone number, we can look up the info in
- * the cache:
- * <pre>{@code
- * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber);
- * }</pre>
- * We might also want to fetch the contact information again if the item is expired.
- * <pre>
- * if (cachedContact.isExpired()) {
- * fetchContactForNumber(phoneNumber,
- * new FetchListener() {
- * @Override
- * public void onFetched(Contact contact) {
- * mContactCache.put(phoneNumber, contact);
- * }
- * });
- * }</pre>
- * and insert it back into the cache when the fetch completes.
- * <p>
- * At a certain point we want to expire the content of the cache because we know the content may
- * no longer be up-to-date, for instance, when resuming the activity this is shown into:
- * <pre>
- * @Override
- * protected onResume() {
- * // We were paused for some time, the cached value might no longer be up to date.
- * mContactCache.expireAll();
- * super.onResume();
- * }
- * </pre>
- * The values will be still available from the cache, but they will be expired.
- * <p>
- * If interested only in the value itself, not whether it is expired or not, one should use the
- * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should
- * use the {@link #get(Object)} method instead.
- * <p>
- * This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior
- * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache
- * by using the {@link #create(LruCache)} method, which can define a custom expiration policy.
- * Since the underlying cache maps keys to cached values it can determine which items are expired
- * and which are not, allowing for an implementation that evicts expired items before non expired
- * ones.
- * <p>
- * This class is thread-safe.
- *
- * @param <K> the type of the keys
- * @param <V> the type of the values
- */
-@ThreadSafe
-public class ExpirableCache<K, V> {
- /**
- * A cached value stored inside the cache.
- * <p>
- * It provides access to the value stored in the cache but also allows to check whether the
- * value is expired.
- *
- * @param <V> the type of value stored in the cache
- */
- public interface CachedValue<V> {
- /** Returns the value stored in the cache for a given key. */
- public V getValue();
-
- /**
- * Checks whether the value, while still being present in the cache, is expired.
- *
- * @return true if the value is expired
- */
- public boolean isExpired();
- }
-
- /**
- * Cached values storing the generation at which they were added.
- */
- @Immutable
- private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> {
- /** The value stored in the cache. */
- public final V mValue;
- /** The generation at which the value was added to the cache. */
- private final int mGeneration;
- /** The atomic integer storing the current generation of the cache it belongs to. */
- private final AtomicInteger mCacheGeneration;
-
- /**
- * @param cacheGeneration the atomic integer storing the generation of the cache in which
- * this value will be stored
- */
- public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) {
- mValue = value;
- mCacheGeneration = cacheGeneration;
- // Snapshot the current generation.
- mGeneration = mCacheGeneration.get();
- }
-
- @Override
- public V getValue() {
- return mValue;
- }
-
- @Override
- public boolean isExpired() {
- return mGeneration != mCacheGeneration.get();
- }
- }
-
- /** The underlying cache used to stored the cached values. */
- private LruCache<K, CachedValue<V>> mCache;
-
- /**
- * The current generation of items added to the cache.
- * <p>
- * Items in the cache can belong to a previous generation, but in that case they would be
- * expired.
- *
- * @see ExpirableCache.CachedValue#isExpired()
- */
- private final AtomicInteger mGeneration;
-
- private ExpirableCache(LruCache<K, CachedValue<V>> cache) {
- mCache = cache;
- mGeneration = new AtomicInteger(0);
- }
-
- /**
- * Returns the cached value for the given key, or null if no value exists.
- * <p>
- * The cached value gives access both to the value associated with the key and whether it is
- * expired or not.
- * <p>
- * If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)}
- * instead.
- * <p>
- * If only wants values that are not expired, use {@link #get(Object)} instead.
- *
- * @param key the key to look up
- */
- public CachedValue<V> getCachedValue(K key) {
- return mCache.get(key);
- }
-
- /**
- * Returns the value for the given key, or null if no value exists.
- * <p>
- * When using this method, it is not possible to determine whether the value is expired or not.
- * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using
- * {@link #getCachedValue(Object)} to determine if an item is expired, one should use the item
- * within the {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the
- * value afterwards, since that is not guaranteed to return the same value or that the newly
- * returned value is in the same state.
- *
- * @param key the key to look up
- */
- public V getPossiblyExpired(K key) {
- CachedValue<V> cachedValue = getCachedValue(key);
- return cachedValue == null ? null : cachedValue.getValue();
- }
-
- /**
- * Returns the value for the given key only if it is not expired, or null if no value exists or
- * is expired.
- * <p>
- * This method will return null if either there is no value associated with this key or if the
- * associated value is expired.
- *
- * @param key the key to look up
- */
- @NeededForTesting
- public V get(K key) {
- CachedValue<V> cachedValue = getCachedValue(key);
- return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue();
- }
-
- /**
- * Puts an item in the cache.
- * <p>
- * Newly added item will not be expired until {@link #expireAll()} is next called.
- *
- * @param key the key to look up
- * @param value the value to associate with the key
- */
- public void put(K key, V value) {
- mCache.put(key, newCachedValue(value));
- }
-
- /**
- * Mark all items currently in the cache as expired.
- * <p>
- * Newly added items after this call will be marked as not expired.
- * <p>
- * Expiring the items in the cache does not imply they will be evicted.
- */
- public void expireAll() {
- mGeneration.incrementAndGet();
- }
-
- /**
- * Creates a new {@link CachedValue} instance to be stored in this cache.
- * <p>
- * Implementation of {@link LruCache#create(K)} can use this method to create a new entry.
- */
- public CachedValue<V> newCachedValue(V value) {
- return new GenerationalCachedValue<V>(value, mGeneration);
- }
-
- /**
- * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}.
- * <p>
- * The created cache takes ownership of the cache passed in as an argument.
- *
- * @param <K> the type of the keys
- * @param <V> the type of the values
- * @param cache the cache to store the value in
- * @return the newly created expirable cache
- * @throws IllegalArgumentException if the cache is not empty
- */
- public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) {
- return new ExpirableCache<K, V>(cache);
- }
-
- /**
- * Creates a new {@link ExpirableCache} with the given maximum size.
- *
- * @param <K> the type of the keys
- * @param <V> the type of the values
- * @return the newly created expirable cache
- */
- public static <K, V> ExpirableCache<K, V> create(int maxSize) {
- return create(new LruCache<K, CachedValue<V>>(maxSize));
- }
-}
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java b/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
deleted file mode 100644
index 5973387..0000000
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackFragment.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2011 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.voicemail;
-
-import static com.android.contacts.CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK;
-import static com.android.contacts.CallDetailActivity.EXTRA_VOICEMAIL_URI;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.provider.VoicemailContract;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.ProximitySensorAware;
-import com.android.contacts.R;
-import com.android.contacts.util.AsyncTaskExecutors;
-import com.android.ex.variablespeed.MediaPlayerProxy;
-import com.android.ex.variablespeed.VariableSpeed;
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.NotThreadSafe;
-
-/**
- * Displays and plays back a single voicemail.
- * <p>
- * When the Activity containing this Fragment is created, voicemail playback
- * will begin immediately. The Activity is expected to be started via an intent
- * containing a suitable voicemail uri to playback.
- * <p>
- * This class is not thread-safe, it is thread-confined. All calls to all public
- * methods on this class are expected to come from the main ui thread.
- */
-@NotThreadSafe
-public class VoicemailPlaybackFragment extends Fragment {
- private static final String TAG = "VoicemailPlayback";
- private static final int NUMBER_OF_THREADS_IN_POOL = 2;
- private static final String[] HAS_CONTENT_PROJECTION = new String[] {
- VoicemailContract.Voicemails.HAS_CONTENT,
- };
-
- private VoicemailPlaybackPresenter mPresenter;
- private ScheduledExecutorService mScheduledExecutorService;
- private View mPlaybackLayout;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mPlaybackLayout = inflater.inflate(R.layout.playback_layout, null);
- return mPlaybackLayout;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mScheduledExecutorService = createScheduledExecutorService();
- Bundle arguments = getArguments();
- Preconditions.checkNotNull(arguments, "fragment must be started with arguments");
- Uri voicemailUri = arguments.getParcelable(EXTRA_VOICEMAIL_URI);
- Preconditions.checkNotNull(voicemailUri, "fragment must contain EXTRA_VOICEMAIL_URI");
- boolean startPlayback = arguments.getBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, false);
- PowerManager powerManager =
- (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
- PowerManager.WakeLock wakeLock =
- powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK, getClass().getSimpleName());
- mPresenter = new VoicemailPlaybackPresenter(createPlaybackViewImpl(),
- createMediaPlayer(mScheduledExecutorService), voicemailUri,
- mScheduledExecutorService, startPlayback,
- AsyncTaskExecutors.createAsyncTaskExecutor(), wakeLock);
- mPresenter.onCreate(savedInstanceState);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- mPresenter.onSaveInstanceState(outState);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onDestroy() {
- mPresenter.onDestroy();
- mScheduledExecutorService.shutdown();
- super.onDestroy();
- }
-
- @Override
- public void onPause() {
- mPresenter.onPause();
- super.onPause();
- }
-
- private PlaybackViewImpl createPlaybackViewImpl() {
- return new PlaybackViewImpl(new ActivityReference(), getActivity().getApplicationContext(),
- mPlaybackLayout);
- }
-
- private MediaPlayerProxy createMediaPlayer(ExecutorService executorService) {
- return VariableSpeed.createVariableSpeed(executorService);
- }
-
- private ScheduledExecutorService createScheduledExecutorService() {
- return Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
- }
-
- /**
- * Formats a number of milliseconds as something that looks like {@code 00:05}.
- * <p>
- * We always use four digits, two for minutes two for seconds. In the very unlikely event
- * that the voicemail duration exceeds 99 minutes, the display is capped at 99 minutes.
- */
- private static String formatAsMinutesAndSeconds(int millis) {
- int seconds = millis / 1000;
- int minutes = seconds / 60;
- seconds -= minutes * 60;
- if (minutes > 99) {
- minutes = 99;
- }
- return String.format("%02d:%02d", minutes, seconds);
- }
-
- /**
- * An object that can provide us with an Activity.
- * <p>
- * Fragments suffer the drawback that the Activity they belong to may sometimes be null. This
- * can happen if the Fragment is detached, for example. In that situation a call to
- * {@link Fragment#getString(int)} will throw and {@link IllegalStateException}. Also, calling
- * {@link Fragment#getActivity()} is dangerous - it may sometimes return null. And thus blindly
- * calling a method on the result of getActivity() is dangerous too.
- * <p>
- * To work around this, I have made the {@link PlaybackViewImpl} class static, so that it does
- * not have access to any Fragment methods directly. Instead it uses an application Context for
- * things like accessing strings, accessing system services. It only uses the Activity when it
- * absolutely needs it - and does so through this class. This makes it easy to see where we have
- * to check for null properly.
- */
- private final class ActivityReference {
- /** Gets this Fragment's Activity: <b>may be null</b>. */
- public final Activity get() {
- return getActivity();
- }
- }
-
- /** Methods required by the PlaybackView for the VoicemailPlaybackPresenter. */
- private static final class PlaybackViewImpl implements VoicemailPlaybackPresenter.PlaybackView {
- private final ActivityReference mActivityReference;
- private final Context mApplicationContext;
- private final SeekBar mPlaybackSeek;
- private final ImageButton mStartStopButton;
- private final ImageButton mPlaybackSpeakerphone;
- private final ImageButton mRateDecreaseButton;
- private final ImageButton mRateIncreaseButton;
- private final TextViewWithMessagesController mTextController;
-
- public PlaybackViewImpl(ActivityReference activityReference, Context applicationContext,
- View playbackLayout) {
- Preconditions.checkNotNull(activityReference);
- Preconditions.checkNotNull(applicationContext);
- Preconditions.checkNotNull(playbackLayout);
- mActivityReference = activityReference;
- mApplicationContext = applicationContext;
- mPlaybackSeek = (SeekBar) playbackLayout.findViewById(R.id.playback_seek);
- mStartStopButton = (ImageButton) playbackLayout.findViewById(
- R.id.playback_start_stop);
- mPlaybackSpeakerphone = (ImageButton) playbackLayout.findViewById(
- R.id.playback_speakerphone);
- mRateDecreaseButton = (ImageButton) playbackLayout.findViewById(
- R.id.rate_decrease_button);
- mRateIncreaseButton = (ImageButton) playbackLayout.findViewById(
- R.id.rate_increase_button);
- mTextController = new TextViewWithMessagesController(
- (TextView) playbackLayout.findViewById(R.id.playback_position_text),
- (TextView) playbackLayout.findViewById(R.id.playback_speed_text));
- }
-
- @Override
- public void finish() {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.finish();
- }
- }
-
- @Override
- public void runOnUiThread(Runnable runnable) {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.runOnUiThread(runnable);
- }
- }
-
- @Override
- public Context getDataSourceContext() {
- return mApplicationContext;
- }
-
- @Override
- public void setRateDecreaseButtonListener(View.OnClickListener listener) {
- mRateDecreaseButton.setOnClickListener(listener);
- }
-
- @Override
- public void setRateIncreaseButtonListener(View.OnClickListener listener) {
- mRateIncreaseButton.setOnClickListener(listener);
- }
-
- @Override
- public void setStartStopListener(View.OnClickListener listener) {
- mStartStopButton.setOnClickListener(listener);
- }
-
- @Override
- public void setSpeakerphoneListener(View.OnClickListener listener) {
- mPlaybackSpeakerphone.setOnClickListener(listener);
- }
-
- @Override
- public void setRateDisplay(float rate, int stringResourceId) {
- mTextController.setTemporaryText(
- mApplicationContext.getString(stringResourceId), 1, TimeUnit.SECONDS);
- }
-
- @Override
- public void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener) {
- mPlaybackSeek.setOnSeekBarChangeListener(listener);
- }
-
- @Override
- public void playbackStarted() {
- mStartStopButton.setImageResource(R.drawable.ic_hold_pause_holo_dark);
- }
-
- @Override
- public void playbackStopped() {
- mStartStopButton.setImageResource(R.drawable.ic_play);
- }
-
- @Override
- public void enableProximitySensor() {
- // Only change the state if the activity is still around.
- Activity activity = mActivityReference.get();
- if (activity != null && activity instanceof ProximitySensorAware) {
- ((ProximitySensorAware) activity).enableProximitySensor();
- }
- }
-
- @Override
- public void disableProximitySensor() {
- // Only change the state if the activity is still around.
- Activity activity = mActivityReference.get();
- if (activity != null && activity instanceof ProximitySensorAware) {
- ((ProximitySensorAware) activity).disableProximitySensor(true);
- }
- }
-
- @Override
- public void registerContentObserver(Uri uri, ContentObserver observer) {
- mApplicationContext.getContentResolver().registerContentObserver(uri, false, observer);
- }
-
- @Override
- public void unregisterContentObserver(ContentObserver observer) {
- mApplicationContext.getContentResolver().unregisterContentObserver(observer);
- }
-
- @Override
- public void setClipPosition(int clipPositionInMillis, int clipLengthInMillis) {
- int seekBarPosition = Math.max(0, clipPositionInMillis);
- int seekBarMax = Math.max(seekBarPosition, clipLengthInMillis);
- if (mPlaybackSeek.getMax() != seekBarMax) {
- mPlaybackSeek.setMax(seekBarMax);
- }
- mPlaybackSeek.setProgress(seekBarPosition);
- mTextController.setPermanentText(
- formatAsMinutesAndSeconds(seekBarMax - seekBarPosition));
- }
-
- private String getString(int resId) {
- return mApplicationContext.getString(resId);
- }
-
- @Override
- public void setIsBuffering() {
- disableUiElements();
- mTextController.setPermanentText(getString(R.string.voicemail_buffering));
- }
-
- @Override
- public void setIsFetchingContent() {
- disableUiElements();
- mTextController.setPermanentText(getString(R.string.voicemail_fetching_content));
- }
-
- @Override
- public void setFetchContentTimeout() {
- disableUiElements();
- mTextController.setPermanentText(getString(R.string.voicemail_fetching_timout));
- }
-
- @Override
- public int getDesiredClipPosition() {
- return mPlaybackSeek.getProgress();
- }
-
- @Override
- public void disableUiElements() {
- mRateIncreaseButton.setEnabled(false);
- mRateDecreaseButton.setEnabled(false);
- mStartStopButton.setEnabled(false);
- mPlaybackSpeakerphone.setEnabled(false);
- mPlaybackSeek.setProgress(0);
- mPlaybackSeek.setEnabled(false);
- }
-
- @Override
- public void playbackError(Exception e) {
- disableUiElements();
- mTextController.setPermanentText(getString(R.string.voicemail_playback_error));
- Log.e(TAG, "Could not play voicemail", e);
- }
-
- @Override
- public void enableUiElements() {
- mRateIncreaseButton.setEnabled(true);
- mRateDecreaseButton.setEnabled(true);
- mStartStopButton.setEnabled(true);
- mPlaybackSpeakerphone.setEnabled(true);
- mPlaybackSeek.setEnabled(true);
- }
-
- @Override
- public void sendFetchVoicemailRequest(Uri voicemailUri) {
- Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, voicemailUri);
- mApplicationContext.sendBroadcast(intent);
- }
-
- @Override
- public boolean queryHasContent(Uri voicemailUri) {
- ContentResolver contentResolver = mApplicationContext.getContentResolver();
- Cursor cursor = contentResolver.query(
- voicemailUri, HAS_CONTENT_PROJECTION, null, null, null);
- try {
- if (cursor != null && cursor.moveToNext()) {
- return cursor.getInt(cursor.getColumnIndexOrThrow(
- VoicemailContract.Voicemails.HAS_CONTENT)) == 1;
- }
- } finally {
- MoreCloseables.closeQuietly(cursor);
- }
- return false;
- }
-
- private AudioManager getAudioManager() {
- return (AudioManager) mApplicationContext.getSystemService(Context.AUDIO_SERVICE);
- }
-
- @Override
- public boolean isSpeakerPhoneOn() {
- return getAudioManager().isSpeakerphoneOn();
- }
-
- @Override
- public void setSpeakerPhoneOn(boolean on) {
- getAudioManager().setSpeakerphoneOn(on);
- if (on) {
- mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_on);
- } else {
- mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_off);
- }
- }
-
- @Override
- public void setVolumeControlStream(int streamType) {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.setVolumeControlStream(streamType);
- }
- }
- }
-
- /**
- * Controls a TextView with dynamically changing text.
- * <p>
- * There are two methods here of interest,
- * {@link TextViewWithMessagesController#setPermanentText(String)} and
- * {@link TextViewWithMessagesController#setTemporaryText(String, long, TimeUnit)}. The
- * former is used to set the text on the text view immediately, and is used in our case for
- * the countdown of duration remaining during voicemail playback. The second is used to
- * temporarily replace this countdown with a message, in our case faster voicemail speed or
- * slower voicemail speed, before returning to the countdown display.
- * <p>
- * All the methods on this class must be called from the ui thread.
- */
- private static final class TextViewWithMessagesController {
- private static final float VISIBLE = 1;
- private static final float INVISIBLE = 0;
- private static final long SHORT_ANIMATION_MS = 200;
- private static final long LONG_ANIMATION_MS = 400;
- private final Object mLock = new Object();
- private final TextView mPermanentTextView;
- private final TextView mTemporaryTextView;
- @GuardedBy("mLock") private Runnable mRunnable;
-
- public TextViewWithMessagesController(TextView permanentTextView,
- TextView temporaryTextView) {
- mPermanentTextView = permanentTextView;
- mTemporaryTextView = temporaryTextView;
- }
-
- public void setPermanentText(String text) {
- mPermanentTextView.setText(text);
- }
-
- public void setTemporaryText(String text, long duration, TimeUnit units) {
- synchronized (mLock) {
- mTemporaryTextView.setText(text);
- mTemporaryTextView.animate().alpha(VISIBLE).setDuration(SHORT_ANIMATION_MS);
- mPermanentTextView.animate().alpha(INVISIBLE).setDuration(SHORT_ANIMATION_MS);
- mRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- // We check for (mRunnable == this) becuase if not true, then another
- // setTemporaryText call has taken place in the meantime, and this
- // one is now defunct and needs to take no action.
- if (mRunnable == this) {
- mRunnable = null;
- mTemporaryTextView.animate()
- .alpha(INVISIBLE).setDuration(LONG_ANIMATION_MS);
- mPermanentTextView.animate()
- .alpha(VISIBLE).setDuration(LONG_ANIMATION_MS);
- }
- }
- }
- };
- mTemporaryTextView.postDelayed(mRunnable, units.toMillis(duration));
- }
- }
- }
-}
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
deleted file mode 100644
index 2c7ba52..0000000
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright (C) 2011 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.voicemail;
-
-import static android.util.MathUtils.constrain;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.view.View;
-import android.widget.SeekBar;
-
-import com.android.contacts.R;
-import com.android.contacts.util.AsyncTaskExecutor;
-import com.android.ex.variablespeed.MediaPlayerProxy;
-import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Contains the controlling logic for a voicemail playback ui.
- * <p>
- * Specifically right now this class is used to control the
- * {@link com.android.contacts.voicemail.VoicemailPlaybackFragment}.
- * <p>
- * This class is not thread safe. The thread policy for this class is
- * thread-confinement, all calls into this class from outside must be done from
- * the main ui thread.
- */
-@NotThreadSafe
-@VisibleForTesting
-public class VoicemailPlaybackPresenter {
- /** The stream used to playback voicemail. */
- private static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
-
- /** Contract describing the behaviour we need from the ui we are controlling. */
- public interface PlaybackView {
- Context getDataSourceContext();
- void runOnUiThread(Runnable runnable);
- void setStartStopListener(View.OnClickListener listener);
- void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener);
- void setSpeakerphoneListener(View.OnClickListener listener);
- void setIsBuffering();
- void setClipPosition(int clipPositionInMillis, int clipLengthInMillis);
- int getDesiredClipPosition();
- void playbackStarted();
- void playbackStopped();
- void playbackError(Exception e);
- boolean isSpeakerPhoneOn();
- void setSpeakerPhoneOn(boolean on);
- void finish();
- void setRateDisplay(float rate, int stringResourceId);
- void setRateIncreaseButtonListener(View.OnClickListener listener);
- void setRateDecreaseButtonListener(View.OnClickListener listener);
- void setIsFetchingContent();
- void disableUiElements();
- void enableUiElements();
- void sendFetchVoicemailRequest(Uri voicemailUri);
- boolean queryHasContent(Uri voicemailUri);
- void setFetchContentTimeout();
- void registerContentObserver(Uri uri, ContentObserver observer);
- void unregisterContentObserver(ContentObserver observer);
- void enableProximitySensor();
- void disableProximitySensor();
- void setVolumeControlStream(int streamType);
- }
-
- /** The enumeration of {@link AsyncTask} objects we use in this class. */
- public enum Tasks {
- CHECK_FOR_CONTENT,
- CHECK_CONTENT_AFTER_CHANGE,
- PREPARE_MEDIA_PLAYER,
- RESET_PREPARE_START_MEDIA_PLAYER,
- }
-
- /** Update rate for the slider, 30fps. */
- private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30;
- /** Time our ui will wait for content to be fetched before reporting not available. */
- private static final long FETCH_CONTENT_TIMEOUT_MS = 20000;
- /**
- * If present in the saved instance bundle, we should not resume playback on
- * create.
- */
- private static final String PAUSED_STATE_KEY = VoicemailPlaybackPresenter.class.getName()
- + ".PAUSED_STATE_KEY";
- /**
- * If present in the saved instance bundle, indicates where to set the
- * playback slider.
- */
- private static final String CLIP_POSITION_KEY = VoicemailPlaybackPresenter.class.getName()
- + ".CLIP_POSITION_KEY";
-
- /** The preset variable-speed rates. Each is greater than the previous by 25%. */
- private static final float[] PRESET_RATES = new float[] {
- 0.64f, 0.8f, 1.0f, 1.25f, 1.5625f
- };
- /** The string resource ids corresponding to the names given to the above preset rates. */
- private static final int[] PRESET_NAMES = new int[] {
- R.string.voicemail_speed_slowest,
- R.string.voicemail_speed_slower,
- R.string.voicemail_speed_normal,
- R.string.voicemail_speed_faster,
- R.string.voicemail_speed_fastest,
- };
-
- /**
- * Pointer into the {@link VoicemailPlaybackPresenter#PRESET_RATES} array.
- * <p>
- * This doesn't need to be synchronized, it's used only by the {@link RateChangeListener}
- * which in turn is only executed on the ui thread. This can't be encapsulated inside the
- * rate change listener since multiple rate change listeners must share the same value.
- */
- private int mRateIndex = 2;
-
- /**
- * The most recently calculated duration.
- * <p>
- * We cache this in a field since we don't want to keep requesting it from the player, as
- * this can easily lead to throwing {@link IllegalStateException} (any time the player is
- * released, it's illegal to ask for the duration).
- */
- private final AtomicInteger mDuration = new AtomicInteger(0);
-
- private final PlaybackView mView;
- private final MediaPlayerProxy mPlayer;
- private final PositionUpdater mPositionUpdater;
-
- /** Voicemail uri to play. */
- private final Uri mVoicemailUri;
- /** Start playing in onCreate iff this is true. */
- private final boolean mStartPlayingImmediately;
- /** Used to run async tasks that need to interact with the ui. */
- private final AsyncTaskExecutor mAsyncTaskExecutor;
-
- /**
- * Used to handle the result of a successful or time-out fetch result.
- * <p>
- * This variable is thread-contained, accessed only on the ui thread.
- */
- private FetchResultHandler mFetchResultHandler;
- private PowerManager.WakeLock mWakeLock;
- private AsyncTask<Void, ?, ?> mPrepareTask;
-
- public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
- Uri voicemailUri, ScheduledExecutorService executorService,
- boolean startPlayingImmediately, AsyncTaskExecutor asyncTaskExecutor,
- PowerManager.WakeLock wakeLock) {
- mView = view;
- mPlayer = player;
- mVoicemailUri = voicemailUri;
- mStartPlayingImmediately = startPlayingImmediately;
- mAsyncTaskExecutor = asyncTaskExecutor;
- mPositionUpdater = new PositionUpdater(executorService, SLIDER_UPDATE_PERIOD_MILLIS);
- mWakeLock = wakeLock;
- }
-
- public void onCreate(Bundle bundle) {
- mView.setVolumeControlStream(PLAYBACK_STREAM);
- checkThatWeHaveContent();
- }
-
- /**
- * Checks to see if we have content available for this voicemail.
- * <p>
- * This method will be called once, after the fragment has been created, before we know if the
- * voicemail we've been asked to play has any content available.
- * <p>
- * This method will notify the user through the ui that we are fetching the content, then check
- * to see if the content field in the db is set. If set, we proceed to
- * {@link #postSuccessfullyFetchedContent()} method. If not set, we will make a request to fetch
- * the content asynchronously via {@link #makeRequestForContent()}.
- */
- private void checkThatWeHaveContent() {
- mView.setIsFetchingContent();
- mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() {
- @Override
- public Boolean doInBackground(Void... params) {
- return mView.queryHasContent(mVoicemailUri);
- }
-
- @Override
- public void onPostExecute(Boolean hasContent) {
- if (hasContent) {
- postSuccessfullyFetchedContent();
- } else {
- makeRequestForContent();
- }
- }
- });
- }
-
- /**
- * Makes a broadcast request to ask that a voicemail source fetch this content.
- * <p>
- * This method <b>must be called on the ui thread</b>.
- * <p>
- * This method will be called when we realise that we don't have content for this voicemail. It
- * will trigger a broadcast to request that the content be downloaded. It will add a listener to
- * the content resolver so that it will be notified when the has_content field changes. It will
- * also set a timer. If the has_content field changes to true within the allowed time, we will
- * proceed to {@link #postSuccessfullyFetchedContent()}. If the has_content field does not
- * become true within the allowed time, we will update the ui to reflect the fact that content
- * was not available.
- */
- private void makeRequestForContent() {
- Handler handler = new Handler();
- Preconditions.checkState(mFetchResultHandler == null, "mFetchResultHandler should be null");
- mFetchResultHandler = new FetchResultHandler(handler);
- mView.registerContentObserver(mVoicemailUri, mFetchResultHandler);
- handler.postDelayed(mFetchResultHandler.getTimeoutRunnable(), FETCH_CONTENT_TIMEOUT_MS);
- mView.sendFetchVoicemailRequest(mVoicemailUri);
- }
-
- @ThreadSafe
- private class FetchResultHandler extends ContentObserver implements Runnable {
- private AtomicBoolean mResultStillPending = new AtomicBoolean(true);
- private final Handler mHandler;
-
- public FetchResultHandler(Handler handler) {
- super(handler);
- mHandler = handler;
- }
-
- public Runnable getTimeoutRunnable() {
- return this;
- }
-
- @Override
- public void run() {
- if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
- mView.setFetchContentTimeout();
- }
- }
-
- public void destroy() {
- if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
- mHandler.removeCallbacks(this);
- }
- }
-
- @Override
- public void onChange(boolean selfChange) {
- mAsyncTaskExecutor.submit(Tasks.CHECK_CONTENT_AFTER_CHANGE,
- new AsyncTask<Void, Void, Boolean>() {
- @Override
- public Boolean doInBackground(Void... params) {
- return mView.queryHasContent(mVoicemailUri);
- }
-
- @Override
- public void onPostExecute(Boolean hasContent) {
- if (hasContent) {
- if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
- postSuccessfullyFetchedContent();
- }
- }
- }
- });
- }
- }
-
- /**
- * Prepares the voicemail content for playback.
- * <p>
- * This method will be called once we know that our voicemail has content (according to the
- * content provider). This method will try to prepare the data source through the media player.
- * If preparing the media player works, we will call through to
- * {@link #postSuccessfulPrepareActions()}. If preparing the media player fails (perhaps the
- * file the content provider points to is actually missing, perhaps it is of an unknown file
- * format that we can't play, who knows) then we will show an error on the ui.
- */
- private void postSuccessfullyFetchedContent() {
- mView.setIsBuffering();
- mAsyncTaskExecutor.submit(Tasks.PREPARE_MEDIA_PLAYER,
- new AsyncTask<Void, Void, Exception>() {
- @Override
- public Exception doInBackground(Void... params) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
- mPlayer.setAudioStreamType(PLAYBACK_STREAM);
- mPlayer.prepare();
- return null;
- } catch (Exception e) {
- return e;
- }
- }
-
- @Override
- public void onPostExecute(Exception exception) {
- if (exception == null) {
- postSuccessfulPrepareActions();
- } else {
- mView.playbackError(exception);
- }
- }
- });
- }
-
- /**
- * Enables the ui, and optionally starts playback immediately.
- * <p>
- * This will be called once we have successfully prepared the media player, and will optionally
- * playback immediately.
- */
- private void postSuccessfulPrepareActions() {
- mView.enableUiElements();
- mView.setPositionSeekListener(new PlaybackPositionListener());
- mView.setStartStopListener(new StartStopButtonListener());
- mView.setSpeakerphoneListener(new SpeakerphoneListener());
- mPlayer.setOnErrorListener(new MediaPlayerErrorListener());
- mPlayer.setOnCompletionListener(new MediaPlayerCompletionListener());
- mView.setSpeakerPhoneOn(mView.isSpeakerPhoneOn());
- mView.setRateDecreaseButtonListener(createRateDecreaseListener());
- mView.setRateIncreaseButtonListener(createRateIncreaseListener());
- mView.setClipPosition(0, mPlayer.getDuration());
- mView.playbackStopped();
- // Always disable on stop.
- mView.disableProximitySensor();
- if (mStartPlayingImmediately) {
- resetPrepareStartPlaying(0);
- }
- // TODO: Now I'm ignoring the bundle, when previously I was checking for contains against
- // the PAUSED_STATE_KEY, and CLIP_POSITION_KEY.
- }
-
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition());
- if (!mPlayer.isPlaying()) {
- outState.putBoolean(PAUSED_STATE_KEY, true);
- }
- }
-
- public void onDestroy() {
- mPlayer.release();
- if (mFetchResultHandler != null) {
- mFetchResultHandler.destroy();
- mFetchResultHandler = null;
- }
- mPositionUpdater.stopUpdating();
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- mView.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- handleError(new IllegalStateException("MediaPlayer error listener invoked"));
- }
- });
- return true;
- }
- }
-
- private class MediaPlayerCompletionListener implements MediaPlayer.OnCompletionListener {
- @Override
- public void onCompletion(final MediaPlayer mp) {
- mView.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- handleCompletion(mp);
- }
- });
- }
- }
-
- public View.OnClickListener createRateDecreaseListener() {
- return new RateChangeListener(false);
- }
-
- public View.OnClickListener createRateIncreaseListener() {
- return new RateChangeListener(true);
- }
-
- /**
- * Listens to clicks on the rate increase and decrease buttons.
- * <p>
- * This class is not thread-safe, but all interactions with it will happen on the ui thread.
- */
- private class RateChangeListener implements View.OnClickListener {
- private final boolean mIncrease;
-
- public RateChangeListener(boolean increase) {
- mIncrease = increase;
- }
-
- @Override
- public void onClick(View v) {
- // Adjust the current rate, then clamp it to the allowed values.
- mRateIndex = constrain(mRateIndex + (mIncrease ? 1 : -1), 0, PRESET_RATES.length - 1);
- // Whether or not we have actually changed the index, call changeRate().
- // This will ensure that we show the "fastest" or "slowest" text on the ui to indicate
- // to the user that it doesn't get any faster or slower.
- changeRate(PRESET_RATES[mRateIndex], PRESET_NAMES[mRateIndex]);
- }
- }
-
- private void resetPrepareStartPlaying(final int clipPositionInMillis) {
- if (mPrepareTask != null) {
- mPrepareTask.cancel(false);
- }
- mPrepareTask = mAsyncTaskExecutor.submit(Tasks.RESET_PREPARE_START_MEDIA_PLAYER,
- new AsyncTask<Void, Void, Exception>() {
- @Override
- public Exception doInBackground(Void... params) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
- mPlayer.setAudioStreamType(PLAYBACK_STREAM);
- mPlayer.prepare();
- return null;
- } catch (Exception e) {
- return e;
- }
- }
-
- @Override
- public void onPostExecute(Exception exception) {
- mPrepareTask = null;
- if (exception == null) {
- mDuration.set(mPlayer.getDuration());
- int startPosition =
- constrain(clipPositionInMillis, 0, mDuration.get());
- mView.setClipPosition(startPosition, mDuration.get());
- mPlayer.seekTo(startPosition);
- mPlayer.start();
- mView.playbackStarted();
- if (!mWakeLock.isHeld()) {
- mWakeLock.acquire();
- }
- // Only enable if we are not currently using the speaker phone.
- if (!mView.isSpeakerPhoneOn()) {
- mView.enableProximitySensor();
- }
- mPositionUpdater.startUpdating(startPosition, mDuration.get());
- } else {
- handleError(exception);
- }
- }
- });
- }
-
- private void handleError(Exception e) {
- mView.playbackError(e);
- mPositionUpdater.stopUpdating();
- mPlayer.release();
- }
-
- public void handleCompletion(MediaPlayer mediaPlayer) {
- stopPlaybackAtPosition(0, mDuration.get());
- }
-
- private void stopPlaybackAtPosition(int clipPosition, int duration) {
- mPositionUpdater.stopUpdating();
- mView.playbackStopped();
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- // Always disable on stop.
- mView.disableProximitySensor();
- mView.setClipPosition(clipPosition, duration);
- if (mPlayer.isPlaying()) {
- mPlayer.pause();
- }
- }
-
- private class PlaybackPositionListener implements SeekBar.OnSeekBarChangeListener {
- private boolean mShouldResumePlaybackAfterSeeking;
-
- @Override
- public void onStartTrackingTouch(SeekBar arg0) {
- if (mPlayer.isPlaying()) {
- mShouldResumePlaybackAfterSeeking = true;
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else {
- mShouldResumePlaybackAfterSeeking = false;
- }
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar arg0) {
- if (mPlayer.isPlaying()) {
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- }
- if (mShouldResumePlaybackAfterSeeking) {
- resetPrepareStartPlaying(mView.getDesiredClipPosition());
- }
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- mView.setClipPosition(seekBar.getProgress(), seekBar.getMax());
- }
- }
-
- private void changeRate(float rate, int stringResourceId) {
- ((SingleThreadedMediaPlayerProxy) mPlayer).setVariableSpeed(rate);
- mView.setRateDisplay(rate, stringResourceId);
- }
-
- private class SpeakerphoneListener implements View.OnClickListener {
- @Override
- public void onClick(View v) {
- boolean previousState = mView.isSpeakerPhoneOn();
- mView.setSpeakerPhoneOn(!previousState);
- if (mPlayer.isPlaying() && previousState) {
- // If we are currently playing and we are disabling the speaker phone, enable the
- // sensor.
- mView.enableProximitySensor();
- } else {
- // If we are not currently playing, disable the sensor.
- mView.disableProximitySensor();
- }
- }
- }
-
- private class StartStopButtonListener implements View.OnClickListener {
- @Override
- public void onClick(View arg0) {
- if (mPlayer.isPlaying()) {
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else {
- resetPrepareStartPlaying(mView.getDesiredClipPosition());
- }
- }
- }
-
- /**
- * Controls the animation of the playback slider.
- */
- @ThreadSafe
- private final class PositionUpdater implements Runnable {
- private final ScheduledExecutorService mExecutorService;
- private final int mPeriodMillis;
- private final Object mLock = new Object();
- @GuardedBy("mLock") private ScheduledFuture<?> mScheduledFuture;
- private final Runnable mSetClipPostitionRunnable = new Runnable() {
- @Override
- public void run() {
- int currentPosition = 0;
- synchronized (mLock) {
- if (mScheduledFuture == null) {
- // This task has been canceled. Just stop now.
- return;
- }
- currentPosition = mPlayer.getCurrentPosition();
- }
- mView.setClipPosition(currentPosition, mDuration.get());
- }
- };
-
- public PositionUpdater(ScheduledExecutorService executorService, int periodMillis) {
- mExecutorService = executorService;
- mPeriodMillis = periodMillis;
- }
-
- @Override
- public void run() {
- mView.runOnUiThread(mSetClipPostitionRunnable);
- }
-
- public void startUpdating(int beginPosition, int endPosition) {
- synchronized (mLock) {
- if (mScheduledFuture != null) {
- mScheduledFuture.cancel(false);
- }
- mScheduledFuture = mExecutorService.scheduleAtFixedRate(this, 0, mPeriodMillis,
- TimeUnit.MILLISECONDS);
- }
- }
-
- public void stopUpdating() {
- synchronized (mLock) {
- if (mScheduledFuture != null) {
- mScheduledFuture.cancel(false);
- mScheduledFuture = null;
- }
- }
- }
- }
-
- public void onPause() {
- if (mPlayer.isPlaying()) {
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- }
- if (mPrepareTask != null) {
- mPrepareTask.cancel(false);
- }
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-}
diff --git a/src/com/android/contacts/voicemail/VoicemailStatusHelper.java b/src/com/android/contacts/voicemail/VoicemailStatusHelper.java
deleted file mode 100644
index 6664b88..0000000
--- a/src/com/android/contacts/voicemail/VoicemailStatusHelper.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2011 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.voicemail;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.VoicemailContract.Status;
-
-import java.util.List;
-
-/**
- * Interface used by the call log UI to determine what user message, if any, related to voicemail
- * source status needs to be shown. The messages are returned in the order of importance.
- * <p>
- * The implementation of this interface interacts with the voicemail content provider to fetch
- * statuses of all the registered voicemail sources and determines if any status message needs to
- * be shown. The user of this interface must observe/listen to provider changes and invoke
- * this class to check if any message needs to be shown.
- */
-public interface VoicemailStatusHelper {
- public class StatusMessage {
- /** Package of the source on behalf of which this message has to be shown.*/
- public final String sourcePackage;
- /**
- * The string resource id of the status message that should be shown in the call log
- * page. Set to -1, if this message is not to be shown in call log.
- */
- public final int callLogMessageId;
- /**
- * The string resource id of the status message that should be shown in the call details
- * page. Set to -1, if this message is not to be shown in call details page.
- */
- public final int callDetailsMessageId;
- /** The string resource id of the action message that should be shown. */
- public final int actionMessageId;
- /** URI for the corrective action, where applicable. Null if no action URI is available. */
- public final Uri actionUri;
- public StatusMessage(String sourcePackage, int callLogMessageId, int callDetailsMessageId,
- int actionMessageId, Uri actionUri) {
- this.sourcePackage = sourcePackage;
- this.callLogMessageId = callLogMessageId;
- this.callDetailsMessageId = callDetailsMessageId;
- this.actionMessageId = actionMessageId;
- this.actionUri = actionUri;
- }
-
- /** Whether this message should be shown in the call log page. */
- public boolean showInCallLog() {
- return callLogMessageId != -1;
- }
-
- /** Whether this message should be shown in the call details page. */
- public boolean showInCallDetails() {
- return callDetailsMessageId != -1;
- }
- }
-
- /**
- * Returns a list of messages, in the order or priority that should be shown to the user. An
- * empty list is returned if no message needs to be shown.
- * @param cursor The cursor pointing to the query on {@link Status#CONTENT_URI}. The projection
- * to be used is defined by the implementation class of this interface.
- */
- public List<StatusMessage> getStatusMessages(Cursor cursor);
-
- /**
- * Returns the number of active voicemail sources installed.
- * <p>
- * The number of sources is counted by querying the voicemail status table.
- */
- public int getNumberActivityVoicemailSources(Cursor cursor);
-}
diff --git a/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java b/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java
deleted file mode 100644
index c7f9c8f..0000000
--- a/src/com/android/contacts/voicemail/VoicemailStatusHelperImpl.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2011 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.voicemail;
-
-import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
-import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_OK;
-import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
-import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.VoicemailContract.Status;
-
-import com.android.contacts.R;
-import com.android.contacts.util.UriUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/** Implementation of {@link VoicemailStatusHelper}. */
-public class VoicemailStatusHelperImpl implements VoicemailStatusHelper {
- private static final int SOURCE_PACKAGE_INDEX = 0;
- private static final int CONFIGURATION_STATE_INDEX = 1;
- private static final int DATA_CHANNEL_STATE_INDEX = 2;
- private static final int NOTIFICATION_CHANNEL_STATE_INDEX = 3;
- private static final int SETTINGS_URI_INDEX = 4;
- private static final int VOICEMAIL_ACCESS_URI_INDEX = 5;
- private static final int NUM_COLUMNS = 6;
- /** Projection on the voicemail_status table used by this class. */
- public static final String[] PROJECTION = new String[NUM_COLUMNS];
- static {
- PROJECTION[SOURCE_PACKAGE_INDEX] = Status.SOURCE_PACKAGE;
- PROJECTION[CONFIGURATION_STATE_INDEX] = Status.CONFIGURATION_STATE;
- PROJECTION[DATA_CHANNEL_STATE_INDEX] = Status.DATA_CHANNEL_STATE;
- PROJECTION[NOTIFICATION_CHANNEL_STATE_INDEX] = Status.NOTIFICATION_CHANNEL_STATE;
- PROJECTION[SETTINGS_URI_INDEX] = Status.SETTINGS_URI;
- PROJECTION[VOICEMAIL_ACCESS_URI_INDEX] = Status.VOICEMAIL_ACCESS_URI;
- }
-
- /** Possible user actions. */
- public static enum Action {
- NONE(-1),
- CALL_VOICEMAIL(R.string.voicemail_status_action_call_server),
- CONFIGURE_VOICEMAIL(R.string.voicemail_status_action_configure);
-
- private final int mMessageId;
- private Action(int messageId) {
- mMessageId = messageId;
- }
-
- public int getMessageId() {
- return mMessageId;
- }
- }
-
- /**
- * Overall state of the source status. Each state is associated with the corresponding display
- * string and the corrective action. The states are also assigned a relative priority which is
- * used to order the messages from different sources.
- */
- private static enum OverallState {
- // TODO: Add separate string for call details and call log pages for the states that needs
- // to be shown in both.
- /** Both notification and data channel are not working. */
- NO_CONNECTION(0, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
- R.string.voicemail_status_audio_not_available),
- /** Notifications working, but data channel is not working. Audio cannot be downloaded. */
- NO_DATA(1, Action.CALL_VOICEMAIL, R.string.voicemail_status_voicemail_not_available,
- R.string.voicemail_status_audio_not_available),
- /** Messages are known to be waiting but data channel is not working. */
- MESSAGE_WAITING(2, Action.CALL_VOICEMAIL, R.string.voicemail_status_messages_waiting,
- R.string.voicemail_status_audio_not_available),
- /** Notification channel not working, but data channel is. */
- NO_NOTIFICATIONS(3, Action.CALL_VOICEMAIL,
- R.string.voicemail_status_voicemail_not_available),
- /** Invite user to set up voicemail. */
- INVITE_FOR_CONFIGURATION(4, Action.CONFIGURE_VOICEMAIL,
- R.string.voicemail_status_configure_voicemail),
- /**
- * No detailed notifications, but data channel is working.
- * This is normal mode of operation for certain sources. No action needed.
- */
- NO_DETAILED_NOTIFICATION(5, Action.NONE, -1),
- /** Visual voicemail not yet set up. No local action needed. */
- NOT_CONFIGURED(6, Action.NONE, -1),
- /** Everything is OK. */
- OK(7, Action.NONE, -1),
- /** If one or more state value set by the source is not valid. */
- INVALID(8, Action.NONE, -1);
-
- private final int mPriority;
- private final Action mAction;
- private final int mCallLogMessageId;
- private final int mCallDetailsMessageId;
-
- private OverallState(int priority, Action action, int callLogMessageId) {
- this(priority, action, callLogMessageId, -1);
- }
-
- private OverallState(int priority, Action action, int callLogMessageId,
- int callDetailsMessageId) {
- mPriority = priority;
- mAction = action;
- mCallLogMessageId = callLogMessageId;
- mCallDetailsMessageId = callDetailsMessageId;
- }
-
- public Action getAction() {
- return mAction;
- }
-
- public int getPriority() {
- return mPriority;
- }
-
- public int getCallLogMessageId() {
- return mCallLogMessageId;
- }
-
- public int getCallDetailsMessageId() {
- return mCallDetailsMessageId;
- }
- }
-
- /** A wrapper on {@link StatusMessage} which additionally stores the priority of the message. */
- private static class MessageStatusWithPriority {
- private final StatusMessage mMessage;
- private final int mPriority;
-
- public MessageStatusWithPriority(StatusMessage message, int priority) {
- mMessage = message;
- mPriority = priority;
- }
- }
-
- @Override
- public List<StatusMessage> getStatusMessages(Cursor cursor) {
- List<MessageStatusWithPriority> messages =
- new ArrayList<VoicemailStatusHelperImpl.MessageStatusWithPriority>();
- cursor.moveToPosition(-1);
- while(cursor.moveToNext()) {
- MessageStatusWithPriority message = getMessageForStatusEntry(cursor);
- if (message != null) {
- messages.add(message);
- }
- }
- // Finally reorder the messages by their priority.
- return reorderMessages(messages);
- }
-
- @Override
- public int getNumberActivityVoicemailSources(Cursor cursor) {
- int count = 0;
- cursor.moveToPosition(-1);
- while(cursor.moveToNext()) {
- if (isVoicemailSourceActive(cursor)) {
- ++count;
- }
- }
- return count;
- }
-
- /** Returns whether the source status in the cursor corresponds to an active source. */
- private boolean isVoicemailSourceActive(Cursor cursor) {
- return cursor.getString(SOURCE_PACKAGE_INDEX) != null
- && cursor.getInt(CONFIGURATION_STATE_INDEX) == Status.CONFIGURATION_STATE_OK;
- }
-
- private List<StatusMessage> reorderMessages(List<MessageStatusWithPriority> messageWrappers) {
- Collections.sort(messageWrappers, new Comparator<MessageStatusWithPriority>() {
- @Override
- public int compare(MessageStatusWithPriority msg1, MessageStatusWithPriority msg2) {
- return msg1.mPriority - msg2.mPriority;
- }
- });
- List<StatusMessage> reorderMessages = new ArrayList<VoicemailStatusHelper.StatusMessage>();
- // Copy the ordered message objects into the final list.
- for (MessageStatusWithPriority messageWrapper : messageWrappers) {
- reorderMessages.add(messageWrapper.mMessage);
- }
- return reorderMessages;
- }
-
- /**
- * Returns the message for the status entry pointed to by the cursor.
- */
- private MessageStatusWithPriority getMessageForStatusEntry(Cursor cursor) {
- final String sourcePackage = cursor.getString(SOURCE_PACKAGE_INDEX);
- if (sourcePackage == null) {
- return null;
- }
- final OverallState overallState = getOverallState(cursor.getInt(CONFIGURATION_STATE_INDEX),
- cursor.getInt(DATA_CHANNEL_STATE_INDEX),
- cursor.getInt(NOTIFICATION_CHANNEL_STATE_INDEX));
- final Action action = overallState.getAction();
-
- // No source package or no action, means no message shown.
- if (action == Action.NONE) {
- return null;
- }
-
- Uri actionUri = null;
- if (action == Action.CALL_VOICEMAIL) {
- actionUri = UriUtils.parseUriOrNull(cursor.getString(VOICEMAIL_ACCESS_URI_INDEX));
- // Even if actionUri is null, it is still be useful to show the notification.
- } else if (action == Action.CONFIGURE_VOICEMAIL) {
- actionUri = UriUtils.parseUriOrNull(cursor.getString(SETTINGS_URI_INDEX));
- // If there is no settings URI, there is no point in showing the notification.
- if (actionUri == null) {
- return null;
- }
- }
- return new MessageStatusWithPriority(
- new StatusMessage(sourcePackage, overallState.getCallLogMessageId(),
- overallState.getCallDetailsMessageId(), action.getMessageId(),
- actionUri),
- overallState.getPriority());
- }
-
- private OverallState getOverallState(int configurationState, int dataChannelState,
- int notificationChannelState) {
- if (configurationState == CONFIGURATION_STATE_OK) {
- // Voicemail is configured. Let's see how is the data channel.
- if (dataChannelState == DATA_CHANNEL_STATE_OK) {
- // Data channel is fine. What about notification channel?
- if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
- return OverallState.OK;
- } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
- return OverallState.NO_DETAILED_NOTIFICATION;
- } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
- return OverallState.NO_NOTIFICATIONS;
- }
- } else if (dataChannelState == DATA_CHANNEL_STATE_NO_CONNECTION) {
- // Data channel is not working. What about notification channel?
- if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_OK) {
- return OverallState.NO_DATA;
- } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING) {
- return OverallState.MESSAGE_WAITING;
- } else if (notificationChannelState == NOTIFICATION_CHANNEL_STATE_NO_CONNECTION) {
- return OverallState.NO_CONNECTION;
- }
- }
- } else if (configurationState == CONFIGURATION_STATE_CAN_BE_CONFIGURED) {
- // Voicemail not configured. data/notification channel states are irrelevant.
- return OverallState.INVITE_FOR_CONFIGURATION;
- } else if (configurationState == Status.CONFIGURATION_STATE_NOT_CONFIGURED) {
- // Voicemail not configured. data/notification channel states are irrelevant.
- return OverallState.NOT_CONFIGURED;
- }
- // Will reach here only if the source has set an invalid value.
- return OverallState.INVALID;
- }
-}
diff --git a/tests/Android.mk b/tests/Android.mk
index ef11c5e..241ff5e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -6,6 +6,7 @@
LOCAL_CERTIFICATE := shared
LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := com.android.contacts.common.test
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index d80a35d..a3dacbe 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -76,16 +76,6 @@
</intent-filter>
</activity>
- <activity android:name=".calllog.FillCallLogTestActivity"
- android:label="@string/fillCallLogTest"
- >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
-
<activity android:name=".streamitems.StreamItemPopulatorActivity"
android:label="@string/streamItemPopulator"
>
@@ -139,9 +129,4 @@
android:label="Contacts launch performance">
</instrumentation>
- <instrumentation android:name="com.android.contacts.DialerLaunchPerformance"
- android:targetPackage="com.android.contacts"
- android:label="Dialer launch performance">
- </instrumentation>
-
</manifest>
diff --git a/tests/src/com/android/contacts/CallDetailActivityTest.java b/tests/src/com/android/contacts/CallDetailActivityTest.java
deleted file mode 100644
index ae28929..0000000
--- a/tests/src/com/android/contacts/CallDetailActivityTest.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2011 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;
-
-import static com.android.contacts.CallDetailActivity.Tasks.UPDATE_PHONE_CALL_DETAILS;
-import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
-import static com.android.contacts.voicemail.VoicemailPlaybackPresenter.Tasks.PREPARE_MEDIA_PLAYER;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.res.AssetManager;
-import android.net.Uri;
-import android.provider.CallLog;
-import android.provider.VoicemailContract;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.view.Menu;
-import android.widget.TextView;
-
-import com.android.contacts.util.AsyncTaskExecutors;
-import com.android.contacts.util.FakeAsyncTaskExecutor;
-import com.android.contacts.util.IntegrationTestUtils;
-import com.android.contacts.util.LocaleTestUtils;
-import com.android.internal.view.menu.ContextMenuBuilder;
-import com.google.common.io.Closeables;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Unit tests for the {@link CallDetailActivity}.
- */
-@LargeTest
-public class CallDetailActivityTest extends ActivityInstrumentationTestCase2<CallDetailActivity> {
- private static final String TEST_ASSET_NAME = "quick_test_recording.mp3";
- private static final String MIME_TYPE = "audio/mp3";
- private static final String CONTACT_NUMBER = "+1412555555";
- private static final String VOICEMAIL_FILE_LOCATION = "/sdcard/sadlfj893w4j23o9sfu.mp3";
-
- private Uri mCallLogUri;
- private Uri mVoicemailUri;
- private IntegrationTestUtils mTestUtils;
- private LocaleTestUtils mLocaleTestUtils;
- private FakeAsyncTaskExecutor mFakeAsyncTaskExecutor;
- private CallDetailActivity mActivityUnderTest;
-
- public CallDetailActivityTest() {
- super(CallDetailActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mFakeAsyncTaskExecutor = new FakeAsyncTaskExecutor(getInstrumentation());
- AsyncTaskExecutors.setFactoryForTest(mFakeAsyncTaskExecutor.getFactory());
- // I don't like the default of focus-mode for tests, the green focus border makes the
- // screenshots look weak.
- setActivityInitialTouchMode(true);
- mTestUtils = new IntegrationTestUtils(getInstrumentation());
- // Some of the tests rely on the text that appears on screen - safest to force a
- // specific locale.
- mLocaleTestUtils = new LocaleTestUtils(getInstrumentation().getTargetContext());
- mLocaleTestUtils.setLocale(Locale.US);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mLocaleTestUtils.restoreLocale();
- mLocaleTestUtils = null;
- cleanUpUri();
- mTestUtils = null;
- AsyncTaskExecutors.setFactoryForTest(null);
- super.tearDown();
- }
-
- public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- // When the activity first starts, we will show "Fetching voicemail" on the screen.
- // The duration should not be visible.
- assertHasOneTextViewContaining("Fetching voicemail");
- assertZeroTextViewsContaining("00:00");
- }
-
- public void testWhenCheckForContentCompletes_UiShowsBuffering() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- // There is a background check that is testing to see if we have the content available.
- // Once that task completes, we shouldn't be showing the fetching message, we should
- // be showing "Buffering".
- mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- assertHasOneTextViewContaining("Buffering");
- assertZeroTextViewsContaining("Fetching voicemail");
- }
-
- public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- // There should be exactly one background task ready to prepare the media player.
- // Preparing the media player will have thrown an IOException since the file doesn't exist.
- // This should have put a failed to play message on screen, buffering is gone.
- mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
- assertHasOneTextViewContaining("Couldn't play voicemail");
- assertZeroTextViewsContaining("Buffering");
- }
-
- public void testOnResumeDoesNotCreateManyFragments() throws Throwable {
- // There was a bug where every time the activity was resumed, a new fragment was created.
- // Before the fix, this was failing reproducibly with at least 3 "Buffering" views.
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- getInstrumentation().callActivityOnPause(mActivityUnderTest);
- getInstrumentation().callActivityOnResume(mActivityUnderTest);
- getInstrumentation().callActivityOnPause(mActivityUnderTest);
- getInstrumentation().callActivityOnResume(mActivityUnderTest);
- }
- });
- assertHasOneTextViewContaining("Buffering");
- }
-
- /**
- * Test for bug where increase rate button with invalid voicemail causes a crash.
- * <p>
- * The repro steps for this crash were to open a voicemail that does not have an attachment,
- * then click the play button (which just reported an error), then after that try to adjust the
- * rate. See http://b/5047879.
- */
- public void testClickIncreaseRateButtonWithInvalidVoicemailDoesNotCrash() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
- mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
- }
-
- /** Test for bug where missing Extras on intent used to start Activity causes NPE. */
- public void testCallLogUriWithMissingExtrasShouldNotCauseNPE() throws Throwable {
- setActivityIntentForTestCallEntry();
- startActivityUnderTest();
- }
-
- /**
- * Test for bug where voicemails should not have remove-from-call-log entry.
- * <p>
- * See http://b/5054103.
- */
- public void testVoicemailDoesNotHaveRemoveFromCallLog() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- Menu menu = new ContextMenuBuilder(mActivityUnderTest);
- mActivityUnderTest.onCreateOptionsMenu(menu);
- mActivityUnderTest.onPrepareOptionsMenu(menu);
- assertFalse(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
- }
-
- /** Test to check that I haven't broken the remove-from-call-log entry from regular calls. */
- public void testRegularCallDoesHaveRemoveFromCallLog() throws Throwable {
- setActivityIntentForTestCallEntry();
- startActivityUnderTest();
- Menu menu = new ContextMenuBuilder(mActivityUnderTest);
- mActivityUnderTest.onCreateOptionsMenu(menu);
- mActivityUnderTest.onPrepareOptionsMenu(menu);
- assertTrue(menu.findItem(R.id.menu_remove_from_call_log).isVisible());
- }
-
- /**
- * Test to show that we are correctly displaying playback rate on the ui.
- * <p>
- * See bug http://b/5044075.
- */
- @Suppress
- public void testVoicemailPlaybackRateDisplayedOnUi() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
- startActivityUnderTest();
- // Find the TextView containing the duration. It should be initially displaying "00:00".
- List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, "00:00");
- assertEquals(1, views.size());
- TextView timeDisplay = views.get(0);
- // Hit the plus button. At this point we should be displaying "fast speed".
- mTestUtils.clickButton(mActivityUnderTest, R.id.rate_increase_button);
- assertEquals("fast speed", mTestUtils.getText(timeDisplay));
- // Hit the minus button. We should be back to "normal" speed.
- mTestUtils.clickButton(mActivityUnderTest, R.id.rate_decrease_button);
- assertEquals("normal speed", mTestUtils.getText(timeDisplay));
- // Wait for one and a half seconds. The timer will be back.
- Thread.sleep(1500);
- assertEquals("00:00", mTestUtils.getText(timeDisplay));
- }
-
- @Suppress
- public void testClickingCallStopsPlayback() throws Throwable {
- setActivityIntentForRealFileVoicemailEntry();
- startActivityUnderTest();
- mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
- mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
- mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
- mTestUtils.clickButton(mActivityUnderTest, R.id.call_and_sms_main_action);
- Thread.sleep(2000);
- // TODO: Suppressed the test for now, because I'm looking for an easy way to say "the audio
- // is not playing at this point", and I can't find it without doing dirty things.
- }
-
- private void setActivityIntentForTestCallEntry() {
- assertNull(mCallLogUri);
- ContentResolver contentResolver = getContentResolver();
- ContentValues values = new ContentValues();
- values.put(CallLog.Calls.NUMBER, CONTACT_NUMBER);
- values.put(CallLog.Calls.TYPE, CallLog.Calls.INCOMING_TYPE);
- mCallLogUri = contentResolver.insert(CallLog.Calls.CONTENT_URI, values);
- setActivityIntent(new Intent(Intent.ACTION_VIEW, mCallLogUri));
- }
-
- private void setActivityIntentForTestVoicemailEntry() {
- assertNull(mVoicemailUri);
- ContentResolver contentResolver = getContentResolver();
- ContentValues values = new ContentValues();
- values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
- values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
- values.put(VoicemailContract.Voicemails._DATA, VOICEMAIL_FILE_LOCATION);
- mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
- Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
- ContentUris.parseId(mVoicemailUri));
- Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
- setActivityIntent(intent);
- }
-
- private void setActivityIntentForRealFileVoicemailEntry() throws IOException {
- assertNull(mVoicemailUri);
- ContentValues values = new ContentValues();
- values.put(VoicemailContract.Voicemails.DATE, String.valueOf(System.currentTimeMillis()));
- values.put(VoicemailContract.Voicemails.NUMBER, CONTACT_NUMBER);
- values.put(VoicemailContract.Voicemails.MIME_TYPE, MIME_TYPE);
- values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
- String packageName = getInstrumentation().getTargetContext().getPackageName();
- mVoicemailUri = getContentResolver().insert(
- VoicemailContract.Voicemails.buildSourceUri(packageName), values);
- AssetManager assets = getAssets();
- OutputStream outputStream = null;
- InputStream inputStream = null;
- try {
- inputStream = assets.open(TEST_ASSET_NAME);
- outputStream = getContentResolver().openOutputStream(mVoicemailUri);
- copyBetweenStreams(inputStream, outputStream);
- } finally {
- Closeables.closeQuietly(outputStream);
- Closeables.closeQuietly(inputStream);
- }
- Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
- ContentUris.parseId(mVoicemailUri));
- Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
- intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI, mVoicemailUri);
- setActivityIntent(intent);
- }
-
- public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
- byte[] buffer = new byte[1024];
- int bytesRead;
- int total = 0;
- while ((bytesRead = in.read(buffer)) != -1) {
- total += bytesRead;
- out.write(buffer, 0, bytesRead);
- }
- }
-
- private void cleanUpUri() {
- if (mVoicemailUri != null) {
- getContentResolver().delete(VoicemailContract.Voicemails.CONTENT_URI,
- "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mVoicemailUri)) });
- mVoicemailUri = null;
- }
- if (mCallLogUri != null) {
- getContentResolver().delete(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
- "_ID = ?", new String[] { String.valueOf(ContentUris.parseId(mCallLogUri)) });
- mCallLogUri = null;
- }
- }
-
- private ContentResolver getContentResolver() {
- return getInstrumentation().getTargetContext().getContentResolver();
- }
-
- private TextView assertHasOneTextViewContaining(String text) throws Throwable {
- assertNotNull(mActivityUnderTest);
- List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, text);
- assertEquals("There should have been one TextView with text '" + text + "' but found "
- + views, 1, views.size());
- return views.get(0);
- }
-
- private void assertZeroTextViewsContaining(String text) throws Throwable {
- assertNotNull(mActivityUnderTest);
- List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, text);
- assertEquals("There should have been no TextViews with text '" + text + "' but found "
- + views, 0, views.size());
- }
-
- private void startActivityUnderTest() throws Throwable {
- assertNull(mActivityUnderTest);
- mActivityUnderTest = getActivity();
- assertNotNull("activity should not be null", mActivityUnderTest);
- // We have to run all tasks, not just one.
- // This is because it seems that we can have onResume, onPause, onResume during the course
- // of a single unit test.
- mFakeAsyncTaskExecutor.runAllTasks(UPDATE_PHONE_CALL_DETAILS);
- }
-
- private AssetManager getAssets() {
- return getInstrumentation().getContext().getAssets();
- }
-}
diff --git a/tests/src/com/android/contacts/DialerLaunchPerformance.java b/tests/src/com/android/contacts/DialerLaunchPerformance.java
deleted file mode 100644
index 0803c6b..0000000
--- a/tests/src/com/android/contacts/DialerLaunchPerformance.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.test.LaunchPerformanceBase;
-
-/**
- * Instrumentation class for Address Book launch performance testing.
- */
-public class DialerLaunchPerformance extends LaunchPerformanceBase {
-
- @Override
- public void onCreate(Bundle arguments) {
- mIntent.setAction(Intent.ACTION_MAIN);
- mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- mIntent.setComponent(new ComponentName("com.android.contacts",
- "com.android.contacts.activities.DialtactsActivity"));
-
- start();
- }
-
- /**
- * Calls LaunchApp and finish.
- */
- @Override
- public void onStart() {
- super.onStart();
- LaunchApp();
- finish(Activity.RESULT_OK, mResults);
- }
-}
diff --git a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java b/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
deleted file mode 100644
index b852b58..0000000
--- a/tests/src/com/android/contacts/PhoneCallDetailsHelperTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.provider.CallLog.Calls;
-import android.test.AndroidTestCase;
-import android.text.Html;
-import android.text.Spanned;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.contacts.calllog.CallTypeHelper;
-import com.android.contacts.calllog.PhoneNumberHelper;
-import com.android.contacts.calllog.TestPhoneNumberHelper;
-import com.android.contacts.util.LocaleTestUtils;
-import com.android.internal.telephony.CallerInfo;
-
-import java.util.GregorianCalendar;
-import java.util.Locale;
-
-/**
- * Unit tests for {@link PhoneCallDetailsHelper}.
- */
-public class PhoneCallDetailsHelperTest extends AndroidTestCase {
- /** The number to be used to access the voicemail. */
- private static final String TEST_VOICEMAIL_NUMBER = "125";
- /** The date of the call log entry. */
- private static final long TEST_DATE =
- new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis();
- /** A test duration value for phone calls. */
- private static final long TEST_DURATION = 62300;
- /** The number of the caller/callee in the log entry. */
- private static final String TEST_NUMBER = "14125555555";
- /** The formatted version of {@link #TEST_NUMBER}. */
- private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
- /** The country ISO name used in the tests. */
- private static final String TEST_COUNTRY_ISO = "US";
- /** The geocoded location used in the tests. */
- private static final String TEST_GEOCODE = "United States";
-
- /** The object under test. */
- private PhoneCallDetailsHelper mHelper;
- /** The views to fill. */
- private PhoneCallDetailsViews mViews;
- private TextView mNameView;
- private PhoneNumberHelper mPhoneNumberHelper;
- private LocaleTestUtils mLocaleTestUtils;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- Context context = getContext();
- Resources resources = context.getResources();
- CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
- mPhoneNumberHelper = new TestPhoneNumberHelper(resources, TEST_VOICEMAIL_NUMBER);
- mHelper = new PhoneCallDetailsHelper(resources, callTypeHelper, mPhoneNumberHelper);
- mHelper.setCurrentTimeForTest(
- new GregorianCalendar(2011, 5, 4, 13, 0, 0).getTimeInMillis());
- mViews = PhoneCallDetailsViews.createForTest(context);
- mNameView = new TextView(context);
- mLocaleTestUtils = new LocaleTestUtils(getContext());
- mLocaleTestUtils.setLocale(Locale.US);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mLocaleTestUtils.restoreLocale();
- mNameView = null;
- mViews = null;
- mHelper = null;
- mPhoneNumberHelper = null;
- super.tearDown();
- }
-
- public void testSetPhoneCallDetails_Unknown() {
- setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
- assertNameEqualsResource(R.string.unknown);
- }
-
- public void testSetPhoneCallDetails_Private() {
- setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
- assertNameEqualsResource(R.string.private_num);
- }
-
- public void testSetPhoneCallDetails_Payphone() {
- setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
- assertNameEqualsResource(R.string.payphone);
- }
-
- public void testSetPhoneCallDetails_Voicemail() {
- setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
- assertNameEqualsResource(R.string.voicemail);
- }
-
- public void testSetPhoneCallDetails_Normal() {
- setPhoneCallDetailsWithNumber("14125551212", "1-412-555-1212");
- assertEquals("yesterday", mViews.callTypeAndDate.getText().toString());
- assertEqualsHtml("<font color='#33b5e5'><b>yesterday</b></font>",
- mViews.callTypeAndDate.getText());
- }
-
- /** Asserts that a char sequence is actually a Spanned corresponding to the expected HTML. */
- private void assertEqualsHtml(String expectedHtml, CharSequence actualText) {
- // In order to contain HTML, the text should actually be a Spanned.
- assertTrue(actualText instanceof Spanned);
- Spanned actualSpanned = (Spanned) actualText;
- // Convert from and to HTML to take care of alternative formatting of HTML.
- assertEquals(Html.toHtml(Html.fromHtml(expectedHtml)), Html.toHtml(actualSpanned));
-
- }
-
- public void testSetPhoneCallDetails_Date() {
- mHelper.setCurrentTimeForTest(
- new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis());
-
- setPhoneCallDetailsWithDate(
- new GregorianCalendar(2011, 5, 3, 13, 0, 0).getTimeInMillis());
- assertDateEquals("0 mins ago");
-
- setPhoneCallDetailsWithDate(
- new GregorianCalendar(2011, 5, 3, 12, 0, 0).getTimeInMillis());
- assertDateEquals("1 hour ago");
-
- setPhoneCallDetailsWithDate(
- new GregorianCalendar(2011, 5, 2, 13, 0, 0).getTimeInMillis());
- assertDateEquals("yesterday");
-
- setPhoneCallDetailsWithDate(
- new GregorianCalendar(2011, 5, 1, 13, 0, 0).getTimeInMillis());
- assertDateEquals("2 days ago");
- }
-
- public void testSetPhoneCallDetails_CallTypeIcons() {
- setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE);
- assertCallTypeIconsEquals(Calls.INCOMING_TYPE);
-
- setPhoneCallDetailsWithCallTypeIcons(Calls.OUTGOING_TYPE);
- assertCallTypeIconsEquals(Calls.OUTGOING_TYPE);
-
- setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE);
- assertCallTypeIconsEquals(Calls.MISSED_TYPE);
-
- setPhoneCallDetailsWithCallTypeIcons(Calls.VOICEMAIL_TYPE);
- assertCallTypeIconsEquals(Calls.VOICEMAIL_TYPE);
- }
-
- public void testSetPhoneCallDetails_MultipleCallTypeIcons() {
- setPhoneCallDetailsWithCallTypeIcons(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
- assertCallTypeIconsEquals(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
-
- setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
- assertCallTypeIconsEquals(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
- }
-
- public void testSetPhoneCallDetails_MultipleCallTypeIconsLastOneDropped() {
- setPhoneCallDetailsWithCallTypeIcons(Calls.MISSED_TYPE, Calls.MISSED_TYPE,
- Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
- assertCallTypeIconsEqualsPlusOverflow("(4)",
- Calls.MISSED_TYPE, Calls.MISSED_TYPE, Calls.INCOMING_TYPE);
- }
-
- public void testSetPhoneCallDetails_Geocode() {
- setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", "Pennsylvania");
- assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
- assertNumberEquals("Pennsylvania"); // The geocode is shown as the number.
- }
-
- public void testSetPhoneCallDetails_NoGeocode() {
- setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", null);
- assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
- assertNumberEquals("-"); // The empty geocode is shown as the number.
- }
-
- public void testSetPhoneCallDetails_EmptyGeocode() {
- setPhoneCallDetailsWithNumberAndGeocode("+14125555555", "1-412-555-5555", "");
- assertNameEquals("1-412-555-5555"); // The phone number is shown as the name.
- assertNumberEquals("-"); // The empty geocode is shown as the number.
- }
-
- public void testSetPhoneCallDetails_NoGeocodeForVoicemail() {
- setPhoneCallDetailsWithNumberAndGeocode(TEST_VOICEMAIL_NUMBER, "", "United States");
- assertNumberEquals("-"); // The empty geocode is shown as the number.
- }
-
- public void testSetPhoneCallDetails_Highlighted() {
- setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, "");
- }
-
- public void testSetCallDetailsHeader_NumberOnly() {
- setCallDetailsHeaderWithNumberOnly(TEST_NUMBER);
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("Add to contacts", mNameView.getText().toString());
- }
-
- public void testSetCallDetailsHeader_UnknownNumber() {
- setCallDetailsHeaderWithNumberOnly(CallerInfo.UNKNOWN_NUMBER);
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("Unknown", mNameView.getText().toString());
- }
-
- public void testSetCallDetailsHeader_PrivateNumber() {
- setCallDetailsHeaderWithNumberOnly(CallerInfo.PRIVATE_NUMBER);
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("Private number", mNameView.getText().toString());
- }
-
- public void testSetCallDetailsHeader_PayphoneNumber() {
- setCallDetailsHeaderWithNumberOnly(CallerInfo.PAYPHONE_NUMBER);
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("Pay phone", mNameView.getText().toString());
- }
-
- public void testSetCallDetailsHeader_VoicemailNumber() {
- setCallDetailsHeaderWithNumberOnly(TEST_VOICEMAIL_NUMBER);
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("Voicemail", mNameView.getText().toString());
- }
-
- public void testSetCallDetailsHeader() {
- setCallDetailsHeader("John Doe");
- assertEquals(View.VISIBLE, mNameView.getVisibility());
- assertEquals("John Doe", mNameView.getText().toString());
- }
-
- /** Asserts that the name text field contains the value of the given string resource. */
- private void assertNameEqualsResource(int resId) {
- assertNameEquals(getContext().getString(resId));
- }
-
- /** Asserts that the name text field contains the given string value. */
- private void assertNameEquals(String text) {
- assertEquals(text, mViews.nameView.getText().toString());
- }
-
- /** Asserts that the number text field contains the given string value. */
- private void assertNumberEquals(String text) {
- assertEquals(text, mViews.numberView.getText().toString());
- }
-
- /** Asserts that the date text field contains the given string value. */
- private void assertDateEquals(String text) {
- assertEquals(text, mViews.callTypeAndDate.getText().toString());
- }
-
- /** Asserts that the call type contains the images with the given drawables. */
- private void assertCallTypeIconsEquals(int... ids) {
- assertEquals(ids.length, mViews.callTypeIcons.getCount());
- for (int index = 0; index < ids.length; ++index) {
- int id = ids[index];
- assertEquals(id, mViews.callTypeIcons.getCallType(index));
- }
- assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
- assertEquals("yesterday", mViews.callTypeAndDate.getText().toString());
- }
-
- /**
- * Asserts that the call type contains the images with the given drawables and shows the given
- * text next to the icons.
- */
- private void assertCallTypeIconsEqualsPlusOverflow(String overflowText, int... ids) {
- assertEquals(ids.length, mViews.callTypeIcons.getCount());
- for (int index = 0; index < ids.length; ++index) {
- int id = ids[index];
- assertEquals(id, mViews.callTypeIcons.getCallType(index));
- }
- assertEquals(View.VISIBLE, mViews.callTypeIcons.getVisibility());
- assertEquals(overflowText + " yesterday", mViews.callTypeAndDate.getText().toString());
- }
-
- /** Sets the phone call details with default values and the given number. */
- private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
- setPhoneCallDetailsWithNumberAndGeocode(number, formattedNumber, TEST_GEOCODE);
- }
-
- /** Sets the phone call details with default values and the given number. */
- private void setPhoneCallDetailsWithNumberAndGeocode(String number, String formattedNumber,
- String geocodedLocation) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, geocodedLocation,
- new int[]{ Calls.VOICEMAIL_TYPE }, TEST_DATE, TEST_DURATION),
- true);
- }
-
- /** Sets the phone call details with default values and the given date. */
- private void setPhoneCallDetailsWithDate(long date) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, date, TEST_DURATION),
- false);
- }
-
- /** Sets the phone call details with default values and the given call types using icons. */
- private void setPhoneCallDetailsWithCallTypeIcons(int... callTypes) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, callTypes, TEST_DATE, TEST_DURATION),
- false);
- }
-
- private void setCallDetailsHeaderWithNumberOnly(String number) {
- mHelper.setCallDetailsHeader(mNameView,
- new PhoneCallDetails(number, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION));
- }
-
- private void setCallDetailsHeader(String name) {
- mHelper.setCallDetailsHeader(mNameView,
- new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, new int[]{ Calls.INCOMING_TYPE }, TEST_DATE, TEST_DURATION,
- name, 0, "", null, null));
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java b/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
deleted file mode 100644
index 5bc31f9..0000000
--- a/tests/src/com/android/contacts/calllog/CallLogAdapterTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.database.MatrixCursor;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.View;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * Unit tests for {@link CallLogAdapter}.
- */
-@SmallTest
-public class CallLogAdapterTest extends AndroidTestCase {
- private static final String TEST_NUMBER = "12345678";
- private static final String TEST_NAME = "name";
- private static final String TEST_NUMBER_LABEL = "label";
- private static final int TEST_NUMBER_TYPE = 1;
- private static final String TEST_COUNTRY_ISO = "US";
-
- /** The object under test. */
- private TestCallLogAdapter mAdapter;
-
- private MatrixCursor mCursor;
- private View mView;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- // Use a call fetcher that does not do anything.
- CallLogAdapter.CallFetcher fakeCallFetcher = new CallLogAdapter.CallFetcher() {
- @Override
- public void fetchCalls() {}
- };
-
- ContactInfoHelper fakeContactInfoHelper =
- new ContactInfoHelper(getContext(), TEST_COUNTRY_ISO) {
- @Override
- public ContactInfo lookupNumber(String number, String countryIso) {
- ContactInfo info = new ContactInfo();
- info.number = number;
- info.formattedNumber = number;
- return info;
- }
- };
-
- mAdapter = new TestCallLogAdapter(getContext(), fakeCallFetcher, fakeContactInfoHelper);
- // The cursor used in the tests to store the entries to display.
- mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
- mCursor.moveToFirst();
- // The views into which to store the data.
- mView = new View(getContext());
- mView.setTag(CallLogListItemViews.createForTest(getContext()));
- }
-
- @Override
- protected void tearDown() throws Exception {
- mAdapter = null;
- mCursor = null;
- mView = null;
- super.tearDown();
- }
-
- public void testBindView_NoCallLogCacheNorMemoryCache_EnqueueRequest() {
- mCursor.addRow(createCallLogEntry());
-
- // Bind the views of a single row.
- mAdapter.bindStandAloneView(mView, getContext(), mCursor);
-
- // There is one request for contact details.
- assertEquals(1, mAdapter.requests.size());
-
- TestCallLogAdapter.Request request = mAdapter.requests.get(0);
- // It is for the number we need to show.
- assertEquals(TEST_NUMBER, request.number);
- // It has the right country.
- assertEquals(TEST_COUNTRY_ISO, request.countryIso);
- // Since there is nothing in the cache, it is an immediate request.
- assertTrue("should be immediate", request.immediate);
- }
-
- public void testBindView_CallLogCacheButNoMemoryCache_EnqueueRequest() {
- mCursor.addRow(createCallLogEntryWithCachedValues());
-
- // Bind the views of a single row.
- mAdapter.bindStandAloneView(mView, getContext(), mCursor);
-
- // There is one request for contact details.
- assertEquals(1, mAdapter.requests.size());
-
- TestCallLogAdapter.Request request = mAdapter.requests.get(0);
- // The values passed to the request, match the ones in the call log cache.
- assertEquals(TEST_NAME, request.callLogInfo.name);
- assertEquals(1, request.callLogInfo.type);
- assertEquals(TEST_NUMBER_LABEL, request.callLogInfo.label);
- }
-
-
- public void testBindView_NoCallLogButMemoryCache_EnqueueRequest() {
- mCursor.addRow(createCallLogEntry());
- mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
-
- // Bind the views of a single row.
- mAdapter.bindStandAloneView(mView, getContext(), mCursor);
-
- // There is one request for contact details.
- assertEquals(1, mAdapter.requests.size());
-
- TestCallLogAdapter.Request request = mAdapter.requests.get(0);
- // Since there is something in the cache, it is not an immediate request.
- assertFalse("should not be immediate", request.immediate);
- }
-
- public void testBindView_BothCallLogAndMemoryCache_NoEnqueueRequest() {
- mCursor.addRow(createCallLogEntryWithCachedValues());
- mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, createContactInfo());
-
- // Bind the views of a single row.
- mAdapter.bindStandAloneView(mView, getContext(), mCursor);
-
- // Cache and call log are up-to-date: no need to request update.
- assertEquals(0, mAdapter.requests.size());
- }
-
- public void testBindView_MismatchBetwenCallLogAndMemoryCache_EnqueueRequest() {
- mCursor.addRow(createCallLogEntryWithCachedValues());
-
- // Contact info contains a different name.
- ContactInfo info = createContactInfo();
- info.name = "new name";
- mAdapter.injectContactInfoForTest(TEST_NUMBER, TEST_COUNTRY_ISO, info);
-
- // Bind the views of a single row.
- mAdapter.bindStandAloneView(mView, getContext(), mCursor);
-
- // There is one request for contact details.
- assertEquals(1, mAdapter.requests.size());
-
- TestCallLogAdapter.Request request = mAdapter.requests.get(0);
- // Since there is something in the cache, it is not an immediate request.
- assertFalse("should not be immediate", request.immediate);
- }
-
- /** Returns a contact info with default values. */
- private ContactInfo createContactInfo() {
- ContactInfo info = new ContactInfo();
- info.number = TEST_NUMBER;
- info.name = TEST_NAME;
- info.type = TEST_NUMBER_TYPE;
- info.label = TEST_NUMBER_LABEL;
- return info;
- }
-
- /** Returns a call log entry without cached values. */
- private Object[] createCallLogEntry() {
- Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
- values[CallLogQuery.NUMBER] = TEST_NUMBER;
- values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
- return values;
- }
-
- /** Returns a call log entry with a cached values. */
- private Object[] createCallLogEntryWithCachedValues() {
- Object[] values = createCallLogEntry();
- values[CallLogQuery.CACHED_NAME] = TEST_NAME;
- values[CallLogQuery.CACHED_NUMBER_TYPE] = TEST_NUMBER_TYPE;
- values[CallLogQuery.CACHED_NUMBER_LABEL] = TEST_NUMBER_LABEL;
- return values;
- }
-
- /**
- * Subclass of {@link CallLogAdapter} used in tests to intercept certain calls.
- */
- // TODO: This would be better done by splitting the contact lookup into a collaborator class
- // instead.
- private static final class TestCallLogAdapter extends CallLogAdapter {
- public static class Request {
- public final String number;
- public final String countryIso;
- public final ContactInfo callLogInfo;
- public final boolean immediate;
-
- public Request(String number, String countryIso, ContactInfo callLogInfo,
- boolean immediate) {
- this.number = number;
- this.countryIso = countryIso;
- this.callLogInfo = callLogInfo;
- this.immediate = immediate;
- }
- }
-
- public final List<Request> requests = Lists.newArrayList();
-
- public TestCallLogAdapter(Context context, CallFetcher callFetcher,
- ContactInfoHelper contactInfoHelper) {
- super(context, callFetcher, contactInfoHelper);
- }
-
- @Override
- void enqueueRequest(String number, String countryIso, ContactInfo callLogInfo,
- boolean immediate) {
- requests.add(new Request(number, countryIso, callLogInfo, immediate));
- }
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java b/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java
deleted file mode 100644
index 0eaca60..0000000
--- a/tests/src/com/android/contacts/calllog/CallLogFragmentTest.java
+++ /dev/null
@@ -1,632 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts.calllog;
-
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.ComponentName;
-import android.content.ContentUris;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.MatrixCursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.VoicemailContract;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import com.android.contacts.CallDetailActivity;
-import com.android.contacts.R;
-import com.android.contacts.test.FragmentTestActivity;
-import com.android.internal.telephony.CallerInfo;
-
-import java.util.Date;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.Random;
-
-/**
- * Tests for the contact call list activity.
- *
- * Running all tests:
- *
- * runtest contacts
- * or
- * adb shell am instrument \
- * -w com.android.contacts.tests/android.test.InstrumentationTestRunner
- */
-@LargeTest
-public class CallLogFragmentTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
- private static final int RAND_DURATION = -1;
- private static final long NOW = -1L;
-
- /** A test value for the URI of a contact. */
- private static final Uri TEST_LOOKUP_URI = Uri.parse("content://contacts/2");
- /** A test value for the country ISO of the phone number in the call log. */
- private static final String TEST_COUNTRY_ISO = "US";
- /** A phone number to be used in tests. */
- private static final String TEST_NUMBER = "12125551000";
- /** The formatted version of {@link #TEST_NUMBER}. */
- private static final String TEST_FORMATTED_NUMBER = "1 212-555-1000";
-
- /** The activity in which we are hosting the fragment. */
- private FragmentTestActivity mActivity;
- private CallLogFragment mFragment;
- private FrameLayout mParentView;
- /**
- * The adapter used by the fragment to build the rows in the call log. We use it with our own in
- * memory database.
- */
- private CallLogAdapter mAdapter;
- private String mVoicemail;
-
- // In memory array to hold the rows corresponding to the 'calls' table.
- private MatrixCursor mCursor;
- private int mIndex; // Of the next row.
-
- private Random mRnd;
-
- // References to the icons bitmaps used to build the list are stored in a
- // map mIcons. The keys to retrieve the icons are:
- // Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE and Calls.MISSED_TYPE.
- private HashMap<Integer, Bitmap> mCallTypeIcons;
-
- // An item in the call list. All the methods performing checks use it.
- private CallLogListItemViews mItem;
- // The list of views representing the data in the DB. View are in
- // reverse order compare to the DB.
- private View[] mList;
-
- public CallLogFragmentTest() {
- super("com.android.contacts", FragmentTestActivity.class);
- mIndex = 1;
- mRnd = new Random();
- }
-
- @Override
- public void setUp() {
- mActivity = getActivity();
- // Needed by the CallLogFragment.
- mActivity.setTheme(R.style.DialtactsTheme);
-
- // Create the fragment and load it into the activity.
- mFragment = new CallLogFragment();
- FragmentManager fragmentManager = mActivity.getFragmentManager();
- FragmentTransaction transaction = fragmentManager.beginTransaction();
- transaction.add(R.id.fragment, mFragment);
- transaction.commit();
- // Wait for the fragment to be loaded.
- getInstrumentation().waitForIdleSync();
-
- mVoicemail = TelephonyManager.getDefault().getVoiceMailNumber();
- mAdapter = mFragment.getAdapter();
- // Do not process requests for details during tests. This would start a background thread,
- // which makes the tests flaky.
- mAdapter.disableRequestProcessingForTest();
- mAdapter.stopRequestProcessing();
- mParentView = new FrameLayout(mActivity);
- mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
- buildIconMap();
- }
-
- /**
- * Checks that the call icon is not visible for private and
- * unknown numbers.
- * Use 2 passes, one where new views are created and one where
- * half of the total views are updated and the other half created.
- */
- @MediumTest
- public void testCallViewIsNotVisibleForPrivateAndUnknownNumbers() {
- final int SIZE = 100;
- mList = new View[SIZE];
-
- // Insert the first batch of entries.
- mCursor.moveToFirst();
- insertRandomEntries(SIZE / 2);
- int startOfSecondBatch = mCursor.getPosition();
-
- buildViewListFromDb();
- checkCallStatus();
-
- // Append the rest of the entries. We keep the first set of
- // views around so they get updated and not built from
- // scratch, this exposes some bugs that are not there when the
- // call log is launched for the 1st time but show up when the
- // call log gets updated afterwards.
- mCursor.move(startOfSecondBatch);
- insertRandomEntries(SIZE / 2);
-
- buildViewListFromDb();
- checkCallStatus();
- }
-
- @MediumTest
- public void testCallAndGroupViews_GroupView() {
- mCursor.moveToFirst();
- insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newGroupView(getActivity(), mParentView);
- mAdapter.bindGroupView(view, getActivity(), mCursor, 3, false);
- assertNotNull(view.findViewById(R.id.secondary_action_icon));
- }
-
- @MediumTest
- public void testCallAndGroupViews_StandAloneView() {
- mCursor.moveToFirst();
- insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
- assertNotNull(view.findViewById(R.id.secondary_action_icon));
- }
-
- @MediumTest
- public void testCallAndGroupViews_ChildView() {
- mCursor.moveToFirst();
- insert(CallerInfo.PRIVATE_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newChildView(getActivity(), mParentView);
- mAdapter.bindChildView(view, getActivity(), mCursor);
- assertNotNull(view.findViewById(R.id.secondary_action_icon));
- }
-
- @MediumTest
- public void testBindView_NumberOnlyNoCache() {
- mCursor.moveToFirst();
- insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, TEST_NUMBER);
- }
-
- @MediumTest
- public void testBindView_NumberOnlyDbCachedFormattedNumber() {
- mCursor.moveToFirst();
- Object[] values = getValuesToInsert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- values[CallLogQuery.CACHED_FORMATTED_NUMBER] = TEST_FORMATTED_NUMBER;
- insertValues(values);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, TEST_FORMATTED_NUMBER);
- }
-
- @MediumTest
- public void testBindView_WithCachedName() {
- mCursor.moveToFirst();
- insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_HOME, "");
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, "John Doe");
- assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
- }
-
- @MediumTest
- public void testBindView_UriNumber() {
- mCursor.moveToFirst();
- insertWithCachedValues("sip:johndoe@gmail.com", NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_HOME, "");
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, "John Doe");
- assertNumberAndLabelAre(views, "sip:johndoe@gmail.com", null);
- }
-
- @MediumTest
- public void testBindView_HomeLabel() {
- mCursor.moveToFirst();
- insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_HOME, "");
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, "John Doe");
- assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_HOME));
- }
-
- @MediumTest
- public void testBindView_WorkLabel() {
- mCursor.moveToFirst();
- insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_WORK, "");
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, "John Doe");
- assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, getTypeLabel(Phone.TYPE_WORK));
- }
-
- @MediumTest
- public void testBindView_CustomLabel() {
- mCursor.moveToFirst();
- String numberLabel = "My label";
- insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_CUSTOM, numberLabel);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertNameIs(views, "John Doe");
- assertNumberAndLabelAre(views, TEST_FORMATTED_NUMBER, numberLabel);
- }
-
- @MediumTest
- public void testBindView_WithQuickContactBadge() {
- mCursor.moveToFirst();
- insertWithCachedValues(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE,
- "John Doe", Phone.TYPE_HOME, "");
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertTrue(views.quickContactView.isEnabled());
- }
-
- @MediumTest
- public void testBindView_WithoutQuickContactBadge() {
- mCursor.moveToFirst();
- insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- assertFalse(views.quickContactView.isEnabled());
- }
-
- @MediumTest
- public void testBindView_CallButton() {
- mCursor.moveToFirst();
- insert(TEST_NUMBER, NOW, 0, Calls.INCOMING_TYPE);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
- Intent intent = intentProvider.getIntent(mActivity);
- // Starts a call.
- assertEquals(Intent.ACTION_CALL_PRIVILEGED, intent.getAction());
- // To the entry's number.
- assertEquals(Uri.parse("tel:" + TEST_NUMBER), intent.getData());
- }
-
- @MediumTest
- public void testBindView_PlayButton() {
- mCursor.moveToFirst();
- insertVoicemail(TEST_NUMBER, NOW, 0);
- View view = mAdapter.newStandAloneView(getActivity(), mParentView);
- mAdapter.bindStandAloneView(view, getActivity(), mCursor);
-
- CallLogListItemViews views = (CallLogListItemViews) view.getTag();
- IntentProvider intentProvider = (IntentProvider) views.secondaryActionView.getTag();
- Intent intent = intentProvider.getIntent(mActivity);
- // Starts the call detail activity.
- assertEquals(new ComponentName(mActivity, CallDetailActivity.class),
- intent.getComponent());
- // With the given entry.
- assertEquals(ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, 1),
- intent.getData());
- // With the URI of the voicemail.
- assertEquals(
- ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, 1),
- intent.getParcelableExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI));
- // And starts playback.
- assertTrue(
- intent.getBooleanExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false));
- }
-
- /** Returns the label associated with a given phone type. */
- private CharSequence getTypeLabel(int phoneType) {
- return Phone.getTypeLabel(getActivity().getResources(), phoneType, "");
- }
-
- //
- // HELPERS to check conditions on the DB/views
- //
- /**
- * Go over all the views in the list and check that the Call
- * icon's visibility matches the nature of the number.
- */
- private void checkCallStatus() {
- for (int i = 0; i < mList.length; i++) {
- if (null == mList[i]) {
- break;
- }
- mItem = (CallLogListItemViews) mList[i].getTag();
- String number = getPhoneNumberForListEntry(i);
- if (CallerInfo.PRIVATE_NUMBER.equals(number) ||
- CallerInfo.UNKNOWN_NUMBER.equals(number)) {
- assertFalse(View.VISIBLE == mItem.secondaryActionView.getVisibility());
- } else {
- assertEquals(View.VISIBLE, mItem.secondaryActionView.getVisibility());
- }
- }
- }
-
-
- //
- // HELPERS to setup the tests.
- //
-
- /**
- * Get the Bitmap from the icons in the contacts package.
- */
- private Bitmap getBitmap(String resName) {
- Resources r = mActivity.getResources();
- int resid = r.getIdentifier(resName, "drawable", "com.android.contacts");
- BitmapDrawable d = (BitmapDrawable) r.getDrawable(resid);
- assertNotNull(d);
- return d.getBitmap();
- }
-
- /**
- * Fetch all the icons we need in tests from the contacts app and store them in a map.
- */
- private void buildIconMap() {
- mCallTypeIcons = new HashMap<Integer, Bitmap>(3);
-
- mCallTypeIcons.put(Calls.INCOMING_TYPE, getBitmap("ic_call_incoming_holo_dark"));
- mCallTypeIcons.put(Calls.MISSED_TYPE, getBitmap("ic_call_missed_holo_dark"));
- mCallTypeIcons.put(Calls.OUTGOING_TYPE, getBitmap("ic_call_outgoing_holo_dark"));
- }
-
- //
- // HELPERS to build/update the call entries (views) from the DB.
- //
-
- /**
- * Read the DB and foreach call either update the existing view if
- * one exists already otherwise create one.
- * The list is build from a DESC view of the DB (last inserted entry is first).
- */
- private void buildViewListFromDb() {
- int i = 0;
- mCursor.moveToLast();
- while(!mCursor.isBeforeFirst()) {
- if (null == mList[i]) {
- mList[i] = mAdapter.newStandAloneView(mActivity, mParentView);
- }
- mAdapter.bindStandAloneView(mList[i], mActivity, mCursor);
- mCursor.moveToPrevious();
- i++;
- }
- }
-
- /** Returns the number associated with the given entry in {{@link #mList}. */
- private String getPhoneNumberForListEntry(int index) {
- // The entries are added backward, so count from the end of the cursor.
- mCursor.moveToPosition(mCursor.getCount() - index - 1);
- return mCursor.getString(CallLogQuery.NUMBER);
- }
-
- //
- // HELPERS to insert numbers in the call log DB.
- //
-
- /**
- * Insert a certain number of random numbers in the DB. Makes sure
- * there is at least one private and one unknown number in the DB.
- * @param num Of entries to be inserted.
- */
- private void insertRandomEntries(int num) {
- if (num < 10) {
- throw new IllegalArgumentException("num should be >= 10");
- }
- boolean privateOrUnknownOrVm[];
- privateOrUnknownOrVm = insertRandomRange(0, num - 2);
-
- if (privateOrUnknownOrVm[0] && privateOrUnknownOrVm[1]) {
- insertRandomRange(num - 2, num);
- } else {
- insertPrivate(NOW, RAND_DURATION);
- insertUnknown(NOW, RAND_DURATION);
- }
- }
-
- /**
- * Insert a new call entry in the test DB.
- *
- * It includes the values for the cached contact associated with the number.
- *
- * @param number The phone number. For unknown and private numbers,
- * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
- * @param cachedName the name of the contact with this number
- * @param cachedNumberType the type of the number, from the contact with this number
- * @param cachedNumberLabel the label of the number, from the contact with this number
- */
- private void insertWithCachedValues(String number, long date, int duration, int type,
- String cachedName, int cachedNumberType, String cachedNumberLabel) {
- insert(number, date, duration, type);
- ContactInfo contactInfo = new ContactInfo();
- contactInfo.lookupUri = TEST_LOOKUP_URI;
- contactInfo.name = cachedName;
- contactInfo.type = cachedNumberType;
- contactInfo.label = cachedNumberLabel;
- String formattedNumber = PhoneNumberUtils.formatNumber(number, TEST_COUNTRY_ISO);
- if (formattedNumber == null) {
- formattedNumber = number;
- }
- contactInfo.formattedNumber = formattedNumber;
- contactInfo.normalizedNumber = number;
- contactInfo.photoId = 0;
- mAdapter.injectContactInfoForTest(number, TEST_COUNTRY_ISO, contactInfo);
- }
-
- /**
- * Insert a new call entry in the test DB.
- * @param number The phone number. For unknown and private numbers,
- * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
- */
- private void insert(String number, long date, int duration, int type) {
- insertValues(getValuesToInsert(number, date, duration, type));
- }
-
- /** Inserts the given values in the cursor. */
- private void insertValues(Object[] values) {
- mCursor.addRow(values);
- ++mIndex;
- }
-
- /**
- * Returns the values for a new call entry.
- *
- * @param number The phone number. For unknown and private numbers,
- * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- * @param type Either Call.OUTGOING_TYPE or Call.INCOMING_TYPE or Call.MISSED_TYPE.
- */
- private Object[] getValuesToInsert(String number, long date, int duration, int type) {
- Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
- values[CallLogQuery.ID] = mIndex;
- values[CallLogQuery.NUMBER] = number;
- values[CallLogQuery.DATE] = date == NOW ? new Date().getTime() : date;
- values[CallLogQuery.DURATION] = duration < 0 ? mRnd.nextInt(10 * 60) : duration;
- if (mVoicemail != null && mVoicemail.equals(number)) {
- assertEquals(Calls.OUTGOING_TYPE, type);
- }
- values[CallLogQuery.CALL_TYPE] = type;
- values[CallLogQuery.COUNTRY_ISO] = TEST_COUNTRY_ISO;
- values[CallLogQuery.SECTION] = CallLogQuery.SECTION_OLD_ITEM;
- return values;
- }
-
- /**
- * Insert a new voicemail entry in the test DB.
- * @param number The phone number. For unknown and private numbers,
- * use CallerInfo.UNKNOWN_NUMBER or CallerInfo.PRIVATE_NUMBER.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- */
- private void insertVoicemail(String number, long date, int duration) {
- Object[] values = getValuesToInsert(number, date, duration, Calls.VOICEMAIL_TYPE);
- // Must have the same index as the row.
- values[CallLogQuery.VOICEMAIL_URI] =
- ContentUris.withAppendedId(VoicemailContract.Voicemails.CONTENT_URI, mIndex);
- insertValues(values);
- }
-
- /**
- * Insert a new private call entry in the test DB.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- */
- private void insertPrivate(long date, int duration) {
- insert(CallerInfo.PRIVATE_NUMBER, date, duration, Calls.INCOMING_TYPE);
- }
-
- /**
- * Insert a new unknown call entry in the test DB.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- */
- private void insertUnknown(long date, int duration) {
- insert(CallerInfo.UNKNOWN_NUMBER, date, duration, Calls.INCOMING_TYPE);
- }
-
- /**
- * Insert a new call to voicemail entry in the test DB.
- * @param date In millisec since epoch. Use NOW to use the current time.
- * @param duration In seconds of the call. Use RAND_DURATION to pick a random one.
- */
- private void insertCalltoVoicemail(long date, int duration) {
- // mVoicemail may be null
- if (mVoicemail != null) {
- insert(mVoicemail, date, duration, Calls.OUTGOING_TYPE);
- }
- }
-
- /**
- * Insert a range [start, end) of random numbers in the DB. For
- * each row, there is a 1/10 probability that the number will be
- * marked as PRIVATE or UNKNOWN or VOICEMAIL. For regular numbers, a number is
- * inserted, its last 4 digits will be the number of the iteration
- * in the range.
- * @param start Of the range.
- * @param end Of the range (excluded).
- * @return An array with 2 booleans [0 = private number, 1 =
- * unknown number, 2 = voicemail] to indicate if at least one
- * private or unknown or voicemail number has been inserted. Since
- * the numbers are random some tests may want to enforce the
- * insertion of such numbers.
- */
- // TODO: Should insert numbers with contact entries too.
- private boolean[] insertRandomRange(int start, int end) {
- boolean[] privateOrUnknownOrVm = new boolean[] {false, false, false};
-
- for (int i = start; i < end; i++ ) {
- int type = mRnd.nextInt(10);
-
- if (0 == type) {
- insertPrivate(NOW, RAND_DURATION);
- privateOrUnknownOrVm[0] = true;
- } else if (1 == type) {
- insertUnknown(NOW, RAND_DURATION);
- privateOrUnknownOrVm[1] = true;
- } else if (2 == type) {
- insertCalltoVoicemail(NOW, RAND_DURATION);
- privateOrUnknownOrVm[2] = true;
- } else {
- int inout = mRnd.nextBoolean() ? Calls.OUTGOING_TYPE : Calls.INCOMING_TYPE;
- String number = new Formatter().format("1800123%04d", i).toString();
- insert(number, NOW, RAND_DURATION, inout);
- }
- }
- return privateOrUnknownOrVm;
- }
-
- /** Asserts that the name text view is shown and contains the given text. */
- private void assertNameIs(CallLogListItemViews views, String name) {
- assertEquals(View.VISIBLE, views.phoneCallDetailsViews.nameView.getVisibility());
- assertEquals(name, views.phoneCallDetailsViews.nameView.getText());
- }
-
- /** Asserts that the number and label text view contains the given text. */
- private void assertNumberAndLabelAre(CallLogListItemViews views, CharSequence number,
- CharSequence label) {
- assertEquals(View.VISIBLE, views.phoneCallDetailsViews.numberView.getVisibility());
- assertEquals(number, views.phoneCallDetailsViews.numberView.getText().toString());
-
- assertEquals(label == null ? View.GONE : View.VISIBLE,
- views.phoneCallDetailsViews.labelView.getVisibility());
- if (label != null) {
- assertEquals(label, views.phoneCallDetailsViews.labelView.getText().toString());
- }
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
deleted file mode 100644
index 1fced0b..0000000
--- a/tests/src/com/android/contacts/calllog/CallLogGroupBuilderTest.java
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-import android.database.MatrixCursor;
-import android.provider.CallLog.Calls;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.util.List;
-
-/**
- * Unit tests for {@link CallLogGroupBuilder}
- */
-@SmallTest
-public class CallLogGroupBuilderTest extends AndroidTestCase {
- /** A phone number for testing. */
- private static final String TEST_NUMBER1 = "14125551234";
- /** A phone number for testing. */
- private static final String TEST_NUMBER2 = "14125555555";
-
- /** The object under test. */
- private CallLogGroupBuilder mBuilder;
- /** Records the created groups. */
- private FakeGroupCreator mFakeGroupCreator;
- /** Cursor to store the values. */
- private MatrixCursor mCursor;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mFakeGroupCreator = new FakeGroupCreator();
- mBuilder = new CallLogGroupBuilder(mFakeGroupCreator);
- createCursor();
- }
-
- @Override
- protected void tearDown() throws Exception {
- mCursor = null;
- mBuilder = null;
- mFakeGroupCreator = null;
- super.tearDown();
- }
-
- public void testAddGroups_NoCalls() {
- mBuilder.addGroups(mCursor);
- assertEquals(0, mFakeGroupCreator.groups.size());
- }
-
- public void testAddGroups_OneCall() {
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(0, mFakeGroupCreator.groups.size());
- }
-
- public void testAddGroups_TwoCallsNotMatching() {
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogEntry(TEST_NUMBER2, Calls.INCOMING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(0, mFakeGroupCreator.groups.size());
- }
-
- public void testAddGroups_ThreeCallsMatching() {
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(1, mFakeGroupCreator.groups.size());
- assertGroupIs(0, 3, false, mFakeGroupCreator.groups.get(0));
- }
-
- public void testAddGroups_MatchingIncomingAndOutgoing() {
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogEntry(TEST_NUMBER1, Calls.OUTGOING_TYPE);
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(1, mFakeGroupCreator.groups.size());
- assertGroupIs(0, 3, false, mFakeGroupCreator.groups.get(0));
- }
-
- public void testAddGroups_HeaderSplitsGroups() {
- addNewCallLogHeader();
- addNewCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addNewCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogHeader();
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- addOldCallLogEntry(TEST_NUMBER1, Calls.INCOMING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(2, mFakeGroupCreator.groups.size());
- assertGroupIs(1, 2, false, mFakeGroupCreator.groups.get(0));
- assertGroupIs(4, 2, false, mFakeGroupCreator.groups.get(1));
- }
-
- public void testAddGroups_Voicemail() {
- // Does not group with other types of calls, include voicemail themselves.
- assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.MISSED_TYPE);
- //assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.MISSED_TYPE, Calls.MISSED_TYPE);
- assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.VOICEMAIL_TYPE);
- assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreNotGrouped(Calls.VOICEMAIL_TYPE, Calls.OUTGOING_TYPE);
- }
-
- public void testAddGroups_Missed() {
- // Groups with one or more missed calls.
- assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.MISSED_TYPE);
- assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.MISSED_TYPE, Calls.MISSED_TYPE);
- // Does not group with other types of calls.
- assertCallsAreNotGrouped(Calls.MISSED_TYPE, Calls.VOICEMAIL_TYPE);
- assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreGrouped(Calls.MISSED_TYPE, Calls.OUTGOING_TYPE);
- }
-
- public void testAddGroups_Incoming() {
- // Groups with one or more incoming or outgoing.
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.MISSED_TYPE);
- // Does not group with voicemail and missed calls.
- assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
- }
-
- public void testAddGroups_Outgoing() {
- // Groups with one or more incoming or outgoing.
- assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE);
- assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE);
- assertCallsAreGrouped(Calls.OUTGOING_TYPE, Calls.OUTGOING_TYPE, Calls.INCOMING_TYPE);
- assertCallsAreGrouped(Calls.INCOMING_TYPE, Calls.MISSED_TYPE);
- // Does not group with voicemail and missed calls.
- assertCallsAreNotGrouped(Calls.INCOMING_TYPE, Calls.VOICEMAIL_TYPE);
- }
-
- public void testAddGroups_Mixed() {
- addMultipleOldCallLogEntries(TEST_NUMBER1,
- Calls.VOICEMAIL_TYPE, // Stand-alone
- Calls.INCOMING_TYPE, // Group 1: 1-4
- Calls.OUTGOING_TYPE,
- Calls.MISSED_TYPE,
- Calls.MISSED_TYPE,
- Calls.VOICEMAIL_TYPE, // Stand-alone
- Calls.INCOMING_TYPE, // Stand-alone
- Calls.VOICEMAIL_TYPE, // Stand-alone
- Calls.MISSED_TYPE, // Group 2: 8-10
- Calls.MISSED_TYPE,
- Calls.OUTGOING_TYPE);
- mBuilder.addGroups(mCursor);
- assertEquals(2, mFakeGroupCreator.groups.size());
- assertGroupIs(1, 4, false, mFakeGroupCreator.groups.get(0));
- assertGroupIs(8, 3, false, mFakeGroupCreator.groups.get(1));
- }
-
- public void testEqualPhoneNumbers() {
- // Identical.
- assertTrue(mBuilder.equalNumbers("6505555555", "6505555555"));
- assertTrue(mBuilder.equalNumbers("650 555 5555", "650 555 5555"));
- // Formatting.
- assertTrue(mBuilder.equalNumbers("6505555555", "650 555 5555"));
- assertTrue(mBuilder.equalNumbers("6505555555", "(650) 555-5555"));
- assertTrue(mBuilder.equalNumbers("650 555 5555", "(650) 555-5555"));
- // Short codes.
- assertTrue(mBuilder.equalNumbers("55555", "55555"));
- assertTrue(mBuilder.equalNumbers("55555", "555 55"));
- // Different numbers.
- assertFalse(mBuilder.equalNumbers("6505555555", "650555555"));
- assertFalse(mBuilder.equalNumbers("6505555555", "6505555551"));
- assertFalse(mBuilder.equalNumbers("650 555 5555", "650 555 555"));
- assertFalse(mBuilder.equalNumbers("650 555 5555", "650 555 5551"));
- assertFalse(mBuilder.equalNumbers("55555", "5555"));
- assertFalse(mBuilder.equalNumbers("55555", "55551"));
- // SIP addresses.
- assertTrue(mBuilder.equalNumbers("6505555555@host.com", "6505555555@host.com"));
- assertTrue(mBuilder.equalNumbers("6505555555@host.com", "6505555555@HOST.COM"));
- assertTrue(mBuilder.equalNumbers("user@host.com", "user@host.com"));
- assertTrue(mBuilder.equalNumbers("user@host.com", "user@HOST.COM"));
- assertFalse(mBuilder.equalNumbers("USER@host.com", "user@host.com"));
- assertFalse(mBuilder.equalNumbers("user@host.com", "user@host1.com"));
- // SIP address vs phone number.
- assertFalse(mBuilder.equalNumbers("6505555555@host.com", "6505555555"));
- assertFalse(mBuilder.equalNumbers("6505555555", "6505555555@host.com"));
- assertFalse(mBuilder.equalNumbers("user@host.com", "6505555555"));
- assertFalse(mBuilder.equalNumbers("6505555555", "user@host.com"));
- // Nulls.
- assertTrue(mBuilder.equalNumbers(null, null));
- assertFalse(mBuilder.equalNumbers(null, "6505555555"));
- assertFalse(mBuilder.equalNumbers("6505555555", null));
- assertFalse(mBuilder.equalNumbers(null, "6505555555@host.com"));
- assertFalse(mBuilder.equalNumbers("6505555555@host.com", null));
- }
-
- public void testCompareSipAddresses() {
- // Identical.
- assertTrue(mBuilder.compareSipAddresses("6505555555@host.com", "6505555555@host.com"));
- assertTrue(mBuilder.compareSipAddresses("user@host.com", "user@host.com"));
- // Host is case insensitive.
- assertTrue(mBuilder.compareSipAddresses("6505555555@host.com", "6505555555@HOST.COM"));
- assertTrue(mBuilder.compareSipAddresses("user@host.com", "user@HOST.COM"));
- // Userinfo is case sensitive.
- assertFalse(mBuilder.compareSipAddresses("USER@host.com", "user@host.com"));
- // Different hosts.
- assertFalse(mBuilder.compareSipAddresses("user@host.com", "user@host1.com"));
- // Different users.
- assertFalse(mBuilder.compareSipAddresses("user1@host.com", "user@host.com"));
- // Nulls.
- assertTrue(mBuilder.compareSipAddresses(null, null));
- assertFalse(mBuilder.compareSipAddresses(null, "6505555555@host.com"));
- assertFalse(mBuilder.compareSipAddresses("6505555555@host.com", null));
- }
-
- /** Creates (or recreates) the cursor used to store the call log content for the tests. */
- private void createCursor() {
- mCursor = new MatrixCursor(CallLogQuery.EXTENDED_PROJECTION);
- }
-
- /** Clears the content of the {@link FakeGroupCreator} used in the tests. */
- private void clearFakeGroupCreator() {
- mFakeGroupCreator.groups.clear();
- }
-
- /** Asserts that calls of the given types are grouped together into a single group. */
- private void assertCallsAreGrouped(int... types) {
- createCursor();
- clearFakeGroupCreator();
- addMultipleOldCallLogEntries(TEST_NUMBER1, types);
- mBuilder.addGroups(mCursor);
- assertEquals(1, mFakeGroupCreator.groups.size());
- assertGroupIs(0, types.length, false, mFakeGroupCreator.groups.get(0));
-
- }
-
- /** Asserts that calls of the given types are not grouped together at all. */
- private void assertCallsAreNotGrouped(int... types) {
- createCursor();
- clearFakeGroupCreator();
- addMultipleOldCallLogEntries(TEST_NUMBER1, types);
- mBuilder.addGroups(mCursor);
- assertEquals(0, mFakeGroupCreator.groups.size());
- }
-
- /** Adds a set of calls with the given types, all from the same number, in the old section. */
- private void addMultipleOldCallLogEntries(String number, int... types) {
- for (int type : types) {
- addOldCallLogEntry(number, type);
- }
- }
-
- /** Adds a call with the given number and type to the old section of the call log. */
- private void addOldCallLogEntry(String number, int type) {
- addCallLogEntry(number, type, CallLogQuery.SECTION_OLD_ITEM);
- }
-
- /** Adds a call with the given number and type to the new section of the call log. */
- private void addNewCallLogEntry(String number, int type) {
- addCallLogEntry(number, type, CallLogQuery.SECTION_NEW_ITEM);
- }
-
- /** Adds a call log entry with the given number and type to the cursor. */
- private void addCallLogEntry(String number, int type, int section) {
- if (section != CallLogQuery.SECTION_NEW_ITEM
- && section != CallLogQuery.SECTION_OLD_ITEM) {
- throw new IllegalArgumentException("not an item section: " + section);
- }
- mCursor.moveToNext();
- Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
- values[CallLogQuery.ID] = mCursor.getPosition();
- values[CallLogQuery.NUMBER] = number;
- values[CallLogQuery.CALL_TYPE] = type;
- values[CallLogQuery.SECTION] = section;
- mCursor.addRow(values);
- }
-
- /** Adds the old section header to the call log. */
- private void addOldCallLogHeader() {
- addCallLogHeader(CallLogQuery.SECTION_OLD_HEADER);
- }
-
- /** Adds the new section header to the call log. */
- private void addNewCallLogHeader() {
- addCallLogHeader(CallLogQuery.SECTION_NEW_HEADER);
- }
-
- /** Adds a call log entry with a header to the cursor. */
- private void addCallLogHeader(int section) {
- if (section != CallLogQuery.SECTION_NEW_HEADER
- && section != CallLogQuery.SECTION_OLD_HEADER) {
- throw new IllegalArgumentException("not a header section: " + section);
- }
- mCursor.moveToNext();
- Object[] values = CallLogQueryTestUtils.createTestExtendedValues();
- values[CallLogQuery.ID] = mCursor.getPosition();
- values[CallLogQuery.SECTION] = section;
- mCursor.addRow(values);
- }
-
- /** Asserts that the group matches the given values. */
- private void assertGroupIs(int cursorPosition, int size, boolean expanded, GroupSpec group) {
- assertEquals(cursorPosition, group.cursorPosition);
- assertEquals(size, group.size);
- assertEquals(expanded, group.expanded);
- }
-
- /** Defines an added group. Used by the {@link FakeGroupCreator}. */
- private static class GroupSpec {
- /** The starting position of the group. */
- public final int cursorPosition;
- /** The number of elements in the group. */
- public final int size;
- /** Whether the group should be initially expanded. */
- public final boolean expanded;
-
- public GroupSpec(int cursorPosition, int size, boolean expanded) {
- this.cursorPosition = cursorPosition;
- this.size = size;
- this.expanded = expanded;
- }
- }
-
- /** Fake implementation of a GroupCreator which stores the created groups in a member field. */
- private static class FakeGroupCreator implements CallLogGroupBuilder.GroupCreator {
- /** The list of created groups. */
- public final List<GroupSpec> groups = newArrayList();
-
- @Override
- public void addGroup(int cursorPosition, int size, boolean expanded) {
- groups.add(new GroupSpec(cursorPosition, size, expanded));
- }
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
deleted file mode 100644
index a184f75..0000000
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.provider.CallLog.Calls;
-import android.test.AndroidTestCase;
-import android.view.View;
-
-import com.android.contacts.PhoneCallDetails;
-import com.android.contacts.PhoneCallDetailsHelper;
-import com.android.internal.telephony.CallerInfo;
-
-/**
- * Unit tests for {@link CallLogListItemHelper}.
- */
-public class CallLogListItemHelperTest extends AndroidTestCase {
- /** A test phone number for phone calls. */
- private static final String TEST_NUMBER = "14125555555";
- /** The formatted version of {@link #TEST_NUMBER}. */
- private static final String TEST_FORMATTED_NUMBER = "1-412-255-5555";
- /** A test date value for phone calls. */
- private static final long TEST_DATE = 1300000000;
- /** A test duration value for phone calls. */
- private static final long TEST_DURATION = 62300;
- /** A test voicemail number. */
- private static final String TEST_VOICEMAIL_NUMBER = "123";
- /** The country ISO name used in the tests. */
- private static final String TEST_COUNTRY_ISO = "US";
- /** The geocoded location used in the tests. */
- private static final String TEST_GEOCODE = "United States";
-
- /** The object under test. */
- private CallLogListItemHelper mHelper;
-
- /** The views used in the tests. */
- private CallLogListItemViews mViews;
- private PhoneNumberHelper mPhoneNumberHelper;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- Context context = getContext();
- Resources resources = context.getResources();
- CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
- mPhoneNumberHelper = new TestPhoneNumberHelper(resources, TEST_VOICEMAIL_NUMBER);
- PhoneCallDetailsHelper phoneCallDetailsHelper = new PhoneCallDetailsHelper(
- resources, callTypeHelper, mPhoneNumberHelper);
- mHelper = new CallLogListItemHelper(phoneCallDetailsHelper, mPhoneNumberHelper, resources);
- mViews = CallLogListItemViews.createForTest(context);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mHelper = null;
- mViews = null;
- super.tearDown();
- }
-
- public void testSetPhoneCallDetails() {
- setPhoneCallDetailsWithNumber("12125551234", "1-212-555-1234");
- assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
- }
-
- public void testSetPhoneCallDetails_Unknown() {
- setPhoneCallDetailsWithNumber(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER);
- assertNoCallButton();
- }
-
- public void testSetPhoneCallDetails_Private() {
- setPhoneCallDetailsWithNumber(CallerInfo.PRIVATE_NUMBER, CallerInfo.PRIVATE_NUMBER);
- assertNoCallButton();
- }
-
- public void testSetPhoneCallDetails_Payphone() {
- setPhoneCallDetailsWithNumber(CallerInfo.PAYPHONE_NUMBER, CallerInfo.PAYPHONE_NUMBER);
- assertNoCallButton();
- }
-
- public void testSetPhoneCallDetails_VoicemailNumber() {
- setPhoneCallDetailsWithNumber(TEST_VOICEMAIL_NUMBER, TEST_VOICEMAIL_NUMBER);
- assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
- }
-
- public void testSetPhoneCallDetails_ReadVoicemail() {
- setPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
- assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
- }
-
- public void testSetPhoneCallDetails_UnreadVoicemail() {
- setUnreadPhoneCallDetailsWithTypes(Calls.VOICEMAIL_TYPE);
- assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
- }
-
- public void testSetPhoneCallDetails_VoicemailFromUnknown() {
- setPhoneCallDetailsWithNumberAndType(CallerInfo.UNKNOWN_NUMBER, CallerInfo.UNKNOWN_NUMBER,
- Calls.VOICEMAIL_TYPE);
- assertEquals(View.VISIBLE, mViews.secondaryActionView.getVisibility());
- }
-
- /** Asserts that the whole call area is gone. */
- private void assertNoCallButton() {
- assertEquals(View.GONE, mViews.secondaryActionView.getVisibility());
- assertEquals(View.GONE, mViews.dividerView.getVisibility());
- }
-
- /** Sets the details of a phone call using the specified phone number. */
- private void setPhoneCallDetailsWithNumber(String number, String formattedNumber) {
- setPhoneCallDetailsWithNumberAndType(number, formattedNumber, Calls.INCOMING_TYPE);
- }
-
- /** Sets the details of a phone call using the specified phone number. */
- private void setPhoneCallDetailsWithNumberAndType(String number, String formattedNumber,
- int callType) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(number, formattedNumber, TEST_COUNTRY_ISO, TEST_GEOCODE,
- new int[]{ callType }, TEST_DATE, TEST_DURATION),
- false);
- }
-
- /** Sets the details of a phone call using the specified call type. */
- private void setPhoneCallDetailsWithTypes(int... types) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
- false);
- }
-
- /** Sets the details of a phone call using the specified call type. */
- private void setUnreadPhoneCallDetailsWithTypes(int... types) {
- mHelper.setPhoneCallDetails(mViews,
- new PhoneCallDetails(TEST_NUMBER, TEST_FORMATTED_NUMBER, TEST_COUNTRY_ISO,
- TEST_GEOCODE, types, TEST_DATE, TEST_DURATION),
- true);
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java b/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
deleted file mode 100644
index a88bf4f..0000000
--- a/tests/src/com/android/contacts/calllog/CallLogQueryTestUtils.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.provider.CallLog.Calls;
-
-import junit.framework.Assert;
-
-/**
- * Helper class to create test values for {@link CallLogQuery}.
- */
-public class CallLogQueryTestUtils {
- public static Object[] createTestValues() {
- Object[] values = new Object[]{
- 0L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null, null, null, null,
- 0L, null, 0,
- };
- assertEquals(CallLogQuery._PROJECTION.length, values.length);
- return values;
- }
-
- public static Object[] createTestExtendedValues() {
- Object[] values = new Object[]{
- 0L, "", 0L, 0L, Calls.INCOMING_TYPE, "", "", "", null, 0, null, null, null, null,
- 0L, null, 1, CallLogQuery.SECTION_OLD_ITEM
- };
- Assert.assertEquals(CallLogQuery.EXTENDED_PROJECTION.length, values.length);
- return values;
- }
-}
diff --git a/tests/src/com/android/contacts/calllog/TestPhoneNumberHelper.java b/tests/src/com/android/contacts/calllog/TestPhoneNumberHelper.java
deleted file mode 100644
index 2bbd978..0000000
--- a/tests/src/com/android/contacts/calllog/TestPhoneNumberHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2011 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.calllog;
-
-import android.content.res.Resources;
-
-/**
- * Modified version of {@link PhoneNumberHelper} to be used in tests that allows injecting the
- * voicemail number.
- */
-public final class TestPhoneNumberHelper extends PhoneNumberHelper {
- private CharSequence mVoicemailNumber;
-
- public TestPhoneNumberHelper(Resources resources, CharSequence voicemailNumber) {
- super(resources);
- mVoicemailNumber = voicemailNumber;
- }
-
- @Override
- public boolean isVoicemailNumber(CharSequence number) {
- return mVoicemailNumber.equals(number);
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
index fcbd83d..3b856ea 100644
--- a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -29,13 +29,13 @@
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.account.AccountType;
import com.android.contacts.model.account.BaseAccountType;
-import com.android.contacts.test.FragmentTestActivity;
+import com.android.contacts.common.test.FragmentTestActivity;
import com.android.contacts.test.InjectedServices;
import com.android.contacts.tests.mocks.ContactsMockContext;
import com.android.contacts.tests.mocks.MockAccountTypeManager;
import com.android.contacts.tests.mocks.MockContentProvider;
import com.android.contacts.tests.mocks.MockContentProvider.Query;
-import com.android.contacts.util.IntegrationTestUtils;
+import com.android.contacts.common.test.IntegrationTestUtils;
/**
* Tests for {@link ContactDeletionInteraction}.
diff --git a/tests/src/com/android/contacts/list/ContactListItemViewTest.java b/tests/src/com/android/contacts/list/ContactListItemViewTest.java
index 748876f..d05e8cf 100644
--- a/tests/src/com/android/contacts/list/ContactListItemViewTest.java
+++ b/tests/src/com/android/contacts/list/ContactListItemViewTest.java
@@ -25,7 +25,7 @@
import com.android.contacts.activities.PeopleActivity;
import com.android.contacts.format.SpannedTestUtils;
-import com.android.contacts.util.IntegrationTestUtils;
+import com.android.contacts.common.test.IntegrationTestUtils;
/**
* Unit tests for {@link ContactListItemView}.
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
index 519dc5c..d7ea5bf 100644
--- a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -512,7 +512,7 @@
}
final Intent intent = new Intent("android.intent.action.VIEW");
intent.setData(uri);
- bindIntentToClass(intent, "com.android.contacts.CallDetailActivity");
+ bindIntentToClass(intent, "com.android.dialer.CallDetailActivity");
startActivity(intent);
break;
}
diff --git a/tests/src/com/android/contacts/tests/calllog/FillCallLogTestActivity.java b/tests/src/com/android/contacts/tests/calllog/FillCallLogTestActivity.java
deleted file mode 100644
index d04d978..0000000
--- a/tests/src/com/android/contacts/tests/calllog/FillCallLogTestActivity.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2011 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.tests.calllog;
-
-import android.app.Activity;
-import android.app.LoaderManager;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.CallLog.Calls;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.contacts.tests.R;
-
-import java.util.Random;
-
-/**
- * Activity to add entries to the call log for testing.
- */
-public class FillCallLogTestActivity extends Activity {
- private static final String TAG = "FillCallLogTestActivity";
- /** Identifier of the loader for querying the call log. */
- private static final int CALLLOG_LOADER_ID = 1;
-
- private static final Random RNG = new Random();
- private static final int[] CALL_TYPES = new int[] {
- Calls.INCOMING_TYPE, Calls.OUTGOING_TYPE, Calls.MISSED_TYPE,
- };
-
- private TextView mNumberTextView;
- private Button mAddButton;
- private ProgressBar mProgressBar;
- private CheckBox mUseRandomNumbers;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.fill_call_log_test);
- mNumberTextView = (TextView) findViewById(R.id.number);
- mAddButton = (Button) findViewById(R.id.add);
- mProgressBar = (ProgressBar) findViewById(R.id.progress);
- mUseRandomNumbers = (CheckBox) findViewById(R.id.use_random_numbers);
-
- mAddButton.setOnClickListener(new View.OnClickListener(){
- @Override
- public void onClick(View v) {
- int count;
- try {
- count = Integer.parseInt(mNumberTextView.getText().toString());
- if (count > 100) {
- throw new RuntimeException("Number too large. Max=100");
- }
- } catch (RuntimeException e) {
- Toast.makeText(FillCallLogTestActivity.this, e.toString(), Toast.LENGTH_LONG)
- .show();
- return;
- }
- addEntriesToCallLog(count, mUseRandomNumbers.isChecked());
- mNumberTextView.setEnabled(false);
- mAddButton.setEnabled(false);
- mProgressBar.setProgress(0);
- mProgressBar.setMax(count);
- mProgressBar.setVisibility(View.VISIBLE);
- }
- });
- }
-
- /**
- * Adds a number of entries to the call log. The content of the entries is based on existing
- * entries.
- *
- * @param count the number of entries to add
- */
- private void addEntriesToCallLog(final int count, boolean useRandomNumbers) {
- if (useRandomNumbers) {
- addRandomNumbers(count);
- } else {
- getLoaderManager().initLoader(CALLLOG_LOADER_ID, null,
- new CallLogLoaderListener(count));
- }
- }
-
- /**
- * Calls when the insertion has completed.
- *
- * @param message the message to show in a toast to the user
- */
- private void insertCompleted(String message) {
- // Hide the progress bar.
- mProgressBar.setVisibility(View.GONE);
- // Re-enable the add button.
- mNumberTextView.setEnabled(true);
- mAddButton.setEnabled(true);
- mNumberTextView.setText("");
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
-
-
- /**
- * Creates a {@link ContentValues} object containing values corresponding to the given cursor.
- *
- * @param cursor the cursor from which to get the values
- * @return a newly created content values object
- */
- private ContentValues createContentValuesFromCursor(Cursor cursor) {
- ContentValues values = new ContentValues();
- for (int column = 0; column < cursor.getColumnCount();
- ++column) {
- String name = cursor.getColumnName(column);
- switch (cursor.getType(column)) {
- case Cursor.FIELD_TYPE_STRING:
- values.put(name, cursor.getString(column));
- break;
- case Cursor.FIELD_TYPE_INTEGER:
- values.put(name, cursor.getLong(column));
- break;
- case Cursor.FIELD_TYPE_FLOAT:
- values.put(name, cursor.getDouble(column));
- break;
- case Cursor.FIELD_TYPE_BLOB:
- values.put(name, cursor.getBlob(column));
- break;
- case Cursor.FIELD_TYPE_NULL:
- values.putNull(name);
- break;
- default:
- Log.d(TAG, "Invalid value in cursor: " + cursor.getType(column));
- break;
- }
- }
- return values;
- }
-
- private void addRandomNumbers(int count) {
- ContentValues[] values = new ContentValues[count];
- for (int i = 0; i < count; i++) {
- values[i] = new ContentValues();
- values[i].put(Calls.NUMBER, generateRandomNumber());
- values[i].put(Calls.DATE, System.currentTimeMillis()); // Will be randomized later
- values[i].put(Calls.DURATION, 1); // Will be overwritten later
- }
- new AsyncCallLogInserter(values).execute(new Void[0]);
- }
-
- private static String generateRandomNumber() {
- return String.format("5%09d", RNG.nextInt(1000000000));
- }
-
- /** Invokes {@link AsyncCallLogInserter} when the call log has loaded. */
- private final class CallLogLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
- /** The number of items to insert when done. */
- private final int mCount;
-
- private CallLogLoaderListener(int count) {
- mCount = count;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- Log.d(TAG, "onCreateLoader");
- return new CursorLoader(FillCallLogTestActivity.this, Calls.CONTENT_URI,
- null, null, null, null);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- try {
- Log.d(TAG, "onLoadFinished");
-
- if (data.getCount() == 0) {
- // If there are no entries in the call log, we cannot generate new ones.
- insertCompleted(getString(R.string.noLogEntriesToast));
- return;
- }
-
- data.moveToPosition(-1);
-
- ContentValues[] values = new ContentValues[mCount];
- for (int index = 0; index < mCount; ++index) {
- if (!data.moveToNext()) {
- data.moveToFirst();
- }
- values[index] = createContentValuesFromCursor(data);
- }
- new AsyncCallLogInserter(values).execute(new Void[0]);
- } finally {
- // This is a one shot loader.
- getLoaderManager().destroyLoader(CALLLOG_LOADER_ID);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {}
- }
-
- /** Inserts a given number of entries in the call log based on the values given. */
- private final class AsyncCallLogInserter extends AsyncTask<Void, Integer, Integer> {
- /** The number of items to insert. */
- private final ContentValues[] mValues;
-
- public AsyncCallLogInserter(ContentValues[] values) {
- mValues = values;
- }
-
- @Override
- protected Integer doInBackground(Void... params) {
- Log.d(TAG, "doInBackground");
- return insertIntoCallLog();
- }
-
- @Override
- protected void onProgressUpdate(Integer... values) {
- Log.d(TAG, "onProgressUpdate");
- updateCount(values[0]);
- }
-
- @Override
- protected void onPostExecute(Integer count) {
- Log.d(TAG, "onPostExecute");
- insertCompleted(getString(R.string.addedLogEntriesToast, count));
- }
-
- /**
- * Inserts a number of entries in the call log based on the given templates.
- *
- * @return the number of inserted entries
- */
- private Integer insertIntoCallLog() {
- int inserted = 0;
-
- for (int index = 0; index < mValues.length; ++index) {
- ContentValues values = mValues[index];
- // These should not be set.
- values.putNull(Calls._ID);
- // Add some randomness to the date. For each new entry being added, add an extra
- // day to the maximum possible offset from the original.
- values.put(Calls.DATE,
- values.getAsLong(Calls.DATE)
- - RNG.nextInt(24 * 60 * 60 * (index + 1)) * 1000L);
- // Add some randomness to the duration.
- if (values.getAsLong(Calls.DURATION) > 0) {
- values.put(Calls.DURATION, RNG.nextInt(30 * 60 * 60 * 1000));
- }
-
- // Overwrite type.
- values.put(Calls.TYPE, CALL_TYPES[RNG.nextInt(CALL_TYPES.length)]);
-
- // Clear cached columns.
- values.putNull(Calls.CACHED_FORMATTED_NUMBER);
- values.putNull(Calls.CACHED_LOOKUP_URI);
- values.putNull(Calls.CACHED_MATCHED_NUMBER);
- values.putNull(Calls.CACHED_NAME);
- values.putNull(Calls.CACHED_NORMALIZED_NUMBER);
- values.putNull(Calls.CACHED_NUMBER_LABEL);
- values.putNull(Calls.CACHED_NUMBER_TYPE);
- values.putNull(Calls.CACHED_PHOTO_ID);
-
- // Insert into the call log the newly generated entry.
- ContentProviderClient contentProvider =
- getContentResolver().acquireContentProviderClient(
- Calls.CONTENT_URI);
- try {
- Log.d(TAG, "adding entry to call log");
- contentProvider.insert(Calls.CONTENT_URI, values);
- ++inserted;
- this.publishProgress(inserted);
- } catch (RemoteException e) {
- Log.d(TAG, "insert failed", e);
- }
- }
- return inserted;
- }
- }
-
- /**
- * Updates the count shown to the user corresponding to the number of entries added.
- *
- * @param count the number of entries inserted so far
- */
- public void updateCount(Integer count) {
- mProgressBar.setProgress(count);
- }
-}
diff --git a/tests/src/com/android/contacts/util/ExpirableCacheTest.java b/tests/src/com/android/contacts/util/ExpirableCacheTest.java
deleted file mode 100644
index 33e176e..0000000
--- a/tests/src/com/android/contacts/util/ExpirableCacheTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.LruCache;
-
-import com.android.contacts.util.ExpirableCache.CachedValue;
-
-/**
- * Unit tests for {@link ExpirableCache}.
- */
-@SmallTest
-public class ExpirableCacheTest extends AndroidTestCase {
- /** The object under test. */
- private ExpirableCache<String, Integer> mCache;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- LruCache<String, CachedValue<Integer>> lruCache =
- new LruCache<String, ExpirableCache.CachedValue<Integer>>(20);
- mCache = ExpirableCache.create(lruCache);
- }
-
- @Override
- protected void tearDown() throws Exception {
- mCache = null;
- super.tearDown();
- }
-
- public void testPut() {
- mCache.put("a", 1);
- mCache.put("b", 2);
- assertEquals(1, mCache.getPossiblyExpired("a").intValue());
- assertEquals(2, mCache.getPossiblyExpired("b").intValue());
- mCache.put("a", 3);
- assertEquals(3, mCache.getPossiblyExpired("a").intValue());
- }
-
- public void testGet_NotExisting() {
- assertNull(mCache.getPossiblyExpired("a"));
- mCache.put("b", 1);
- assertNull(mCache.getPossiblyExpired("a"));
- }
-
- public void testGet_Expired() {
- mCache.put("a", 1);
- assertEquals(1, mCache.getPossiblyExpired("a").intValue());
- mCache.expireAll();
- assertEquals(1, mCache.getPossiblyExpired("a").intValue());
- }
-
- public void testGetNotExpired_NotExisting() {
- assertNull(mCache.get("a"));
- mCache.put("b", 1);
- assertNull(mCache.get("a"));
- }
-
- public void testGetNotExpired_Expired() {
- mCache.put("a", 1);
- assertEquals(1, mCache.get("a").intValue());
- mCache.expireAll();
- assertNull(mCache.get("a"));
- }
-
- public void testGetCachedValue_NotExisting() {
- assertNull(mCache.getCachedValue("a"));
- mCache.put("b", 1);
- assertNull(mCache.getCachedValue("a"));
- }
-
- public void testGetCachedValue_Expired() {
- mCache.put("a", 1);
- assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
- mCache.expireAll();
- assertTrue("Should be expired", mCache.getCachedValue("a").isExpired());
- }
-
- public void testGetChangedValue_PutAfterExpired() {
- mCache.put("a", 1);
- mCache.expireAll();
- mCache.put("a", 1);
- assertFalse("Should not be expired", mCache.getCachedValue("a").isExpired());
- }
-
- public void testComputingCache() {
- // Creates a cache in which all unknown values default to zero.
- mCache = ExpirableCache.create(
- new LruCache<String, ExpirableCache.CachedValue<Integer>>(10) {
- @Override
- protected CachedValue<Integer> create(String key) {
- return mCache.newCachedValue(0);
- }
- });
-
- // The first time we request a new value, we add it to the cache.
- CachedValue<Integer> cachedValue = mCache.getCachedValue("a");
- assertNotNull("Should have been created implicitly", cachedValue);
- assertEquals(0, cachedValue.getValue().intValue());
- assertFalse("Should not be expired", cachedValue.isExpired());
-
- // If we expire all the values, the implicitly created value will also be marked as expired.
- mCache.expireAll();
- CachedValue<Integer> expiredCachedValue = mCache.getCachedValue("a");
- assertNotNull("Should have been created implicitly", expiredCachedValue);
- assertEquals(0, expiredCachedValue.getValue().intValue());
- assertTrue("Should be expired", expiredCachedValue.isExpired());
- }
-}
diff --git a/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java b/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
deleted file mode 100644
index 8c97f21..0000000
--- a/tests/src/com/android/contacts/util/FakeAsyncTaskExecutor.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import android.app.Instrumentation;
-import android.os.AsyncTask;
-
-import com.google.common.collect.Lists;
-
-import junit.framework.Assert;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Test implementation of AsyncTaskExecutor.
- * <p>
- * This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must
- * be called from the main ui thread, however the other public methods may be called from any thread
- * (most commonly the test thread).
- * <p>
- * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
- * list of submitted tasks, where they can be examined. They can also be run on-demand using the run
- * methods, so that different ordering of AsyncTask execution can be simulated.
- * <p>
- * The onPreExecute method of the submitted AsyncTask will be called synchronously during the
- * call to {@link #submit(Object, AsyncTask, Object...)}.
- */
-@ThreadSafe
-public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
- private static final long DEFAULT_TIMEOUT_MS = 10000;
-
- /** The maximum length of time in ms to wait for tasks to execute during tests. */
- private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
-
- private final Object mLock = new Object();
- @GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList();
-
- private final DelayedExecutor mBlockingExecutor = new DelayedExecutor();
- private final Instrumentation mInstrumentation;
-
- /** Create a fake AsyncTaskExecutor for use in unit tests. */
- public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
- Assert.assertNotNull(instrumentation);
- mInstrumentation = instrumentation;
- }
-
- /** Encapsulates an async task with the params and identifier it was submitted with. */
- public interface SubmittedTask {
- Runnable getRunnable();
- Object getIdentifier();
- AsyncTask<?, ?, ?> getAsyncTask();
- }
-
- private static final class SubmittedTaskImpl implements SubmittedTask {
- private final Object mIdentifier;
- private final Runnable mRunnable;
- private final AsyncTask<?, ?, ?> mAsyncTask;
-
- public SubmittedTaskImpl(Object identifier, Runnable runnable,
- AsyncTask<?, ?, ?> asyncTask) {
- mIdentifier = identifier;
- mRunnable = runnable;
- mAsyncTask = asyncTask;
- }
-
- @Override
- public Object getIdentifier() {
- return mIdentifier;
- }
-
- @Override
- public Runnable getRunnable() {
- return mRunnable;
- }
-
- @Override
- public AsyncTask<?, ?, ?> getAsyncTask() {
- return mAsyncTask;
- }
-
- @Override
- public String toString() {
- return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]";
- }
- }
-
- private class DelayedExecutor implements Executor {
- private final Object mNextLock = new Object();
- @GuardedBy("mNextLock") private Object mNextIdentifier;
- @GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask;
-
- @Override
- public void execute(Runnable command) {
- synchronized (mNextLock) {
- Assert.assertNotNull(mNextTask);
- mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier,
- command, mNextTask));
- mNextIdentifier = null;
- mNextTask = null;
- }
- }
-
- public <T> AsyncTask<T, ?, ?> submit(Object identifier,
- AsyncTask<T, ?, ?> task, T... params) {
- synchronized (mNextLock) {
- Assert.assertNull(mNextIdentifier);
- Assert.assertNull(mNextTask);
- mNextIdentifier = identifier;
- Assert.assertNotNull("Already had a valid task.\n"
- + "Are you calling AsyncTaskExecutor.submit(...) from within the "
- + "onPreExecute() method of another task being submitted?\n"
- + "Sorry! Not that's not supported.", task);
- mNextTask = task;
- }
- return task.executeOnExecutor(this, params);
- }
- }
-
- @Override
- public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
- AsyncTaskExecutors.checkCalledFromUiThread();
- return mBlockingExecutor.submit(identifier, task, params);
- }
-
- /**
- * Runs a single task matching the given identifier.
- * <p>
- * Removes the matching task from the list of submitted tasks, then runs it. The executor used
- * to execute this async task will be a same-thread executor.
- * <p>
- * Fails if there was not exactly one task matching the given identifier.
- * <p>
- * This method blocks until the AsyncTask has completely finished executing.
- */
- public void runTask(Object identifier) throws InterruptedException {
- List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
- Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
- runTask(tasks.get(0));
- }
-
- /**
- * Runs all tasks whose identifier matches the given identifier.
- * <p>
- * Removes all matching tasks from the list of submitted tasks, and runs them. The executor used
- * to execute these async tasks will be a same-thread executor.
- * <p>
- * Fails if there were no tasks matching the given identifier.
- * <p>
- * This method blocks until the AsyncTask objects have completely finished executing.
- */
- public void runAllTasks(Object identifier) throws InterruptedException {
- List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
- Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
- for (SubmittedTask task : tasks) {
- runTask(task);
- }
- }
-
- /**
- * Executes a single {@link SubmittedTask}.
- * <p>
- * Blocks until the task has completed running.
- */
- private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException {
- submittedTask.getRunnable().run();
- // Block until the onPostExecute or onCancelled has finished.
- // Unfortunately we can't be sure when the AsyncTask will have posted its result handling
- // code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
- final CountDownLatch latch = new CountDownLatch(1);
- class AsyncTaskHasFinishedRunnable implements Runnable {
- @Override
- public void run() {
- if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) {
- latch.countDown();
- } else {
- mInstrumentation.waitForIdle(this);
- }
- }
- }
- mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable());
- Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
- }
-
- private List<SubmittedTask> getSubmittedTasksByIdentifier(
- Object identifier, boolean remove) {
- Assert.assertNotNull(identifier);
- List<SubmittedTask> results = Lists.newArrayList();
- synchronized (mLock) {
- Iterator<SubmittedTask> iter = mSubmittedTasks.iterator();
- while (iter.hasNext()) {
- SubmittedTask task = iter.next();
- if (identifier.equals(task.getIdentifier())) {
- results.add(task);
- iter.remove();
- }
- }
- }
- return results;
- }
-
- /** Get a factory that will return this instance - useful for testing. */
- public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() {
- return new AsyncTaskExecutors.AsyncTaskExecutorFactory() {
- @Override
- public AsyncTaskExecutor createAsyncTaskExeuctor() {
- return FakeAsyncTaskExecutor.this;
- }
- };
- }
-}
diff --git a/tests/src/com/android/contacts/util/IntegrationTestUtils.java b/tests/src/com/android/contacts/util/IntegrationTestUtils.java
deleted file mode 100644
index 8c9dd6c..0000000
--- a/tests/src/com/android/contacts/util/IntegrationTestUtils.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP;
-import static android.os.PowerManager.FULL_WAKE_LOCK;
-import static android.os.PowerManager.ON_AFTER_RELEASE;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.os.PowerManager;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.google.common.base.Preconditions;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.ThreadSafe;
-
-/** Some utility methods for making integration testing smoother. */
-@ThreadSafe
-public class IntegrationTestUtils {
- private static final String TAG = "IntegrationTestUtils";
-
- private final Instrumentation mInstrumentation;
- private final Object mLock = new Object();
- @GuardedBy("mLock") private PowerManager.WakeLock mWakeLock;
-
- public IntegrationTestUtils(Instrumentation instrumentation) {
- mInstrumentation = instrumentation;
- }
-
- /**
- * Find a view by a given resource id, from the given activity, and click it, iff it is
- * enabled according to {@link View#isEnabled()}.
- */
- public void clickButton(final Activity activity, final int buttonResourceId) throws Throwable {
- runOnUiThreadAndGetTheResult(new Callable<Void>() {
- @Override
- public Void call() throws Exception {
- View view = activity.findViewById(buttonResourceId);
- Assert.assertNotNull(view);
- if (view.isEnabled()) {
- view.performClick();
- }
- return null;
- }
- });
- }
-
- /** Returns the result of running {@link TextView#getText()} on the ui thread. */
- public CharSequence getText(final TextView view) throws Throwable {
- return runOnUiThreadAndGetTheResult(new Callable<CharSequence>() {
- @Override
- public CharSequence call() {
- return view.getText();
- }
- });
- }
-
- // TODO: Move this class and the appropriate documentation into a test library, having checked
- // first to see if exactly this code already exists or not.
- /**
- * Execute a callable on the ui thread, returning its result synchronously.
- * <p>
- * Waits for an idle sync on the main thread (see {@link Instrumentation#waitForIdle(Runnable)})
- * before executing this callable.
- */
- public <T> T runOnUiThreadAndGetTheResult(Callable<T> callable) throws Throwable {
- FutureTask<T> future = new FutureTask<T>(callable);
- mInstrumentation.waitForIdle(future);
- try {
- return future.get();
- } catch (ExecutionException e) {
- // Unwrap the cause of the exception and re-throw it.
- throw e.getCause();
- }
- }
-
- /**
- * Wake up the screen, useful in tests that want or need the screen to be on.
- * <p>
- * This is usually called from setUp() for tests that require it. After calling this method,
- * {@link #releaseScreenWakeLock()} must be called, this is usually done from tearDown().
- */
- public void acquireScreenWakeLock(Context context) {
- synchronized (mLock) {
- Preconditions.checkState(mWakeLock == null, "mWakeLock was already held");
- mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
- .newWakeLock(ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE | FULL_WAKE_LOCK, TAG);
- mWakeLock.acquire();
- }
- }
-
- /** Release the wake lock previously acquired with {@link #acquireScreenWakeLock(Context)}. */
- public void releaseScreenWakeLock() {
- synchronized (mLock) {
- // We don't use Preconditions to force you to have acquired before release.
- // This is because we don't want unnecessary exceptions in tearDown() since they'll
- // typically mask the actual exception that happened during the test.
- // The other reason is that this method is most likely to be called from tearDown(),
- // which is invoked within a finally block, so it's not infrequently the case that
- // the setUp() method fails before getting the lock, at which point we don't want
- // to fail in tearDown().
- if (mWakeLock != null) {
- mWakeLock.release();
- mWakeLock = null;
- }
- }
- }
-
- /**
- * Gets all {@link TextView} objects whose {@link TextView#getText()} contains the given text as
- * a substring.
- */
- public List<TextView> getTextViewsWithString(final Activity activity, final String text)
- throws Throwable {
- return runOnUiThreadAndGetTheResult(new Callable<List<TextView>>() {
- @Override
- public List<TextView> call() throws Exception {
- List<TextView> matchingViews = new ArrayList<TextView>();
- for (TextView textView : getAllViews(TextView.class, getRootView(activity))) {
- if (textView.getText().toString().contains(text)) {
- matchingViews.add(textView);
- }
- }
- return matchingViews;
- }
- });
- }
-
- /** Find the root view for a given activity. */
- public static View getRootView(Activity activity) {
- return activity.findViewById(android.R.id.content).getRootView();
- }
-
- /**
- * Gets a list of all views of a given type, rooted at the given parent.
- * <p>
- * This method will recurse down through all {@link ViewGroup} instances looking for
- * {@link View} instances of the supplied class type. Specifically it will use the
- * {@link Class#isAssignableFrom(Class)} method as the test for which views to add to the list,
- * so if you provide {@code View.class} as your type, you will get every view. The parent itself
- * will be included also, should it be of the right type.
- * <p>
- * This call manipulates the ui, and as such should only be called from the application's main
- * thread.
- */
- private static <T extends View> List<T> getAllViews(final Class<T> clazz, final View parent) {
- List<T> results = new ArrayList<T>();
- if (parent.getClass().equals(clazz)) {
- results.add(clazz.cast(parent));
- }
- if (parent instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) parent;
- for (int i = 0; i < viewGroup.getChildCount(); ++i) {
- results.addAll(getAllViews(clazz, viewGroup.getChildAt(i)));
- }
- }
- return results;
- }
-}
diff --git a/tests/src/com/android/contacts/util/LocaleTestUtils.java b/tests/src/com/android/contacts/util/LocaleTestUtils.java
deleted file mode 100644
index e0a8670..0000000
--- a/tests/src/com/android/contacts/util/LocaleTestUtils.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2011 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.util;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-
-import java.util.Locale;
-
-/**
- * Utility class to save and restore the locale of the system.
- * <p>
- * This can be used for tests that assume to be run in a certain locale, e.g., because they
- * check against strings in a particular language or require an assumption on how the system
- * will behave in a specific locale.
- * <p>
- * In your test, you can change the locale with the following code:
- * <pre>
- * public class CanadaFrenchTest extends AndroidTestCase {
- * private LocaleTestUtils mLocaleTestUtils;
- *
- * @Override
- * public void setUp() throws Exception {
- * super.setUp();
- * mLocaleTestUtils = new LocaleTestUtils(getContext());
- * mLocaleTestUtils.setLocale(Locale.CANADA_FRENCH);
- * }
- *
- * @Override
- * public void tearDown() throws Exception {
- * mLocaleTestUtils.restoreLocale();
- * mLocaleTestUtils = null;
- * super.tearDown();
- * }
- *
- * ...
- * }
- * </pre>
- * Note that one should not call {@link #setLocale(Locale)} more than once without calling
- * {@link #restoreLocale()} first.
- * <p>
- * This class is not thread-safe. Usually its methods should be invoked only from the test thread.
- */
-public class LocaleTestUtils {
- private final Context mContext;
- private boolean mSaved;
- private Locale mSavedContextLocale;
- private Locale mSavedSystemLocale;
-
- /**
- * Create a new instance that can be used to set and reset the locale for the given context.
- *
- * @param context the context on which to alter the locale
- */
- public LocaleTestUtils(Context context) {
- mContext = context;
- mSaved = false;
- }
-
- /**
- * Set the locale to the given value and saves the previous value.
- *
- * @param locale the value to which the locale should be set
- * @throws IllegalStateException if the locale was already set
- */
- public void setLocale(Locale locale) {
- if (mSaved) {
- throw new IllegalStateException(
- "call restoreLocale() before calling setLocale() again");
- }
- mSavedContextLocale = setResourcesLocale(mContext.getResources(), locale);
- mSavedSystemLocale = setResourcesLocale(Resources.getSystem(), locale);
- mSaved = true;
- }
-
- /**
- * Restores the previously set locale.
- *
- * @throws IllegalStateException if the locale was not set using {@link #setLocale(Locale)}
- */
- public void restoreLocale() {
- if (!mSaved) {
- throw new IllegalStateException("call setLocale() before calling restoreLocale()");
- }
- setResourcesLocale(mContext.getResources(), mSavedContextLocale);
- setResourcesLocale(Resources.getSystem(), mSavedSystemLocale);
- mSaved = false;
- }
-
- /**
- * Sets the locale for the given resources and returns the previous locale.
- *
- * @param resources the resources on which to set the locale
- * @param locale the value to which to set the locale
- * @return the previous value of the locale for the resources
- */
- private Locale setResourcesLocale(Resources resources, Locale locale) {
- Configuration contextConfiguration = new Configuration(resources.getConfiguration());
- Locale savedLocale = contextConfiguration.locale;
- contextConfiguration.locale = locale;
- resources.updateConfiguration(contextConfiguration, null);
- return savedLocale;
- }
-}
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/voicemail/VoicemailStatusHelperImplTest.java b/tests/src/com/android/contacts/voicemail/VoicemailStatusHelperImplTest.java
deleted file mode 100644
index 801c162..0000000
--- a/tests/src/com/android/contacts/voicemail/VoicemailStatusHelperImplTest.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2011 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.voicemail;
-
-import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE;
-import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
-import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_NOT_CONFIGURED;
-import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE;
-import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
-import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
-import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.VoicemailContract.Status;
-import android.test.AndroidTestCase;
-
-import com.android.contacts.R;
-import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
-
-import java.util.List;
-
-/**
- * Unit tests for {@link VoicemailStatusHelperImpl}.
- */
-public class VoicemailStatusHelperImplTest extends AndroidTestCase {
- private static final String[] TEST_PACKAGES = new String[] {
- "com.test.package1",
- "com.test.package2"
- };
-
- private static final Uri TEST_SETTINGS_URI = Uri.parse("http://www.visual.voicemail.setup");
- private static final Uri TEST_VOICEMAIL_URI = Uri.parse("tel:901");
-
- private static final int ACTION_MSG_CALL_VOICEMAIL =
- R.string.voicemail_status_action_call_server;
- private static final int ACTION_MSG_CONFIGURE = R.string.voicemail_status_action_configure;
-
- private static final int STATUS_MSG_NONE = -1;
- private static final int STATUS_MSG_VOICEMAIL_NOT_AVAILABLE =
- R.string.voicemail_status_voicemail_not_available;
- private static final int STATUS_MSG_AUDIO_NOT_AVAIALABLE =
- R.string.voicemail_status_audio_not_available;
- private static final int STATUS_MSG_MESSAGE_WAITING = R.string.voicemail_status_messages_waiting;
- private static final int STATUS_MSG_INVITE_FOR_CONFIGURATION =
- R.string.voicemail_status_configure_voicemail;
-
- // Object under test.
- private VoicemailStatusHelper mStatusHelper;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mStatusHelper = new VoicemailStatusHelperImpl();
- }
-
- @Override
- protected void tearDown() throws Exception {
- for (String sourcePackage : TEST_PACKAGES) {
- deleteEntryForPackage(sourcePackage);
- }
- // Set member variables to null so that they are garbage collected across different runs
- // of the tests.
- mStatusHelper = null;
- super.tearDown();
- }
-
-
- public void testNoStatusEntries() {
- assertEquals(0, getStatusMessages().size());
- }
-
- public void testAllOK() {
- insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
- insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
- assertEquals(0, getStatusMessages().size());
- }
-
- public void testNotAllOKForOnePackage() {
- insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
- insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
-
- ContentValues values = new ContentValues();
- // Good data channel + no notification
- // action: call voicemail
- // msg: voicemail not available in call log page & none in call details page.
- values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
- values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
- STATUS_MSG_NONE, ACTION_MSG_CALL_VOICEMAIL);
-
- // Message waiting + good data channel - no action.
- values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
- values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkNoMessages(TEST_PACKAGES[1], values);
-
- // No data channel + no notification
- // action: call voicemail
- // msg: voicemail not available in call log page & audio not available in call details page.
- values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_OK);
- values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
- STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
-
- // No data channel + Notification OK
- // action: call voicemail
- // msg: voicemail not available in call log page & audio not available in call details page.
- values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
- values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
- STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
-
- // No data channel + Notification OK
- // action: call voicemail
- // msg: message waiting in call log page & audio not available in call details page.
- values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
- values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_MESSAGE_WAITING,
- STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);
-
- // Not configured. No user action, so no message.
- values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_NOT_CONFIGURED);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkNoMessages(TEST_PACKAGES[1], values);
-
- // Can be configured - invite user for configure voicemail.
- values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_CAN_BE_CONFIGURED);
- updateEntryForPackage(TEST_PACKAGES[1], values);
- checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_INVITE_FOR_CONFIGURATION,
- STATUS_MSG_NONE, ACTION_MSG_CONFIGURE, TEST_SETTINGS_URI);
- }
-
- // Test that priority of messages are handled well.
- public void testMessageOrdering() {
- insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
- insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
-
- final ContentValues valuesNoNotificationGoodDataChannel = new ContentValues();
- valuesNoNotificationGoodDataChannel.put(NOTIFICATION_CHANNEL_STATE,
- NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
- valuesNoNotificationGoodDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
-
- final ContentValues valuesNoNotificationNoDataChannel = new ContentValues();
- valuesNoNotificationNoDataChannel.put(NOTIFICATION_CHANNEL_STATE,
- NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
- valuesNoNotificationNoDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
-
- // Package1 with valuesNoNotificationGoodDataChannel and
- // package2 with valuesNoNotificationNoDataChannel. Package2 should be above.
- updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationGoodDataChannel);
- updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationNoDataChannel);
- List<StatusMessage> messages = getStatusMessages();
- assertEquals(2, messages.size());
- assertEquals(TEST_PACKAGES[0], messages.get(1).sourcePackage);
- assertEquals(TEST_PACKAGES[1], messages.get(0).sourcePackage);
-
- // Now reverse the values - ordering should be reversed as well.
- updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationNoDataChannel);
- updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationGoodDataChannel);
- messages = getStatusMessages();
- assertEquals(2, messages.size());
- assertEquals(TEST_PACKAGES[0], messages.get(0).sourcePackage);
- assertEquals(TEST_PACKAGES[1], messages.get(1).sourcePackage);
- }
-
- /** Checks that the expected source status message is returned by VoicemailStatusHelper. */
- private void checkExpectedMessage(String sourcePackage, ContentValues values,
- int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
- Uri expectedUri) {
- List<StatusMessage> messages = getStatusMessages();
- assertEquals(1, messages.size());
- checkMessageMatches(messages.get(0), sourcePackage, expectedCallLogMsg,
- expectedCallDetailsMsg, expectedActionMsg, expectedUri);
- }
-
- private void checkExpectedMessage(String sourcePackage, ContentValues values,
- int expectedCallLogMsg, int expectedCallDetailsMessage, int expectedActionMsg) {
- checkExpectedMessage(sourcePackage, values, expectedCallLogMsg, expectedCallDetailsMessage,
- expectedActionMsg, TEST_VOICEMAIL_URI);
- }
-
- private void checkMessageMatches(StatusMessage message, String expectedSourcePackage,
- int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
- Uri expectedUri) {
- assertEquals(expectedSourcePackage, message.sourcePackage);
- assertEquals(expectedCallLogMsg, message.callLogMessageId);
- assertEquals(expectedCallDetailsMsg, message.callDetailsMessageId);
- assertEquals(expectedActionMsg, message.actionMessageId);
- if (expectedUri == null) {
- assertNull(message.actionUri);
- } else {
- assertEquals(expectedUri, message.actionUri);
- }
- }
-
- private void checkNoMessages(String sourcePackage, ContentValues values) {
- assertEquals(1, updateEntryForPackage(sourcePackage, values));
- List<StatusMessage> messages = getStatusMessages();
- assertEquals(0, messages.size());
- }
-
- private ContentValues getAllOkStatusValues() {
- ContentValues values = new ContentValues();
- values.put(Status.SETTINGS_URI, TEST_SETTINGS_URI.toString());
- values.put(Status.VOICEMAIL_ACCESS_URI, TEST_VOICEMAIL_URI.toString());
- values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK);
- values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK);
- values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK);
- return values;
- }
-
- private void insertEntryForPackage(String sourcePackage, ContentValues values) {
- // If insertion fails then try update as the record might already exist.
- if (getContentResolver().insert(Status.buildSourceUri(sourcePackage), values) == null) {
- updateEntryForPackage(sourcePackage, values);
- }
- }
-
- private void deleteEntryForPackage(String sourcePackage) {
- getContentResolver().delete(Status.buildSourceUri(sourcePackage), null, null);
- }
-
- private int updateEntryForPackage(String sourcePackage, ContentValues values) {
- return getContentResolver().update(
- Status.buildSourceUri(sourcePackage), values, null, null);
- }
-
- private List<StatusMessage> getStatusMessages() {
- // Restrict the cursor to only the the test packages to eliminate any side effects if there
- // are other status messages already stored on the device.
- Cursor cursor = getContentResolver().query(Status.CONTENT_URI,
- VoicemailStatusHelperImpl.PROJECTION, getTestPackageSelection(), null, null);
- return mStatusHelper.getStatusMessages(cursor);
- }
-
- private String getTestPackageSelection() {
- StringBuilder sb = new StringBuilder();
- for (String sourcePackage : TEST_PACKAGES) {
- if (sb.length() > 0) {
- sb.append(" OR ");
- }
- sb.append(String.format("(source_package='%s')", sourcePackage));
- }
- return sb.toString();
- }
-
- private ContentResolver getContentResolver() {
- return getContext().getContentResolver();
- }
-}